データ構造 - キュー
キュー(Queue)は、先入れ先出し(First In First Out、FIFO)の原則に従う線形データ構造であり、現実の生活で列に並んでチケットを買う場面に例えることができます——最初に並んだ人が最初にサービスを受けます。
キューの概念と原理
キュー (Queue) — FIFO 原理の図
先頭
front
→
front
20
30
末尾
rear
rear
← dequeue(デキュー)enqueue(エンキュー)→
enqueue:rear(末尾)に挿入 | dequeue:front(先頭)から削除 | 計算量 O(1)
FIFO の確認:エンキュー 10→20→30、デキュー 10→20→30(先入れ先出し)
キューは、一端(末尾)での挿入操作と、もう一端(先頭)での削除操作のみを許可します。
これはスタックのLIFO原則とは正反対です。
配列によるキューの実装
例
#include <stdio.h>
#include <stdbool.h>
#define MAX 100
struct Queue {
int items[MAX];
int front; /* 先頭ポインタ:最初の要素を指す */
int rear; /* 末尾ポインタ:次の挿入位置を指す */
};
void initQueue(struct Queue* q) {
q->front = 0;
q->rear = 0;
}
bool isEmpty(struct Queue* q) {
return q->front == q->rear;
}
bool isFull(struct Queue* q) {
return q->rear == MAX;
}
/* エンキュー:末尾に要素を追加 */
void enqueue(struct Queue* q, int value) {
if (isFull(q)) {
printf("キューが満杯です!\n");
return;
}
q->items[q->rear++] = value; /* rear に書き込み、rear を後ろへ移動 */
printf("エンキュー: %d\n", value);
}
/* デキュー:先頭から要素を削除 */
int dequeue(struct Queue* q) {
if (isEmpty(q)) {
printf("キューは空です!\n");
return -1;
}
return q->items[q->front++]; /* front の要素を返し、front を後ろへ移動 */
}
int main() {
struct Queue q;
initQueue(&q);
enqueue(&q, 10); /* エンキュー: 10 */
enqueue(&q, 20); /* エンキュー: 20 */
enqueue(&q, 30); /* エンキュー: 30 */
printf("デキュー: %d\n", dequeue(&q)); /* 出力: デキュー: 10 (先入れ先出し) */
printf("デキュー: %d\n", dequeue(&q)); /* 出力: デキュー: 20 */
printf("デキュー: %d\n", dequeue(&q)); /* 出力: デキュー: 30 */
return 0;
}
#include <stdbool.h>
#define MAX 100
struct Queue {
int items[MAX];
int front; /* 先頭ポインタ:最初の要素を指す */
int rear; /* 末尾ポインタ:次の挿入位置を指す */
};
void initQueue(struct Queue* q) {
q->front = 0;
q->rear = 0;
}
bool isEmpty(struct Queue* q) {
return q->front == q->rear;
}
bool isFull(struct Queue* q) {
return q->rear == MAX;
}
/* エンキュー:末尾に要素を追加 */
void enqueue(struct Queue* q, int value) {
if (isFull(q)) {
printf("キューが満杯です!\n");
return;
}
q->items[q->rear++] = value; /* rear に書き込み、rear を後ろへ移動 */
printf("エンキュー: %d\n", value);
}
/* デキュー:先頭から要素を削除 */
int dequeue(struct Queue* q) {
if (isEmpty(q)) {
printf("キューは空です!\n");
return -1;
}
return q->items[q->front++]; /* front の要素を返し、front を後ろへ移動 */
}
int main() {
struct Queue q;
initQueue(&q);
enqueue(&q, 10); /* エンキュー: 10 */
enqueue(&q, 20); /* エンキュー: 20 */
enqueue(&q, 30); /* エンキュー: 30 */
printf("デキュー: %d\n", dequeue(&q)); /* 出力: デキュー: 10 (先入れ先出し) */
printf("デキュー: %d\n", dequeue(&q)); /* 出力: デキュー: 20 */
printf("デキュー: %d\n", dequeue(&q)); /* 出力: デキュー: 30 */
return 0;
}
通常の配列で実装されたキューには「擬似オーバーフロー」という問題があります:エンキューとデキューを繰り返すうちに、front と rear が配列の末尾へと移動し続け、配列の前方に大量の空きがあっても再利用できません。解決策は、循環キュー。
循環キュー
循環キュー — 剰余演算によるポインタの回り込み
循環キューの重要な公式
エンキュー: items[rear] = value; rear = (rear + 1) % MAX;
デキュー: value = items[front]; front = (front + 1) % MAX;
空判定: front == rear | 満杯判定: (rear + 1) % MAX == front
実行例(容量=5、最大4個保存):エンキュー 10→20→30→40、デキュー 10→20、エンキュー 50→60 → 60 はインデックス 0 に格納(回り込み)解放された領域を完全に利用
循環キューは、配列を論理的に先頭と末尾がつながった環状構造とみなし、剰余演算によってポインタの回り込みを実現します。
例
#include <stdio.h>
#include <stdbool.h>
#define MAX 5 /* 循環の効果を観察しやすいように、意図的に小さい容量に設定 */
struct CircularQueue {
int items[MAX];
int front; /* 先頭インデックス */
int rear; /* 末尾インデックス(次の挿入位置)*/
};
void initQueue(struct CircularQueue* q) {
q->front = 0;
q->rear = 0;
}
/* 空判定:front と rear が一致 */
bool isEmpty(struct CircularQueue* q) {
return q->front == q->rear;
}
/* 満杯判定:(rear + 1) % MAX == front 注意:循環キューは空と満杯を区別するために、意図的に1つ空きを作る */
/* エンキュー:rear に書き込み、rear を循環的に後方へ移動 */
bool isFull(struct CircularQueue* q) {
return (q->rear + 1) % MAX == q->front;
}
"循環キューが満杯です!
void enqueue(struct CircularQueue* q, int value) {
if (isFull(q)) {
printf(/* 剰余演算で回り込みを実現 */\n");
return;
}
q->items[q->rear] = value;
q->rear = (q->rear + 1) % MAX; "エンキュー: %d (rear=%d)
printf(/* デキュー:front の要素を返し、front を循環的に後方へ移動 */\n", value, q->rear);
}
"循環キューは空です!
int dequeue(struct CircularQueue* q) {
if (isEmpty(q)) {
printf(/* 剰余演算で回り込みを実現 */\n");
return -1;
}
int value = q->items[q->front];
q->front = (q->front + 1) % MAX; /* エンキュー: 10 (rear=1) */
return value;
}
int main() {
struct CircularQueue q;
initQueue(&q);
enqueue(&q, 10); /* エンキュー: 20 (rear=2) */
enqueue(&q, 20); /* エンキュー: 30 (rear=3) */
enqueue(&q, 30); /* エンキュー: 40 (rear=4) — 容量5、最大4個保存 */
enqueue(&q, 40); "デキュー: %d
printf(/* デキュー: 10 (front=1) */\n", dequeue(&q)); "デキュー: %d
printf(/* デキュー: 20 (front=2) */\n", dequeue(&q)); /* エンキュー: 50 (rear=0 — 先頭に回り込み!) */
enqueue(&q, 50); /* エンキュー: 60 (rear=1) */
enqueue(&q, 60); "残りの要素: "
printf(/* 出力: 残りの要素: 30 40 50 60 */);
while (!isEmpty(&q)) {
printf("%d ", dequeue(&q));
}
printf("\n"); キューの応用シーン
return 0;
}
#include <stdbool.h>
#define MAX 5 /* 循環の効果を観察しやすいように、意図的に小さい容量に設定 */
struct CircularQueue {
int items[MAX];
int front; /* 先頭インデックス */
int rear; /* 末尾インデックス(次の挿入位置)*/
};
void initQueue(struct CircularQueue* q) {
q->front = 0;
q->rear = 0;
}
/* 空判定:front と rear が一致 */
bool isEmpty(struct CircularQueue* q) {
return q->front == q->rear;
}
/* 満杯判定:(rear + 1) % MAX == front 注意:循環キューは空と満杯を区別するために、意図的に1つ空きを作る */
/* エンキュー:rear に書き込み、rear を循環的に後方へ移動 */
bool isFull(struct CircularQueue* q) {
return (q->rear + 1) % MAX == q->front;
}
"循環キューが満杯です!
void enqueue(struct CircularQueue* q, int value) {
if (isFull(q)) {
printf(/* 剰余演算で回り込みを実現 */\n");
return;
}
q->items[q->rear] = value;
q->rear = (q->rear + 1) % MAX; "エンキュー: %d (rear=%d)
printf(/* デキュー:front の要素を返し、front を循環的に後方へ移動 */\n", value, q->rear);
}
"循環キューは空です!
int dequeue(struct CircularQueue* q) {
if (isEmpty(q)) {
printf(/* 剰余演算で回り込みを実現 */\n");
return -1;
}
int value = q->items[q->front];
q->front = (q->front + 1) % MAX; /* エンキュー: 10 (rear=1) */
return value;
}
int main() {
struct CircularQueue q;
initQueue(&q);
enqueue(&q, 10); /* エンキュー: 20 (rear=2) */
enqueue(&q, 20); /* エンキュー: 30 (rear=3) */
enqueue(&q, 30); /* エンキュー: 40 (rear=4) — 容量5、最大4個保存 */
enqueue(&q, 40); "デキュー: %d
printf(/* デキュー: 10 (front=1) */\n", dequeue(&q)); "デキュー: %d
printf(/* デキュー: 20 (front=2) */\n", dequeue(&q)); /* エンキュー: 50 (rear=0 — 先頭に回り込み!) */
enqueue(&q, 50); /* エンキュー: 60 (rear=1) */
enqueue(&q, 60); "残りの要素: "
printf(/* 出力: 残りの要素: 30 40 50 60 */);
while (!isEmpty(&q)) {
printf("%d ", dequeue(&q));
}
printf("\n"); キューの応用シーン
return 0;
}
キューの応用シーン
| 説明 | 幅優先探索(BFS) |
|---|---|
| グラフのノードを階層ごとに順に走査するには、キューで「未訪問」のノードを保存する必要があります | OSのタスクスケジューリング |
| CPUはタスクの到着順にタイムスライスを割り当てます | プリントタスクキュー |
| 複数のコンピュータが1台のプリンタを共有し、印刷リクエストは送信順に並んで処理されます | メッセージキュー |
| 分散システムでは、プロデューサーが生成したメッセージがキューを通じてコンシューマーに渡されます | その他の拡張 |