データ構造 - 優先度付きキュー

優先度付きキュー(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;
}

順序付き連結リストによる実装

実装例

#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;
}

ヒープ(Heap)現在では、優先度付きキューを実装する上で最も一般的でバランスの取れた方式です。挿入と最高優先度要素の取り出しは、どちらもO(log n)時間で完了します。この部分の内容は第15章のヒープで詳しく説明します。


応用シーン

シーン説明
タスクスケジューリングOSはタスクの優先度に基づいてCPUの実行順序を決定します
Dijkstra最短経路毎回距離が最小の未訪問ノードを選択するため、優先度付きキューに依存します
ハフマン符号化頻度が最も低い2つのノードを繰り返し取り出して結合し、最適な符号化ツリーを構築します
その他の拡張