キュー
最終更新
キューには使われる端が2つあります。新しい値は末尾に加わり、値は先頭から出ていくので、いちばん長く待った値が最初に処理されます。これが先入れ先出し(FIFO)であり、窓口に並ぶ行列とまったく同じふるまいです。最後尾に並び、先頭から順に呼ばれるからこそ、待ち時間が公平になります。上の再生ボタンを押して、値が片側から入り、反対側から出ていく様子を見てください。
それぞれの端が専用の添字またはポインタで管理されているため、どちらの操作も O(1) で、残りのデータをずらすこともありません。だからこそキューは、到着順に仕事を処理するあらゆるものを支えています。印刷ジョブ、タスクキューやメッセージキュー、リクエストのバッファ、そして 幅優先探索 などです。幅優先探索がグラフを1段ずつ訪れるのは、まさに探索の最前線をキューで保持しているからです。取り除く端を末尾に移せば、代わりに スタック になります。
時間計算量と空間計算量
リングバッファまたは連結リストで実装したキュー、つまり標準的な2つの実装について:
| 操作 | 計算量 | 備考 |
|---|---|---|
| エンキュー(enqueue) | O(1) | 末尾に書き込み、末尾の添字を1つ進める。 |
| デキュー(dequeue) | O(1) | 先頭を読み、先頭の添字を1つ進める。要素をずらす必要はない。 |
| ピーク(front) | O(1) | 取り除かずに先頭の値を読む。 |
| 検索 | O(n) | キューの用途ではない。中を見るには取り出しきる必要がある。 |
| 空間 | O(n) | 待っている値1つにつき1スロット。 |
ステップごとの手順
| ステップ | 何が起きるか |
|---|---|
| 1 | キューは空の状態から始まり、先頭と末尾は同じスロットを指している。 |
| 2 | エンキューは末尾に値を書き込み、それから末尾を1つ進める。 |
| 3 | さらにエンキューすると、すでに待っている値の後ろに並ぶ。 |
| 4 | デキューは先頭にある値を読み、それから先頭を1つ進める。 |
| 5 | 返ってくるのはつねに、いちばん長く待っていた値である。 |
| 6 | 先頭が末尾に追いつくとキューはふたたび空になり、そこからさらにデキューするとエラーになる。 |
具体例
3、7、5 をエンキューし、そのあとキューを空になるまで取り出すと:
| 操作 | キュー(先頭から末尾へ) | 返り値 |
|---|---|---|
enqueue(3) | [3] | なし |
enqueue(7) | [3, 7] | なし |
enqueue(5) | [3, 7, 5] | なし |
dequeue() | [7, 5] | 3、いちばん古い値 |
dequeue() | [5] | 7 |
dequeue() | [] | 5、いちばん新しい値が最後 |
キューを使うべきとき
Queueのコード
Python, JavaScript, Java, C++, Cによるクリーンで実行可能なQueueの実装です。言語を選んでコードをコピーするか、Coddyプレイグラウンドに読み込んだ状態で開けます。
PythonでのQueueのコード
1from collections import deque2
3queue = deque()4
5# Enqueue three values at the rear6for value in [3, 7, 5]:7 queue.append(value)8 print(f"enqueue {value} -> {list(queue)}")9
10# Dequeue them from the front: first in, first out11while queue:12 value = queue.popleft()13 print(f"dequeue {value} -> {list(queue)}")14
15print("empty:", len(queue) == 0)JavaScriptでのQueueのコード
1// A plain array makes dequeue O(n): shift() moves every element left.2// Track a head index instead, the fix the queue article describes.3const queue = { items: [], head: 0 };4
5function enqueue(value) {6 queue.items.push(value);7}8
9function dequeue() {10 const value = queue.items[queue.head];11 queue.items[queue.head] = undefined; // free the slot12 queue.head += 1;13 // Reclaim space once the consumed prefix dominates.14 if (queue.head * 2 >= queue.items.length) {15 queue.items = queue.items.slice(queue.head);16 queue.head = 0;17 }18 return value;19}20
21const size = () => queue.items.length - queue.head;22
23for (const value of [3, 7, 5]) {24 enqueue(value);25 console.log(`enqueue ${value} -> size ${size()}`);26}27
28// Dequeue from the front: first in, first out, amortized O(1)29while (size() > 0) {30 console.log(`dequeue ${dequeue()} -> size ${size()}`);31}32
33console.log('empty:', size() === 0);JavaでのQueueのコード
1import java.util.ArrayDeque;2import java.util.Queue;3
4public class Main {5 public static void main(String[] args) {6 Queue<Integer> queue = new ArrayDeque<>();7
8 // Enqueue three values at the rear9 for (int value : new int[] {3, 7, 5}) {10 queue.add(value);11 System.out.println("enqueue " + value + " -> " + queue);12 }13
14 // Dequeue them from the front: first in, first out15 while (!queue.isEmpty()) {16 int value = queue.remove();17 System.out.println("dequeue " + value + " -> " + queue);18 }19
20 System.out.println("empty: " + queue.isEmpty());21 }22}C++でのQueueのコード
1#include <iostream>2#include <queue>3
4int main() {5 std::queue<int> queue;6
7 // Enqueue three values at the rear8 for (int value : {3, 7, 5}) {9 queue.push(value);10 std::cout << "enqueue " << value << " -> size " << queue.size() << "\n";11 }12
13 // Dequeue them from the front: first in, first out14 while (!queue.empty()) {15 int value = queue.front();16 queue.pop();17 std::cout << "dequeue " << value << " -> size " << queue.size() << "\n";18 }19
20 std::cout << "empty: " << std::boolalpha << queue.empty() << "\n";21 return 0;22}CでのQueueのコード
1#include <stdio.h>2
3#define CAP 164
5int queue[CAP];6int front = 0;7int rear = 0; /* index of the next free slot */8
9int main(void) {10 int values[3] = {3, 7, 5};11
12 /* Enqueue three values at the rear */13 for (int i = 0; i < 3; i++) {14 queue[rear++] = values[i];15 printf("enqueue %d -> size %d\n", values[i], rear - front);16 }17
18 /* Dequeue them from the front: first in, first out */19 while (front < rear) {20 int value = queue[front++];21 printf("dequeue %d -> size %d\n", value, rear - front);22 }23
24 printf("empty: %d\n", front == rear);25 return 0;26}キューに関するよくある質問
FIFO とはどういう意味ですか?
キューとスタックの違いは何ですか?
O(1) で追加します。キューは先頭から取り除き(FIFO)、スタックは追加したのと同じ端から取り除きます(LIFO)。計算量の表は、それ以外まったく同じです。キューの主な操作は何ですか?
enqueue は末尾に値を追加し、dequeue は先頭の値を取り除いて返し、peek(front とも呼ばれます)は取り除かずに先頭を読み、is_empty は待っているものが残っているかどうかを教えます。この4つはすべて O(1) です。素の配列を使うと、なぜデキューが遅くなるのですか?
O(n) になるからです。実際の実装はこれを避けるために、先頭の添字を進めるリングバッファか、先頭ポインタを持つ連結リストを使います。Python の collections.deque と Java の ArrayDeque はこれを代わりにやってくれますが、list.pop(0) はやってくれません。循環キューとは何ですか?
n のキューは配列の末端からはみ出すことなく、いつまでも動き続けます。