データ構造 - 優先度付きキュー
優先度付きキュー(Priority Queue)はキューを拡張した形式の一つであり、通常のキューの「先入れ先出し」とは異なり、優先度付きキュー内の各要素には優先度が割り当てられ、デキュー操作は常に現在のキュー内で最も優先度の高い要素を取り出します。
優先度付きキューの概念と特徴
これは病院の救急外来のトリアージ制度に例えることができます。症状が緊急な患者ほど、後から到着しても優先的に処理されます。
優先度付きキューの主要な操作は通常のキューとは異なります:
| 操作 | 通常のキュー | 優先度付きキュー |
|---|---|---|
| エンキュー (enqueue) | キューの末尾に要素を追加する | 要素を挿入し、優先度を付与する |
| デキュー (dequeue) | キューの先頭要素を返す(最も早くエンキューされた要素) | 優先度が最も高い要素を返す |
3つの実装方法の比較
優先度付きキュー — 3つの実装方法の効率比較
順序なし配列
挿入O(1)
デキューO(n)
空間データのみ
適する場面挿入が頻繁な場合
順序付き連結リスト
挿入O(n)
デキューO(1)
空間データ+ポインタ
適する場面デキューが頻繁な場合
ヒープ (Heap) ★推奨
挿入O(log n)
デキューO(log n)
空間配列でコンパクト
適する場面挿入とデキューの両方が頻繁な場合
ヒープ実装が最適:挿入とデキューの効率を両立でき、Dijkstra最短経路、ハフマン符号化などのアルゴリズムが依存する中核的な要素です。
上の図では3つの実装方法の時間計算量の違いを比較しており、ヒープ実装が最もバランスの取れた選択です。
順序なし配列による実装
実装例
#include <stdio.h>
#define MAX 100
/* 順序なし配列に基づく優先度付きキュー(最大優先度付きキュー:値が大きいほど優先度が高い) */
struct PriorityQueue {
int items[MAX];
int size; /* 現在の要素数 */
};
void initPQ(struct PriorityQueue* pq) { pq->size = 0; }
/* エンキュー:末尾に直接追加,O(1) */
void enqueue(struct PriorityQueue* pq, int value) {
if (pq->size >= MAX) return;
pq->items[pq->size++] = value;
}
/* デキュー:走査して最大値を検索、O(n)
見つかったら最後の要素を削除位置に移動し、size を 1 減らす */
int dequeue(struct PriorityQueue* pq) {
if (pq->size == 0) return -1;
/* 最大値のインデックスを検索する */
int maxIdx = 0;
for (int i = 1; i < pq->size; i++) {
if (pq->items[i] > pq->items[maxIdx]) {
maxIdx = i;
}
}
int maxVal = pq->items[maxIdx];
/* 最後の要素を削除位置に移動する(大量の移動を避けるため) */
pq->items[maxIdx] = pq->items[--pq->size];
return maxVal;
}
int main() {
struct PriorityQueue pq;
initPQ(&pq);
enqueue(&pq, 5);
enqueue(&pq, 9);
enqueue(&pq, 3);
enqueue(&pq, 7);
printf("デキュー(優先度最高): %d\n", dequeue(&pq)); /* 出力: 9 */
printf("デキュー(優先度最高): %d\n", dequeue(&pq)); /* 出力: 7 */
printf("デキュー(優先度最高): %d\n", dequeue(&pq)); /* 出力: 5 */
printf("デキュー: %d\n", dequeue(&pq)); /* 出力: 3 */
return 0;
}
#define MAX 100
/* 順序なし配列に基づく優先度付きキュー(最大優先度付きキュー:値が大きいほど優先度が高い) */
struct PriorityQueue {
int items[MAX];
int size; /* 現在の要素数 */
};
void initPQ(struct PriorityQueue* pq) { pq->size = 0; }
/* エンキュー:末尾に直接追加,O(1) */
void enqueue(struct PriorityQueue* pq, int value) {
if (pq->size >= MAX) return;
pq->items[pq->size++] = value;
}
/* デキュー:走査して最大値を検索、O(n)
見つかったら最後の要素を削除位置に移動し、size を 1 減らす */
int dequeue(struct PriorityQueue* pq) {
if (pq->size == 0) return -1;
/* 最大値のインデックスを検索する */
int maxIdx = 0;
for (int i = 1; i < pq->size; i++) {
if (pq->items[i] > pq->items[maxIdx]) {
maxIdx = i;
}
}
int maxVal = pq->items[maxIdx];
/* 最後の要素を削除位置に移動する(大量の移動を避けるため) */
pq->items[maxIdx] = pq->items[--pq->size];
return maxVal;
}
int main() {
struct PriorityQueue pq;
initPQ(&pq);
enqueue(&pq, 5);
enqueue(&pq, 9);
enqueue(&pq, 3);
enqueue(&pq, 7);
printf("デキュー(優先度最高): %d\n", dequeue(&pq)); /* 出力: 9 */
printf("デキュー(優先度最高): %d\n", dequeue(&pq)); /* 出力: 7 */
printf("デキュー(優先度最高): %d\n", dequeue(&pq)); /* 出力: 5 */
printf("デキュー: %d\n", dequeue(&pq)); /* 出力: 3 */
return 0;
}
順序付き連結リストによる実装
実装例
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
int priority; /* 優先度(値が大きいほど優先度が高い) */
struct Node* next;
};
/* エンキュー:優先度の降順で挿入、O(n) */
struct Node* enqueue(struct Node* head, int data, int priority) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->priority = priority;
newNode->next = NULL;
/* 空の連結リスト、または新しいノードの優先度が最高 → 先頭に挿入 */
if (head == NULL || head->priority < priority) {
newNode->next = head;
return newNode;
}
/* 走査して適切な位置を見つける(降順を維持) */
struct Node* cur = head;
while (cur->next != NULL && cur->next->priority >= priority) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
return head;
}
/* デキュー:先頭ノードを削除(優先度が最高)、O(1) */
struct Node* dequeue(struct Node* head) {
if (head == NULL) return NULL;
struct Node* temp = head;
printf("デキュー: data=%d, priority=%d\n", temp->data, temp->priority);
head = head->next;
free(temp);
return head;
}
int main() {
struct Node* pq = NULL;
pq = enqueue(pq, 10, 1); /* データ 10、優先度 1 */
pq = enqueue(pq, 20, 5); /* データ 20、優先度 5 */
pq = enqueue(pq, 30, 3); /* データ 30、優先度 3 */
pq = dequeue(pq); /* デキュー: data=20, priority=5 (最高優先度) */
pq = dequeue(pq); /* デキュー: data=30, priority=3 */
pq = dequeue(pq); /* デキュー: data=10, priority=1 */
return 0;
}
#include <stdlib.h>
struct Node {
int data;
int priority; /* 優先度(値が大きいほど優先度が高い) */
struct Node* next;
};
/* エンキュー:優先度の降順で挿入、O(n) */
struct Node* enqueue(struct Node* head, int data, int priority) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->priority = priority;
newNode->next = NULL;
/* 空の連結リスト、または新しいノードの優先度が最高 → 先頭に挿入 */
if (head == NULL || head->priority < priority) {
newNode->next = head;
return newNode;
}
/* 走査して適切な位置を見つける(降順を維持) */
struct Node* cur = head;
while (cur->next != NULL && cur->next->priority >= priority) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
return head;
}
/* デキュー:先頭ノードを削除(優先度が最高)、O(1) */
struct Node* dequeue(struct Node* head) {
if (head == NULL) return NULL;
struct Node* temp = head;
printf("デキュー: data=%d, priority=%d\n", temp->data, temp->priority);
head = head->next;
free(temp);
return head;
}
int main() {
struct Node* pq = NULL;
pq = enqueue(pq, 10, 1); /* データ 10、優先度 1 */
pq = enqueue(pq, 20, 5); /* データ 20、優先度 5 */
pq = enqueue(pq, 30, 3); /* データ 30、優先度 3 */
pq = dequeue(pq); /* デキュー: data=20, priority=5 (最高優先度) */
pq = dequeue(pq); /* デキュー: data=30, priority=3 */
pq = dequeue(pq); /* デキュー: data=10, priority=1 */
return 0;
}
ヒープ(Heap)現在では、優先度付きキューを実装する上で最も一般的でバランスの取れた方式です。挿入と最高優先度要素の取り出しは、どちらもO(log n)時間で完了します。この部分の内容は第15章のヒープで詳しく説明します。
応用シーン
| シーン | 説明 |
|---|---|
| タスクスケジューリング | OSはタスクの優先度に基づいてCPUの実行順序を決定します |
| Dijkstra最短経路 | 毎回距離が最小の未訪問ノードを選択するため、優先度付きキューに依存します |
| ハフマン符号化 | 頻度が最も低い2つのノードを繰り返し取り出して結合し、最適な符号化ツリーを構築します |