【C++】最大ヒープからK番目に大きい要素を優先度付きキューで効率よく求める方法
このチュートリアルでは、最大ヒープ(max-heap)から K番目に大きい要素 を取り出すプログラムをC++で実装します。
この問題は、優先度付きキュー(priority queue)を使うことで、ヒープ全体をソートすることなく効率的に解くことができます。アルゴリズムの流れは以下の通りです。
アルゴリズムの手順
- 最大ヒープを正しい値で初期化します。
- 優先度付きキューを作成し、ヒープのルートノードを挿入します。
- ループを
k - 1回繰り返します。- キューから最大の要素を取り出します(pop)。
- 取り出したノードの左の子と右の子を優先度付きキューに追加します。
- ループ終了後、優先度付きキューの先頭にある要素が K番目に大きい要素となります。
- その値を返します。
なぜこの方法が有効か
最大ヒープでは親ノードが必ず子ノード以上の値を持つため、「次に大きい候補」は常に「これまでに取り出したノードの子」の中にしか存在しません。そのため、配列全体をソートする O(n log n) のアプローチと比べ、必要な部分だけを探索するこの手法は O(k log k) で済み、k が小さい場合に非常に効率的です。
実装例
それでは、実際のコードを見てみましょう。
#include <bits/stdc++.h>
using namespace std;
struct Heap {
vector<int> elements;
int n;
Heap(int i = 0) : n(i) {
elements = vector<int>(n);
}
};
inline int leftIndex(int i) {
return 2 * i + 1;
}
inline int rightIndex(int i) {
return 2 * i + 2;
}
int findKthGreatestElement(Heap &heap, int k) {
// ペア(値, インデックス)を格納する優先度付きキュー
priority_queue<pair<int, int>> queue;
queue.push(make_pair(heap.elements[0], 0));
for (int i = 0; i < k - 1; ++i) {
int node = queue.top().second;
queue.pop();
int left = leftIndex(node), right = rightIndex(node);
if (left < heap.n) {
queue.push(make_pair(heap.elements[left], left));
}
if (right < heap.n) {
queue.push(make_pair(heap.elements[right], right));
}
}
return queue.top().first;
}
int main() {
Heap heap(10);
heap.elements = vector<int>{ 44, 42, 35, 33, 31, 19, 27, 10, 26, 14 };
cout << findKthGreatestElement(heap, 4) << endl;
return 0;
}コードのポイント
- 優先度付きキューには
pair<int, int>を格納し、値とインデックスをセットで管理しています。こうすることで、pop したノードから子ノードの位置を特定できます。 - 子のインデックスがヒープのサイズ
nを超えないよう、範囲チェックを行ってから挿入しています。
実行結果
上記のコードを実行すると、次の出力が得られます。
33
サンプルのヒープ {44, 42, 35, 33, ...} の中で、4番目に大きい要素は 33 であるため、正しく動作していることが確認できます。
まとめ
本記事では、優先度付きキューを活用して最大ヒープから K番目に大きい要素を求める方法を解説しました。全要素をソートせずに済むため、k が小さいケースでは特に高速に動作します。チュートリアルについて質問がある場合は、コメント欄でお気軽にお尋ねください。
-
C++で最大ヒープから最小値の要素を見つける方法
問題の概要最大ヒープ(max heap)の中から、最も小さい値を持つ要素を探す方法を解説します。以下のような最大ヒープを例に考えてみましょう。最大ヒープでは、親ノードの値は必ずその子ノードの値以上になります。この性質により、最小値は必ず葉ノード(leaf node)のいずれかに存在すると結論できます。ヒープが n 個のノードを含む場合、葉ノードの数は ceil(n/2) 個になります。また、最大ヒープは完全二分木であるため、配列として表現することができます。このとき、最初の葉ノードは floor(n/2) のインデックス以降に配置されます。上記の例では、最初の葉ノードはインデックス 5 に存在
-
C++でヒープソートを実装する方法:最小ヒープの完全ガイド
ヒープとはヒープ(Heap)とは、完全二分木のデータ構造であり、「最小ヒープ(Min Heap)」または「最大ヒープ(Max Heap)」のいずれかに分類されます。最大ヒープでは、ルートノードのキーがヒープ内のすべてのキーの中で最大値である必要があり、この性質は木の中のすべてのノードに対して再帰的に成り立たなければなりません。最小ヒープはその逆で、親ノードの値が常に子ノードの値以下になるという性質を持ちます。本記事では、最小ヒープを用いたヒープソートの実装方法を解説します。実装する関数の概要今回実装するクラスには、以下の主要なメンバ関数が含まれています。void BHeap::Insert(i