スタックとキューの基本
algorithmdata-structuresundergraduatelecture
data/lecture/information/algorithm/data-structures/data-structures-portal.lecture.n.md
1導入
この講義では、スタックとキューを抽象データ型として定義し、操作の契約、空の構造に対する挙動、代表的な実装、計算量を説明する。さらに、両者の取出順序がDFSとBFSの探索順序を規定することを確認する。
2スタックの契約
スタック(stack)は、最後に追加した要素を最初に除去するLIFO(last in, first out)のADTである。基本操作を次のように定める。
push(x) は要素 x を頂上へ追加する。
pop() は頂上の要素を除去して返す。
top() は頂上の要素を除去せずに返す。
isEmpty() は要素数が 0 であるかを返す。
空のスタックに pop() または top() を適用した結果は、ADTの契約で明示する必要がある。本講義では、この操作を事前条件違反とし、呼出側が isEmpty() によって回避する。
push(a), push(b), pop() を順に実行すると、pop() は b を返し、a が残る。
3キューの契約
キュー(queue)は、最初に追加した要素を最初に除去するFIFO(first in, first out)のADTである。追加側を末尾、除去側を先頭と呼ぶ。
enqueue(x) は x を末尾へ追加する。
dequeue() は先頭の要素を除去して返す。
front() は先頭の要素を除去せずに返す。
isEmpty() は要素数が 0 であるかを返す。
空のキューに dequeue() または front() を適用することも事前条件違反とする。enqueue(a), enqueue(b), dequeue() を順に実行すると、dequeue() は a を返し、b が残る。
4実装と計算量
スタックは動的配列の末尾を頂上とすれば実装できる。容量を定数倍に拡張する動的配列では、push は償却 O(1)、pop、top、isEmpty は最悪時 O(1) である。固定容量配列では、満杯時の挙動を契約に追加する必要がある。
キューは循環配列と先頭・末尾の添字、または両端への参照をもつ連結リストで実装できる。固定容量の循環配列では、満杯でないことを enqueue の事前条件とすれば、すべての操作は最悪時 O(1) である。容量を定数倍に拡張する循環配列では、enqueue は償却 O(1) であるが、拡張時の最悪時間は O(n) である。連結リストでは、要素の割当を O(1) とする計算模型の下で、すべての操作が最悪時 O(1) となる。ただし、通常の配列で dequeue のたびに全要素を左へ移動する実装は O(n) 時間を要する。したがって、O(1) という評価はADT自体ではなく、選択した実装に依存する。
いずれも n 要素を保持する領域は O(n) である。
5DFSとBFSへの接続
深さ優先探索(DFS)は、発見済みで未処理の頂点をスタックで管理する。最後に追加した候補を優先するため、一方向の探索を継続してから後戻りする。再帰実装では、関数呼出しのスタックがこの役割を担う。
幅優先探索(BFS)は候補頂点をキューで管理する。先に発見した候補を先に処理するため、始点からの探索レベルが非減少順になる。このFIFO順序が、無重みグラフにおけるBFSの最短距離計算を支える。
data/lecture/information/algorithm/search/dfs-and-bfs-basics.lecture.n.md
6まとめ
- スタックはLIFO、キューはFIFOの操作契約をもつADTである。
- 空構造への除去操作と参照操作の挙動は、契約で明示する。
- 操作の計算量は具体的な実装に依存する。
- DFSはスタック、BFSはキューによって候補を管理する。