Re-translate the Japanese version (#1871)

* Retranslate Japanese docs with GPT-5.4

* Retranslate Japanese code with GPT-5.4
This commit is contained in:
Yudong Jin
2026-03-30 07:30:15 +08:00
committed by GitHub
parent fe6443235b
commit d7b2277d2b
1444 changed files with 83312 additions and 8363 deletions
+25 -25
View File
@@ -1,67 +1,67 @@
# ヒープ構築操作
# ヒープ構築
場合によっては、リストのすべての要素を使用してヒープを構築したいことがあり、このプロセスは「ヒープ構築操作」として知られています。
場合によっては、リストのすべての要素を使てヒープを構築したいことがあります。この過程を「ヒープ構築」と呼びます。
## ヒープ挿入操作による実
## ヒープへの挿入操作による実
まず空のヒープを作成し、次にリストを反復処理して、各要素に対して順に「ヒープ挿入操作」を実行します。これは、要素をヒープの末尾に追加し、次に下から上に「ヒープ化」することを意味します。
まず空のヒープを作成し、次にリストを走査して、各要素に対して順に「ヒープへの挿入操作」を実行します。つまり、要素をヒープの末尾に追加してから、その要素に対して「下から上へ」のヒープ化を行います。
ヒープに要素が追加されるたびに、ヒープの長さは1つずつ増加します。ノードは二分木に上から下に追加されるため、ヒープは「上から下」構築されます。
要素が1つヒープに挿入されるたびに、ヒープの長さは1増加します。ノードは上から下へ順に二分木へ追加されるため、ヒープは「上から下」構築されます。
要素数を$n$とすると、各要素の挿入操作$O(\log{n})$時間かかるため、このヒープ構築法の時間計算量は$O(n \log n)$です。
要素数を $n$ とすると、各要素のヒープへの挿入操作には $O(\log{n})$時間かかるため、このヒープ構築法の時間計算量は $O(n \log n)$ です。
## 走査によるヒープ化の実装
## 走査によるヒープ化で実現
実際には、2つのステップでより効率的なヒープ構築法を実できます。
実際には、より効率的なヒープ構築法を実でき、全体は2つの手順に分かれます。
1. リストのすべての要素をそのままヒープに追加します。この時点では、ヒープの性質はまだ満たされていません。
2. ヒープを逆順(レベル順走査の逆)で走査し、各非葉ノードに対して「上から下のヒープ化を実行します。
2. ヒープを逆順で走査し(レベル順走査の逆)、各非葉ノードに対して順に「上から下へ」のヒープ化を実行します。
**ノードをヒープ化した後、そのノードを根とする部分木は有効な部分ヒープになります**走査が逆順であるため、ヒープは「下から上」構築されます。
**あるノードをヒープ化するたびに、そのノードを根とする部分木は合法な部分ヒープになります**また、逆順で走査するため、ヒープは「下から上」構築されます。
逆走査を選択する理由は、現在のノードの下部分木がすでに有効な部分ヒープであることを保証し、現在のノードヒープ化を効果的にするためです。
走査を選ぶのは、この方法なら現在のノードの下にある部分木がすでに合法な部分ヒープであることを保証でき、そのうえで現在のノードヒープ化してはじめて有効になるからです。
言及する価値があるのは、**葉ノードは子を持たないため、自然に有効な部分ヒープを形成し、ヒープ化する必要がない**ということです。以下のコード示すように、最後の非葉ノードは最後のノードの親です。そこから開始して逆順に走査してヒープ化を実行します
なお、**葉ノードは子ノードがないため、それ自体が自然に合法な部分ヒープであり、ヒープ化は不要です**。以下のコード示すように、最後の非葉ノードは最後のノードの親ノードであり、そこから逆順に走査してヒープ化を実行します
```src
[file]{my_heap}-[class]{max_heap}-[func]{__init__}
```
## 計算量分析
## 計算量分析
次に、この第2のヒープ構築法の時間計算量を計算してみましょう。
以下では、2つ目のヒープ構築法の時間計算量を求めてみましょう。
-二分木のノード数を$n$と仮定すると、葉ノード数は$(n + 1) / 2$です。ここで$/$ は整数除算です。したがって、ヒープ化が必要なノード数は$(n - 1) / 2$です。
- 上から下のヒープ化」のプロセスでは、各ノードは最大で葉ノードまでヒープ化されるため、最大反復回数は二分木の高さ$\log n$です。
-二分木のノード数を $n$ とすると、葉ノード数は $(n + 1) / 2$ です。ここで $/$ は切り捨て除算を表します。したがって、ヒープ化が必要なノード数は $(n - 1) / 2$ です。
- 上から下のヒープ化の過程では、各ノードは最大で葉ノードまでヒープ化されるため、最大反復回数は二分木の高さ $\log n$ です。
の2つを掛け合わせると、ヒープ構築プロセスの時間計算量は$O(n \log n)$となります。**しかし、この推定は正確ではありません。二分木の下位レベルには上位よりはるかに多くのノードがあるという性質を考慮していないからです**
の2つを掛け合わせると、ヒープ構築過程の時間計算量は $O(n \log n)$ となります。**しかし、この見積もりは正確ではありません。二分木では下層のノード数が上層よりはるかに多という性質を考慮していないためです**
より正確な計算を行いましょう。計算を簡素化するため、$n$個のノードと高さ$h$を持つ「完全二分木」を仮定します。この仮定は結果の正確性に影響しません。
次に、より正確な計算を行いま。計算を簡単にするため、ノード数が $n$ 、高さが $h$ の「満二分木」を仮定します。この仮定は計算結果の正しさに影響しません。
![完全二分木の各レベルのノード数](build_heap.assets/heapify_operations_count.png)
![二分木の各のノード数](build_heap.assets/heapify_operations_count.png)
上図に示すように、ノード「上から下ヒープ化される」最大反復回数は、そのノードから葉ノードまでの距離等しく、これは正確に「ノードの高さ」です。したがって、各レベルで「ノード数×ノードの高さ」を合計して、**すべてのノードのヒープ化反復回数を得る**ことができます。
上図に示すように、ノード「上から下ヒープ化」する最大反復回数は、そのノードから葉ノードまでの距離等しく、この距離こそが「ノードの高さ」です。したがって、各層の「ノード数 $\times$ ノードの高さ」を合計すれば、**すべてのノードのヒープ化反復回数の総和**が得られます。
$$
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1
$$
記の方程式を簡素化するために、高校の数列の知識を使用する必要があります。まず$T(h)$$2$を掛けて以下を得ます
式を簡約するには中学の数列の知識を用います。まず $T(h)$$2$ を掛けると、次のようになります
$$
\begin{aligned}
T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline
2T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^h\times1 \newline
2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline
\end{aligned}
$$
変位法を使用して$2T(h)$から$T(h)$を減算すると、以下を得ます
ずらして引く方法を用い、下式の $2 T(h)$ から上式の $T(h)$ を引くと、次が得られます
$$
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h
$$
方程式を観察すると、$T(h)$は等比数列であり、和の公式を使用して直接計算でき、時間計算量は以下になります
上式を見ると、$T(h)$ は等比数列であることがわかるため、和の公式を直接用いて、時間計算量は次のように求められます
$$
\begin{aligned}
@@ -71,4 +71,4 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline
\end{aligned}
$$
さらに、高さ$h$の完全二分木は$n = 2^{h+1} - 1$個のノードを持つため、計算量は$O(2^h) = O(n)$です。この計算は、**リストを入力してヒープを構築する時間計算量が$O(n)$であり、非常に効率的である**ことを示しています。
さらに、高さ $h$ の満二分木のノード数は $n = 2^{h+1} - 1$ であるため、計算量は容易に $O(2^h) = O(n)$ とわかります。以上の導出は、**入力リストからヒープを構築する時間計算量が $O(n)$ であり、非常に効率的である**ことを示しています。
+141 -137
View File
@@ -1,64 +1,64 @@
# ヒープ
<u>ヒープ</u>は特定の条件を満たす完二分木で、主に次の2つのタイプに分類されます(下図参照)
<u>ヒープheap</u>は特定の条件を満たす完二分木であり、主に次の 2 種類に分けられます
- <u>最小ヒープ</u>:任意のノードの値 $\leq$ その子ノードの値。
- <u>最大ヒープ</u>:任意のノードの値 $\geq$ その子ノードの値。
- <u>最小ヒープmin heap</u>:任意のノードの値 $\leq$ その子ノードの値。
- <u>最大ヒープmax heap</u>:任意のノードの値 $\geq$ その子ノードの値。
![最小ヒープと最大ヒープ](heap.assets/min_heap_and_max_heap.png)
完備二分木の特別なケースとして、ヒープには以下の特性があります
ヒープは完全二分木の特殊な例であり、次の性質を持ちます
- 最下層のノードは左からに埋められ、の層のノードは完全に埋められています。
- 二分木の根ノードをヒープの「先頭」と呼び、最も右下のノードをヒープの「末尾」と呼びます。
- 最大ヒープ(最小ヒープ)の場合、先頭要素(根)の値はすべての要素の中で最大(最小)です。
- 最下層のノードは左からに埋められ、ほかの層のノードはすべて埋まっています。
- 二分木の根ノードをヒープ頂点」、最下層で最も右にあるノードをヒープ」と呼びます。
- 最大ヒープ(最小ヒープ)では、ヒープ頂点の要素(根ノード)の値が最大(最小)です。
## ヒープの一般的な操作
## ヒープの基本操作
多くのプログラミング言語が<u>優先度キュー</u>を提供していることに注意してください。これは優先度付きソートを持つキューとして定義される抽象データ構造です。
ここで注意したいのは、多くのプログラミング言語が提供しているのは<u>優先度付きキューpriority queue</u>であり、これは優先度順に並ぶキューとして定義される抽象データ構造だということです。
実際には、**ヒープは優先度キュー実装するためによく使用されます。最大ヒープは要素が降順でデキューされる優先度キューに対応します**。使用の観点から、「優先度キュー」と「ヒープ」を同等のデータ構造と考えることができます。したがって、この本では両者を特に区別せず、統一して「ヒープ」と呼びます。
実際には、**ヒープは通常、優先度付きキュー実装に用いられ、最大ヒープは要素が大きい順に取り出される優先度付きキューに相当します**。用の観点では、「優先度付きキュー」と「ヒープ」は等価なデータ構造とみなせます。そのため、本書では両者を特に区別せず、まとめて「ヒープ」と呼びます。
ヒープの一般的な操作を下表に示します。メソッド名はプログラミング言語によって異なる場合があります。
ヒープの基本操作を以下の表に示します。メソッド名はプログラミング言語によって異なります。
<p align="center"> 表 <id> &nbsp; ヒープ操作効率 </p>
<p align="center"> 表 <id> &nbsp; ヒープ操作効率 </p>
| メソッド名 | 説明 | 時間計算量 |
| ----------- | ------------------------------------------------- | ----------- |
| `push()` | ヒープに要素を追加 | $O(\log n)$ |
| `pop()` | ヒープから先頭要素を削除 | $O(\log n)$ |
| `peek()` | 先頭要素にアクセス(最大/最小ヒープの場合、最大/最小値) | $O(1)$ |
| `size()` | ヒープ内の要素数を取得 | $O(1)$ |
| `isEmpty()` | ヒープが空かどうかをチェック | $O(1)$ |
| メソッド名 | 説明 | 時間計算量 |
| ----------- | ------------------------------------------------ | ----------- |
| `push()` | 要素をヒープに追加 | $O(\log n)$ |
| `pop()` | ヒープ頂点の要素を取り出す | $O(\log n)$ |
| `peek()` | ヒープ頂点の要素にアクセス(最大 / 最小ヒープではそれぞれ最大 / 最小値) | $O(1)$ |
| `size()` | ヒープ内の要素数を取得 | $O(1)$ |
| `isEmpty()` | ヒープが空かどうかを判定 | $O(1)$ |
実際は、プログラミング言語によって提供されるヒープクラス(または優先度キュークラス)を直接使用できます。
実際の応用では、プログラミング言語が提供するヒープクラス(または優先度付きキュークラス)をそのまま使えます。
ソートアルゴリズム「昇順」と「降順」があるように、`flag`を設定するか`Comparator`を変更することで「最小ヒープ」と「最大ヒープ」を切り替えることができます。コードは以下のりです:
ソートアルゴリズムにおける「昇順」と「降順」と同様に、`flag` を設定したり `Comparator` を変更したりすることで「最小ヒープ」と「最大ヒープ」を切り替えられます。コードは以下のとおりです:
=== "Python"
```python title="heap.py"
# 最小ヒープ初期化
# 最小ヒープ初期化
min_heap, flag = [], 1
# 最大ヒープ初期化
# 最大ヒープ初期化
max_heap, flag = [], -1
# Pythonheapqモジュールはデフォルトで最小ヒープを実装
# 要素をヒープにプッシュする前に負の値にすることで、順序を反転させ最大ヒープを実
# この例では、flag = 1は最小ヒープに対応し、flag = -1は最大ヒープに対応
# Pythonheapq モジュールはデフォルトで最小ヒープを実装している
# 要素を負にして」からヒープに追加すると、大小関係を反転させ最大ヒープを実現できる
# この例では、flag = 1 のときは最小ヒープ、flag = -1 のときは最大ヒープに対応する
# ヒープに要素をプッシュ
# 要素をヒープに追加
heapq.heappush(max_heap, flag * 1)
heapq.heappush(max_heap, flag * 3)
heapq.heappush(max_heap, flag * 2)
heapq.heappush(max_heap, flag * 5)
heapq.heappush(max_heap, flag * 4)
# ヒープの先頭要素を取得
# ヒープ頂点の要素を取得
peek: int = flag * max_heap[0] # 5
# ヒープの先頭要素をポップ
# ポップされた要素は降順のシーケンスを形成
# ヒープ頂点の要素を取り出す
# 取り出された要素は大きい順の列になる
val = flag * heapq.heappop(max_heap) # 5
val = flag * heapq.heappop(max_heap) # 4
val = flag * heapq.heappop(max_heap) # 3
@@ -68,10 +68,10 @@
# ヒープのサイズを取得
size: int = len(max_heap)
# ヒープが空かどうかをチェック
# ヒープが空かどうかを判定
is_empty: bool = not max_heap
# リストからヒープを作成
# 入力リストからヒープを構築
min_heap: list[int] = [1, 3, 2, 5, 4]
heapq.heapify(min_heap)
```
@@ -79,24 +79,24 @@
=== "C++"
```cpp title="heap.cpp"
/* ヒープ初期化 */
// 最小ヒープ初期化
/* ヒープ初期化 */
// 最小ヒープ初期化
priority_queue<int, vector<int>, greater<int>> minHeap;
// 最大ヒープ初期化
// 最大ヒープ初期化
priority_queue<int, vector<int>, less<int>> maxHeap;
/* ヒープに要素をプッシュ */
/* 要素をヒープに追加 */
maxHeap.push(1);
maxHeap.push(3);
maxHeap.push(2);
maxHeap.push(5);
maxHeap.push(4);
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
int peek = maxHeap.top(); // 5
/* ヒープの先頭要素をポップ */
// ポップされた要素は降順のシーケンスを形成
/* ヒープ頂点の要素を取り出す */
// 取り出された要素は大きい順の列になる
maxHeap.pop(); // 5
maxHeap.pop(); // 4
maxHeap.pop(); // 3
@@ -106,10 +106,10 @@
/* ヒープのサイズを取得 */
int size = maxHeap.size();
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
bool isEmpty = maxHeap.empty();
/* リストからヒープを作成 */
/* 入力リストからヒープを構築 */
vector<int> input{1, 3, 2, 5, 4};
priority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());
```
@@ -117,24 +117,24 @@
=== "Java"
```java title="heap.java"
/* ヒープ初期化 */
// 最小ヒープ初期化
/* ヒープ初期化 */
// 最小ヒープ初期化
Queue<Integer> minHeap = new PriorityQueue<>();
// 最大ヒープ初期化(ラムダ式でComparatorを変更するだけ
// 最大ヒープ初期化(lambda 式で Comparator を変更すればよい
Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
/* ヒープに要素をプッシュ */
/* 要素をヒープに追加 */
maxHeap.offer(1);
maxHeap.offer(3);
maxHeap.offer(2);
maxHeap.offer(5);
maxHeap.offer(4);
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
int peek = maxHeap.peek(); // 5
/* ヒープの先頭要素をポップ */
// ポップされた要素は降順のシーケンスを形成
/* ヒープ頂点の要素を取り出す */
// 取り出された要素は大きい順の列になる
peek = maxHeap.poll(); // 5
peek = maxHeap.poll(); // 4
peek = maxHeap.poll(); // 3
@@ -144,34 +144,34 @@
/* ヒープのサイズを取得 */
int size = maxHeap.size();
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
boolean isEmpty = maxHeap.isEmpty();
/* リストからヒープを作成 */
/* 入力リストからヒープを構築 */
minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));
```
=== "C#"
```csharp title="heap.cs"
/* ヒープ初期化 */
// 最小ヒープ初期化
/* ヒープ初期化 */
// 最小ヒープ初期化
PriorityQueue<int, int> minHeap = new();
// 最大ヒープ初期化(ラムダ式でComparatorを変更するだけ
PriorityQueue<int, int> maxHeap = new(Comparer<int>.Create((x, y) => y - x));
// 最大ヒープ初期化(lambda 式で Comparer を変更すればよい
PriorityQueue<int, int> maxHeap = new(Comparer<int>.Create((x, y) => y.CompareTo(x)));
/* ヒープに要素をプッシュ */
/* 要素をヒープに追加 */
maxHeap.Enqueue(1, 1);
maxHeap.Enqueue(3, 3);
maxHeap.Enqueue(2, 2);
maxHeap.Enqueue(5, 5);
maxHeap.Enqueue(4, 4);
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
int peek = maxHeap.Peek();//5
/* ヒープの先頭要素をポップ */
// ポップされた要素は降順のシーケンスを形成
/* ヒープ頂点の要素を取り出す */
// 取り出された要素は大きい順の列になる
peek = maxHeap.Dequeue(); // 5
peek = maxHeap.Dequeue(); // 4
peek = maxHeap.Dequeue(); // 3
@@ -181,76 +181,76 @@
/* ヒープのサイズを取得 */
int size = maxHeap.Count;
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
bool isEmpty = maxHeap.Count == 0;
/* リストからヒープを作成 */
/* 入力リストからヒープを構築 */
minHeap = new PriorityQueue<int, int>([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);
```
=== "Go"
```go title="heap.go"
// Goでは、heap.Interfaceを実装することで整数の最大ヒープを構築できます
// heap.Interfaceを実装するには、sort.Interfaceも実装する必要があります
// Go では、heap.Interface を実装することで整数の最大ヒープを構築でき
// heap.Interface を実装するには、同時に sort.Interface も実装する必要があ
type intHeap []any
// heap.InterfaceのPushメソッド、要素をヒープにプッシュ
// Push は heap.Interface のメソッド、要素をヒープに追加する
func (h *intHeap) Push(x any) {
// PushとPopの両方でポインタレシーバーを使用
// スライスの要素を調整するだけでなく、の長さも変更するため
// Push と Pop は pointer receiver を引数に取る
// スライスの内容を調整するだけでなく、スライスの長さも変更するため
*h = append(*h, x.(int))
}
// heap.InterfaceのPopメソッド、ヒープの先頭要素を削除
// Pop は heap.Interface のメソッド、ヒープ頂点の要素を取り出す
func (h *intHeap) Pop() any {
// ヒープからポップする要素は末尾に格納
// 取り出す要素は末尾に格納されている
last := (*h)[len(*h)-1]
*h = (*h)[:len(*h)-1]
return last
}
// sort.InterfaceのLenメソッド
// Len は sort.Interface のメソッド
func (h *intHeap) Len() int {
return len(*h)
}
// sort.InterfaceのLessメソッド
// Less は sort.Interface のメソッド
func (h *intHeap) Less(i, j int) bool {
// 最小ヒープを実装したい場合は、これを小なり比較に変更
// 最小ヒープを実装する場合は、不等号を小なりに変更する
return (*h)[i].(int) > (*h)[j].(int)
}
// sort.InterfaceのSwapメソッド
// Swap は sort.Interface のメソッド
func (h *intHeap) Swap(i, j int) {
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}
// Top ヒープの先頭要素を取得
// Top ヒープ頂点の要素を取得
func (h *intHeap) Top() any {
return (*h)[0]
}
/* ドライバーコード */
/* Driver Code */
func TestHeap(t *testing.T) {
/* ヒープ初期化 */
// 最大ヒープ初期化
/* ヒープ初期化 */
// 最大ヒープ初期化
maxHeap := &intHeap{}
heap.Init(maxHeap)
/* ヒープに要素をプッシュ */
// heap.Interfaceのメソッドを呼び出して要素を追加
/* 要素をヒープに追加 */
// heap.Interface のメソッドを呼び出して要素を追加する
heap.Push(maxHeap, 1)
heap.Push(maxHeap, 3)
heap.Push(maxHeap, 2)
heap.Push(maxHeap, 4)
heap.Push(maxHeap, 5)
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
top := maxHeap.Top()
fmt.Printf("ヒープの先頭要素は %d\n", top)
fmt.Printf("ヒープ頂点の要素は %d\n", top)
/* ヒープの先頭要素をポップ */
// heap.Interfaceのメソッドを呼び出して要素を削除
/* ヒープ頂点の要素を取り出す */
// heap.Interface のメソッドを呼び出して要素を削除する
heap.Pop(maxHeap) // 5
heap.Pop(maxHeap) // 4
heap.Pop(maxHeap) // 3
@@ -261,30 +261,30 @@
size := len(*maxHeap)
fmt.Printf("ヒープ内の要素数は %d\n", size)
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
isEmpty := len(*maxHeap) == 0
fmt.Printf("ヒープは空ですか? %t\n", isEmpty)
fmt.Printf("ヒープは空 %t\n", isEmpty)
}
```
=== "Swift"
```swift title="heap.swift"
/* ヒープ初期化 */
// SwiftHeap型は最大ヒープと最小ヒープの両方をサポートし、swift-collectionsライブラリが必要
/* ヒープ初期化 */
// SwiftHeap 型は最大ヒープと最小ヒープの両方をサポートしており、swift-collections の導入が必要
var heap = Heap<Int>()
/* ヒープに要素をプッシュ */
/* 要素をヒープに追加 */
heap.insert(1)
heap.insert(3)
heap.insert(2)
heap.insert(5)
heap.insert(4)
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
var peek = heap.max()!
/* ヒープの先頭要素をポップ */
/* ヒープ頂点の要素を取り出す */
peek = heap.removeMax() // 5
peek = heap.removeMax() // 4
peek = heap.removeMax() // 3
@@ -294,29 +294,29 @@
/* ヒープのサイズを取得 */
let size = heap.count
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
let isEmpty = heap.isEmpty
/* リストからヒープを作成 */
/* 入力リストからヒープを構築 */
let heap2 = Heap([1, 3, 2, 5, 4])
```
=== "JS"
```javascript title="heap.js"
// JavaScriptは組み込みのHeapクラスを提供していません
// JavaScriptは組み込みの Heap クラスがない
```
=== "TS"
```typescript title="heap.ts"
// TypeScriptは組み込みのHeapクラスを提供していません
// TypeScriptは組み込みの Heap クラスがない
```
=== "Dart"
```dart title="heap.dart"
// Dartは組み込みのHeapクラスを提供していません
// Dartは組み込みの Heap クラスがない
```
=== "Rust"
@@ -325,24 +325,24 @@
use std::collections::BinaryHeap;
use std::cmp::Reverse;
/* ヒープ初期化 */
// 最小ヒープ初期化
/* ヒープ初期化 */
// 最小ヒープ初期化
let mut min_heap = BinaryHeap::<Reverse<i32>>::new();
// 最大ヒープ初期化
// 最大ヒープ初期化
let mut max_heap = BinaryHeap::new();
/* ヒープに要素をプッシュ */
/* 要素をヒープに追加 */
max_heap.push(1);
max_heap.push(3);
max_heap.push(2);
max_heap.push(5);
max_heap.push(4);
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
let peek = max_heap.peek().unwrap(); // 5
/* ヒープの先頭要素をポップ */
// ポップされた要素は降順のシーケンスを形成
/* ヒープ頂点の要素を取り出す */
// 取り出された要素は大きい順の列になる
let peek = max_heap.pop().unwrap(); // 5
let peek = max_heap.pop().unwrap(); // 4
let peek = max_heap.pop().unwrap(); // 3
@@ -352,40 +352,40 @@
/* ヒープのサイズを取得 */
let size = max_heap.len();
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
let is_empty = max_heap.is_empty();
/* リストからヒープを作成 */
/* 入力リストからヒープを構築 */
let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);
```
=== "C"
```c title="heap.c"
// Cは組み込みのHeapクラスを提供していません
// Cは組み込みの Heap クラスがない
```
=== "Kotlin"
```kotlin title="heap.kt"
/* ヒープ初期化 */
// 最小ヒープ初期化
/* ヒープ初期化 */
// 最小ヒープ初期化
var minHeap = PriorityQueue<Int>()
// 最大ヒープ初期化(ラムダ式でComparatorを変更するだけ
// 最大ヒープ初期化(lambda 式で Comparator を変更すればよい
val maxHeap = PriorityQueue { a: Int, b: Int -> b - a }
/* ヒープに要素をプッシュ */
/* 要素をヒープに追加 */
maxHeap.offer(1)
maxHeap.offer(3)
maxHeap.offer(2)
maxHeap.offer(5)
maxHeap.offer(4)
/* ヒープの先頭要素を取得 */
/* ヒープ頂点の要素を取得 */
var peek = maxHeap.peek() // 5
/* ヒープの先頭要素をポップ */
// ポップされた要素は降順のシーケンスを形成
/* ヒープ頂点の要素を取り出す */
// 取り出された要素は大きい順の列になる
peek = maxHeap.poll() // 5
peek = maxHeap.poll() // 4
peek = maxHeap.poll() // 3
@@ -395,55 +395,59 @@
/* ヒープのサイズを取得 */
val size = maxHeap.size
/* ヒープが空かどうかをチェック */
/* ヒープが空かどうかを判定 */
val isEmpty = maxHeap.isEmpty()
/* リストからヒープを作成 */
/* 入力リストからヒープを構築 */
minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))
```
=== "Ruby"
```ruby title="heap.rb"
# Ruby には組み込みの Heap クラスがない
```
??? pythontutor "実行を可視化"
https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
## ヒープの実装
以下の実装は最大ヒープす。最小ヒープに変換するには、すべてのサイズ論理比較を反転させるだけです(えば、$\geq$$\leq$に置き換え)。興味のある読者は自分で実装することをお勧めします
以下は最大ヒープを実装します。最小ヒープに変換したい場合は、すべての大小比較ロジックを反転させるだけです(たとえば、$\geq$$\leq$ に置き換えます)。興味のある読者は自分で実装してみてください
### ヒープの格納と表現
「二分木」ので述べたように、完二分木は配列表現に非常に適しています。ヒープは完備二分木の一種なので、**配列を使用してヒープを格納します**。
「二分木」ので述べたように、完二分木は配列表現するのに非常に適しています。ヒープはまさに完全二分木の一種なので、**ここでは配列を使てヒープを格納します**。
配列を使用して二分木を表現する場合、要素はノード値を表し、インデックスは二分木内のノード位置を表します。**ノードポインタはインデックスマッピング公式を通じて実装されます**。
配列二分木を表場合、要素はノード値を表し、インデックスは二分木におけるノード位置を表します。**ノード間の参照関係はインデックスの対応式によって実現できます**。
図に示すように、インデックス$i$が与えられた場合、その左の子のインデックスは$2i + 1$、右子のインデックスは$2i + 2$、親のインデックスは$(i - 1) / 2$除算)です。インデックスが範囲外の場合、nullノードまたはノードが存在しないことを意味します。
次の図に示すように、インデックス $i$ に対して、左子ノードのインデックスは $2i + 1$ 、右子ノードのインデックスは $2i + 2$ 、親ノードのインデックスは $(i - 1) / 2$切り捨て除算)です。インデックスが範囲外であれば、空ノードまたはノードが存在しないことをします。
![ヒープの表現と格納](heap.assets/representation_of_heap.png)
後で便利に使用するため、インデックスマッピング公式を関数にカプセル化できます:
インデックスの対応式は関数にまとめておくと、後続で使いやすくなります:
```src
[file]{my_heap}-[class]{max_heap}-[func]{parent}
```
### ヒープの先頭要素へのアクセス
### ヒープ頂点の要素にアクセス
ヒープの先頭要素は二分木の根ノードで、リストの最初の要素でもあります:
ヒープ頂点の要素は二分木の根ノード、すなわちリストの先頭要素です:
```src
[file]{my_heap}-[class]{max_heap}-[func]{peek}
```
### ヒープへの要素挿入
### 要素をヒープに追加
要素`val`が与えられた場合、まずそれをヒープの底に追加します。追加後、`val`がヒープ内のの要素より大きい可能性があるため、ヒープの完全性が損なわれる可能性があります。**したがって、挿入されたノードから根ノードまでのパスを修復する必要があります**。この操作<u>ヒープ化</u>と呼ばれます。
与えられた要素 `val` を、まずヒープの底に追加します。追加後、`val` がヒープ内のほかの要素より大きい可能性があるため、ヒープ条件が崩れているかもしれません。**そのため、挿入ノードから根ノードまでの経路上にある各ノードを修復する必要があります**。この操作<u>ヒープ化heapify</u>と呼ます。
挿入されたノードから開始して、**下から上ヒープ化を実行します**。下図に示すように、挿入されたノードの値をその親ノード比較し、挿入されたノードが大きい場合はそれらを交換します。次にこの操作を続行し、根に到達するか交換不要ノードに遭遇するまで、下から上にヒープ内の各ノードを修復します。
ヒープへ追加したノードから始めて、**下から上ヒープ化**を行います。次の図のように、挿入ノードとその親ノードの値を比較し、挿入ノードのほうが大きければそれらを交換します。その後もこの操作を繰り返し、下から上へ各ノードを修復して、根ノードを越えるか交換不要ノードに達した時点で終了します。
=== "<1>"
![ヒープへの要素挿入の手順](heap.assets/heap_push_step1.png)
![要素をヒープに追加する手順](heap.assets/heap_push_step1.png)
=== "<2>"
![heap_push_step2](heap.assets/heap_push_step2.png)
@@ -469,24 +473,24 @@
=== "<9>"
![heap_push_step9](heap.assets/heap_push_step9.png)
ノード数を$n$とすると、木の高さは$O(\log n)$です。したがって、ヒープ化操作のループ反復回数は最大$O(\log n)$、**要素挿入操作の時間計算量は$O(\log n)$になります**。コードは以下のりです:
ノード数を $n$ とすると、木の高さは $O(\log n)$ です。したがって、ヒープ化操作のループ回数は高々 $O(\log n)$ であり、**要素をヒープに追加する操作の時間計算量は $O(\log n)$** です。コードは以下のとおりです:
```src
[file]{my_heap}-[class]{max_heap}-[func]{sift_up}
```
### ヒープからの先頭要素削除
### ヒープ頂点の要素を取り出す
ヒープの先頭要素は二分木の根ノード、つまりリストの最初の要素です。リストから最初の要素を直接削除すると、二分木内のすべてのノードインデックスが変更され、後続の修復にヒープ化を使用することが困難になります。要素インデックスの変更を最小限に抑えるため、次の手順を使用します。
ヒープ頂点の要素は二分木の根ノード、すなわちリストの先頭要素です。もし先頭要素をそのまま削除すると、二分木内のすべてのノードインデックスが変化してしまい、その後のヒープ化による修復が困難になります。要素インデックスの変動をできるだけ小さくするため、次の手順を取ります。
1. ヒープの先頭要素と底の要素を交換します(根ノードと最も右の葉ノードを交換)。
2. 交換後、リストからヒープの底を削除します(交換されているため、実際には元の先頭要素が削除される)。
3. 根ノードから開始し、**上から下ヒープ化を実行します**
1. ヒープ頂点の要素とヒープ底の要素を交換す(根ノードと最も右の葉ノードを交換する)。
2. 交換後、ヒープ底をリストから削除する(すでに交換済みであるため、実際に削除されるのは元のヒープ頂点の要素であることに注意)。
3. 根ノードから開始し、**上から下ヒープ化**を行う
下図に示すように、**「上から下のヒープ化」の方向は「下から上のヒープ化」と反対です**。根ノードの値をその2つの子と比較し、最大の子と交換します。次に、葉ノードに到達するか交換不要ノードに遭遇するまで、この操作を繰り返します。
次の図のように、**「上から下のヒープ化」の方向は「下から上のヒープ化」と逆**です。根ノードの値を 2 つの子ノードと比較し、最大の子ノードと根ノードを交換します。その後、この操作を繰り返し、葉ノードを越えるか交換不要ノードに達した時点で終了します。
=== "<1>"
![ヒープからの先頭要素削除の手順](heap.assets/heap_pop_step1.png)
![ヒープ頂点の要素を取り出す手順](heap.assets/heap_pop_step1.png)
=== "<2>"
![heap_pop_step2](heap.assets/heap_pop_step2.png)
@@ -515,14 +519,14 @@
=== "<10>"
![heap_pop_step10](heap.assets/heap_pop_step10.png)
要素挿入操作と同様に、先頭要素削除操作の時間計算量も$O(\log n)$です。コードは以下のりです:
要素をヒープに追加する操作と同様に、ヒープ頂点の要素を取り出す操作の時間計算量も $O(\log n)$ です。コードは以下のとおりです:
```src
[file]{my_heap}-[class]{max_heap}-[func]{sift_down}
```
## ヒープの一般的な応用
## ヒープの代表的な応用
- **優先度キュー**:ヒープは優先度キューを実装するための好ましいデータ構造で、エンキュー操作とデキュー操作の両方の時間計算量が$O(\log n)$、キュー構築の時間計算量が$O(n)$で、すべて非常に効率的です。
- **ヒープソート**データセットが与えられた場合、それらからヒープを作成し、次に要素削除操作を継続的に実行して順序付けされたデータを取得できます。ただし、ヒープソートを実装するより洗練された方法があり、「ヒープソート」の章で説明されています
- **最大$k$個の要素の発見**:これは古典的なアルゴリズム問題であり、一般的な使用例でもあります。Weiboホット検索のトップ10ホットニュースの選択や、トップ10の売れ筋商品の選択などです。
- **優先度付きキュー**:ヒープは優先度付きキューを実装するための代表的なデータ構造です。キューへの追加と取り出しの時間計算量はいずれも $O(\log n)$ で、ヒープ構築は $O(n)$ であり、これらの操作はいずれも非常に効率的です。
- **ヒープソート**与えられたデータ群からヒープを構築し、要素の取り出しを繰り返すことで整列済みデータを得られます。ただし、通常はより洗練された方法でヒープソートを実装します。詳しくは「ヒープソート」の章を参照してください
- **最大$k$ 個の要素を取得**:これは古典的なアルゴリズム問題であると同時に、典型的な応用でもあります。たとえば、人気上位 10 件のニュースをホットトピックとして選んだり、売上上位 10 件の商品を選んだりする場面です。
+3 -3
View File
@@ -4,6 +4,6 @@
!!! abstract
ヒープは山とその険しい峰のように、層をなして起伏し、それぞれが独特の形を持っています
各山の頂は散らばった高さで上下しますが、最も高いものが常に最初に注目を集めます
ヒープは連なる山々の峰のように、幾重にも重なり、さまざまな形をしている
いくつもの山の高さはまちまちだが、最も高い峰がいつも最初に目に入る
+10 -10
View File
@@ -1,17 +1,17 @@
# まとめ
### 重要な復習
### 重要なポイントの振り返り
- ヒープは完二分木で、その構築性質に基づいて最大ヒープまたは最小ヒープに分類できます。最大ヒープの先頭要素は最大で、最小ヒープの先頭要素は最小です
- 優先度キューは、デキューの優先度を持つキューとして定義され、通常ヒープを使用して実装されます
- ヒープの一般的な操作とそれに対応する時間計算量には以下があります:ヒープへの要素挿入$O(\log n)$、ヒープからの先頭要素削除$O(\log n)$、ヒープの先頭要素へのアクセス$O(1)$。
-二分木は配列で表現するのに適しているため、ヒープは一般的に配列を使用して格納されます
- ヒープ化操作はヒープの性質を維持するために使用され、ヒープの挿入操作と削除操作の両方で使用されます
- $n$個の要素入力して与えられた場合のヒープ構築時間計算量は$O(n)$最適化でき、これは非常に効率的で
- Top-kは古典的なアルゴリズム問題で、ヒープデータ構造を使用して効率的に解でき、時間計算量は$O(n \log k)$です
- ヒープは完二分木であり、条件の違いによって最大ヒープ最小ヒープに分けられる。最大(最小)ヒープの根の要素は最大値(最小値)である
- 優先度付きキューは、取り出し時に優先度が考慮されるキューであり、通常ヒープを用いて実装され
- ヒープの代表的な操作とそれに対応する時間計算量には要素挿入 $O(\log n)$、根の要素削除 $O(\log n)$、根の要素へのアクセス $O(1)$ などがある
-二分木は配列で表現するのに非常に適しているため、通常は配列を使ってヒープを格納する
- ヒープ化操作はヒープの性質を保つために用いられ、挿入操作と削除操作の両方で使用され
- $n$ 個の要素入力してヒープ構築する時間計算量は $O(n)$ まで最適化でき、非常に効率的である
- Top-k は古典的なアルゴリズム問題であり、ヒープ構造を用いることで効率的に解くことができ、時間計算量は $O(n \log k)$ である
### Q & A
**Q**: データ構造の「ヒープ」とメモリ管理の「ヒープ」は同じ概念ですか?
**Q**データ構造の「ヒープ」とメモリ管理の「ヒープ」は同じ概念ですか?
この2つは、どちらも「ヒープ」と呼ばれますが、同じ概念ではありません。コンピュータシステムメモリヒープは動的メモリ割り当ての一部で、プログラム実行中にデータを格納するために使用できます。プログラムは、オブジェクトや配列などの複雑な構造を格納するために、一定量のヒープメモリを要求できます。割り当てられたデータが不要になったときは、メモリリークを防ぐためにプログラムがこのメモリを解放する必要があります。スタックメモリと比較して、ヒープメモリの管理と使用にはより多くの注意が必要で、不適切な使用はメモリリークやダングリングポインタにつながる可能性があります
両者は同じ概念ではなく、たまたまどちらも「ヒープ」と呼ばれているだけである。コンピュータシステムメモリにおけるヒープは動的メモリ割り当ての一部であり、プログラム実行時にこれを使ってデータを格納できる。プログラムは一定量のヒープメモリを要求し、オブジェクトや配列などの複雑な構造を保存できる。これらのデータが不要になったときは、メモリリークを防ぐためにのメモリを解放する必要があ。スタックメモリと比べると、ヒープメモリの管理と使用にはより慎重さが求められ、不適切に扱うとメモリリークやダングリングポインタなどの問題を引き起こす可能性があ
+22 -22
View File
@@ -1,42 +1,42 @@
# Top-k問題
# Top-k 問題
!!! question
長さ$n$の順序付けられていない配列`nums`が与えられたとき、配列内最大$k$個の要素を返してください。
長さ $n$ の未整列配列 `nums` が与えられたとき、配列内最大$k$ 個の要素を返してください。
この問題について、まず2つの直接的な解法を紹介し、次により効率的なヒープベースの方法を説明します。
この問題について、まずは発想が比較的直接的な 2 つの解法を紹介し、その後でより効率の高いヒープ解法を紹介します。
## 方法1:反復選択
## 方法一:走査による選択
図に示すように$k$回の反復を実行し、各回で$1$番目、$2$番目、$\dots$、$k$番目に大きい要素を抽出できます。時間計算量は$O(nk)$です。
以下の図に示すように $k$ 回の走査を行い、各ラウンドでそれぞれ第 $1$、$2$、$\dots$、$k$ 位の要素を取り出すことができます。時間計算量は $O(nk)$ です。
この方法は$k \ll n$の場合にのみ適していま。$k$が$n$に近い場合、時間計算量は$O(n^2)$に近づき、非常に時間がかかります。
この方法は $k \ll n$ の場合にしか適していません。$k$ が $n$ にかなり近いと、時間計算量は $O(n^2)$ に近づき、非常に時間がかかるためです。
![最大k個の要素を反復的に見つける](top_k.assets/top_k_traversal.png)
![走査によって最大の k 個の要素を探す](top_k.assets/top_k_traversal.png)
!!! tip
$k = n$の場合、完全に順序付けられたシーケンスを得ることができ、こは「選択ソート」アルゴリズムと同等です。
$k = n$ のとき、完全な昇順列を得ることができ、この場合は「選択ソート」アルゴリズムと等価になります。
## 方法2:ソート
## 方法:ソート
図に示すように、まず配列`nums`をソートし、次に最後の$k$個の要素を返すことができます。時間計算量は$O(n \log n)$です。
以下の図に示すように、まず配列 `nums` をソートし、その後で右端の $k$ 個の要素を返すことができます。時間計算量は $O(n \log n)$ です。
明らかに、この方法はタスクを「やりすぎ」ています。最大$k$個の要素を見つけるだけでよく、他の要素をソートする必要はありません
明らかに、この方法は必要以上の処理を行っています。なぜなら、必要なのは最大の $k$ 個の要素を見つけることだけであり、他の要素をソートする必要はないからです
![ソートによる最大k個の要素の発見](top_k.assets/top_k_sorting.png)
![ソートによって最大の k 個の要素を探す](top_k.assets/top_k_sorting.png)
## 方法3:ヒープ
## 方法:ヒープ
以下のプロセスに示すように、ヒープに基づいてTop-k問題をより効率的に解決できます。
ヒープを用いることで、Top-k 問題をより効率的に解くことができます。手順は以下の図のとおりです。
1. 最小ヒープを初期化します。先頭要素が最小になります。
2. まず配列の最初の$k$個の要素をヒープ挿入します。
3. $k + 1$番目の要素から開始し、現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープ挿入します。
4. 走査完了した後、ヒープには最大$k$個の要素が含まれています。
1. 最小ヒープを初期化し、そのヒープ頂点の要素が最小となるようにします。
2. まず配列の先頭 $k$ 個の要素を順にヒープ挿入します。
3. $k + 1$ 番目の要素から開始し、現在の要素がヒープ頂点の要素より大きければ、ヒープ頂点の要素を取り出し、現在の要素をヒープ挿入します。
4. 走査完了した後、ヒープに保持されているのが最大の $k$ 個の要素す。
=== "<1>"
![ヒープに基づく最大k個の要素の発見](top_k.assets/top_k_heap_step1.png)
![ヒープに基づいて最大の k 個の要素を探す](top_k.assets/top_k_heap_step1.png)
=== "<2>"
![top_k_heap_step2](top_k.assets/top_k_heap_step2.png)
@@ -62,12 +62,12 @@
=== "<9>"
![top_k_heap_step9](top_k.assets/top_k_heap_step9.png)
サンプルコードは以下のりです
サンプルコードは以下のとおりです
```src
[file]{top_k}-[class]{}-[func]{top_k_heap}
```
合計$n$回のヒープ挿入と削除が実行され、最大ヒープサイズが$k$であるため、時間計算量は$O(n \log k)$です。この方法は非常に効率的で、$k$が小さい場合、時間計算量$O(n)$に近づき、$k$が大きい場合でも、時間計算量は$O(n \log n)$を超えません。
合計$n$ 回のヒープ挿入と取り出しを行い、ヒープの最大長は $k$ であるため、時間計算量は $O(n \log k)$ です。この方法は非常に効率が高く、$k$ が小さいときは時間計算量$O(n)$ に近づき、$k$ が大きいときでも $O(n \log n)$ を超えることはありません。
さらに、この方法は動的データストリームのシナリオに適しています。データ継続的に追加することで、ヒープ内の要素を維持し、最大$k$個の要素動的更新を実現できます。
さらに、この方法は動的データストリームの利用シーンにも適しています。データ継続的に追加される場合でも、ヒープ内の要素を保ち続けることで、最大$k$ 個の要素動的更新できます。