kaage精進録

雑な解説とかライブラリとかおきもちの垂れ流しです。

二分ヒープについて

二分ヒープを書きました

二分ヒープ is 何

平たく言えば std::priority_queue みたいなやつ

次の操作がいい感じの計算量でできる

  • top()
    • 最大値を取得する
    • $O(1)$
  • pop()
    • 最大値を削除する
    • $O(\log N)$
  • push(value)
    • 値を追加する
    • $O(\log N)$

構造

二分木を持つ.各ノード(葉以外も)が値を持っていて,セグ木と同じ要領で 1-indexed である. f:id:kaage:20210329212618p:plain

ヒープは,次の条件をつねに満たす.

  • あるノードの親のもつ値は,子のもつ値以上である.

これを満たすようにクエリに対応する.なお,ノードの追加自体は,ヒープを持っている可変長配列に push_back するだけでできる.これで,任意のサイズのヒープを単純なかたちで作れる.

top

根のノードを参照すればよい.おわり.

pop

根のノードを削除して,一番 index の大きいノードを根の位置にもってくる.

ヒープ条件を満たすように,持ってきたノードを根からぐんぐん下に swap して持っていくとよい.

$O(\log N)$ でできる.

push

ヒープを持つ配列に push_back して,一番下に持ってくる.ヒープ条件を満たすまで上に swap していけばよい.最悪計算量はもちろん $O(\log N)$ だが,平均計算量は $O(1)$ らしい.まあお気持ちはわかるが証明は思いついてない.

$O(1)$ はあくまで平均計算量で,償却はされないことに注意.

進化

これだけだと std::priority_queue と同じなので,嬉しさがない. そこで,次のようにインターフェースを書き換えることを考える.

  • top()
    • key が最大となる要素と,その番号を返す.
    • $O(1)$
  • pop()
    • key が最大となる要素を削除する.
    • $O(\log N)$
  • push(id, key)
    • (id, key) の組を追加する.
    • $O(\log N)$
  • prioritize(id, key)
    • id について,key の値を更新する.
    • $O(\log N)$

こうすると,ダイクストラをするときに queue が小さくなって,嬉しい(push が prioritize に書き換えられて,場合分けが一個減る)

これは,id としてありうる値を index にもてる配列を用意して,A[i] = id が i であるノードのポインタ のようにしてやるとよい.(なお,今回の二分木は配列上に表現できるから,その index を持ってあげれば十分)

prioritize(id, key) が呼ばれたら,この配列を利用してノードの値を書き換え,上か下に適当に swap してやると,$O(\log N)$ でヒープの条件を満たすようにできる.

ともあれ,こうするとダイクストラで定数倍を落とせる.うれしい.

つづき

Fibonacci Heap を使うともっと計算量が落ちます.今度実装してまたここにあげます.