二分ヒープ:配列でつくる優先度付きキュー

配列に並んだ15個のキーを、木として見てみましょう。位置 i の子は、位置 2i + 1 と 2i + 2 にあります。一つずつ挿入する方法と、最後の親から調整する方法でヒープをつくり、比較と交換を一手ずつ確かめます。最小値を取り出すと、次に小さいキーが根に現れます。

インタラクティブシミュレーションを読み込んでいます...

レッスン

理論 — 二分ヒープ:配列でつくる優先度付きキュー

二分最小ヒープは、どの位置 i でも、そこのキーが位置 2i + 1 と 2i + 2 にある子のキー以下になっている配列です。位置 i の親は ⌊(i − 1) / 2⌋ にあります。この関係で配列を読むと完全二分木になり、最小のキーは常に位置0、つまり根にあります。挿入では、キーを末尾に加えて上方修正します。親より小さい間、親と交換して上へ移す操作です。最小値を取り出すときは、末尾のキーを根に移して下方修正します。子のうち小さいほうが自分より小さければ、その子と交換して下へ移します。このように使うヒープは、優先度付きキューとして働きます。

各記号の意味

n
ヒープ内のキーの個数
i
0から数えた配列内の位置
h
その位置の高さ:下に何段あるかを表す値、⌊log₂(n / (i + 1))⌋
前提
比較とは、キーどうしの大小を一度調べることです。交換は、二つの位置のキーを入れ替えること。最小値を取り出す際に末尾のキーを根へ移す操作は、どちらにも数えません。キーが親、または小さいほうの子と等しければ、その場にとどまります。子どうしが等しい場合は左の子を選びます。ここでは最小のキーを根に置きますが、比較の向きをすべて逆にすれば最大ヒープになります。スマートフォンでも木全体を見られるよう、キーは0〜99の値で最大15個としています。表の最終行では、すべて異なるキーの並べ方を数えています。

逐次挿入の手間は深さで、最後の親からの構築の手間は高さで決まる 🖖

大きい順・一つずつ挿入を押してみましょう。新しいキーはヒープ内のどのキーよりも小さいため、底から根まで上がります。1段につき比較と交換が各1回。根は0回、次の2個は各1回、その次の4個は各2回、最後の8個は各3回で、合計34回です。続いて大きい順・最後の親から調整を押します。同じ15個を到着順に並べ、子を持つ位置を6から0へ順に下方修正します。キーが下がれるのは、その下に段があるところまで。15個の位置のうち8個は葉で、下には何もありません。逐次挿入ではそれぞれ3段分の手間がかかった位置が、こちらでは何の操作も要らないのです。高さの合計は11で、1段につき比較は2回なので、計22回。どのヒープでも、高さの合計は n から「n を二進法で表したときの 1 の個数」を引いた値になります。そのため、この構築方法の比較回数は常に 2n 回未満です。1,023個のキーが大きい順に届く場合、両者の比較回数は8,194回と2,026回になります。

小さい順なら、どちらの構築方法も比較は14回 🖖

小さい順・一つずつ挿入を押してみましょう。新しいキーは初めから親以上なので、比較は1回、交換はなく、計14回です。最後の親から調整に切り替えても14回。各親で子どうしを比較し、小さいほうの子と親を比較すると、そこで止まるからです。根以外のキーは少なくとも1回は比較する必要があるため、14回はどちらの方法でも下限です。ここから比較回数が増えるにつれ、両者に差がつきます。大きい順の入力では、逐次挿入は上限に達します。シャッフルした入力なら、比較回数は下限にずっと近くなります。1,000個のキーを200回シャッフルした実験では、平均比較回数は逐次挿入が2,235回、最後の親からの構築が1,862回でした。同じキーを大きい順に逐次挿入した場合の7,987回より、かなり少なくなっています。15個では差はわずかです。シャッフルを何度か押すと、逐次挿入のほうが少ない回数で済むこともあります。

ヒープでも、全体が整列しているとは限らない 🖖

ヒープの条件は、どのキーもその子以下であることです。異なる7個のキーの並べ方5,040通りのうち、この条件を満たすのは80通り。根には最小値が入り、残る6個のうちどの3個を左部分木に割り当ててもかまいません。ただし、左右の部分木もそれぞれヒープである必要があります。この個数は、7! を各部分木の大きさの積 7 · 3 · 3 で割ると求まります。15個の場合、結果の表にあるとおり、並べ方1,307,674,368,000通りのうちヒープは21,964,800通り。その中で昇順に並んでいるのはわずか1通りです。許される並べ方が多いからこそ、ヒープは少ない手間でつくれます。小さい順にそろうのは、キーを取り出していくときです。ヒープソートを押してみましょう。根を取り出すたびに末尾のキーで空きを埋め、下方修正すると、次の最小値が根に現れます。12個のキーが3, 7, 12から93まで、順に出てきます。

全プロセスの詳細解説

  1. キーが千個ほどになると、構築に何回の比較が必要? 4 ステップ

    大きい順・一つずつ挿入、大きい順・最後の親から調整を順に押し、比較回数が34回と22回になる理由を説明しましょう。では、1,023個のキーが大きい順に届く場合、それぞれ何回の比較が必要でしょうか。

    この問題をツールで開く

    1. 位置 i の深さは ⌊log₂(i + 1)⌋ です。深さ0には1個、深さ1には2個、深さ2には4個、深さ3には8個の位置があります。大きい順に届くと、新しいキーは常にそれまでの最小値なので根まで上がり、1段につき比較と交換が各1回必要です。したがって、0 + 2 · 1 + 4 · 2 + 8 · 3 = 34 となります。

    2. 最後の親から構築するときに効くのは、高さです。根の高さは3、位置1と2は2、深さ2の4個は1、葉の8個は0で、合計11。大きい順なら、どのキーも下がれるところまで下がります。15個ではどの親にも子が2個あるため、1段につき比較は2回。比較22回、交換11回になります。

    3. 1,023 = 2¹⁰ − 1 なので、全10段が埋まった二分木になり、深さ d には 2ᵈ 個の位置があります。逐次挿入の比較回数は深さの合計で、1 · 2 + 2 · 4 + 3 · 8 + ⋯ + 9 · 512 = 8,194 回。交換も同じ回数です。

    4. 最後の親から構築する場合、比較回数は高さの合計の2倍です。高さ0の位置が 2⁹ 個、高さ1が 2⁸ 個と続き、根の高さは9。高さの合計は 1 · 256 + 2 · 128 + ⋯ + 9 · 1 = 1,013 で、1,023からその二進表記に含まれる10個の 1 を引いた値に等しくなります。その2倍で比較は2,026回、交換は1,013回です。

    解答

    一つずつ挿入すると比較8,194回、最後の親から調整すると2,026回です。キーあたりでは、およそ8回と2回。ヒープ内の位置の多くは深さが大きく、高さは小さいため、深さに応じて手間のかかる逐次挿入と、高さに応じて手間のかかる最後の親からの構築で差がつきます。このツールで扱えるのは15個までですが、同じ計算で画面の34回と22回が得られます。

学習の道すじ

データ構造が得意なこと

参考文献 (2)