ヒープとバイナリ検索ツリー(BST)


169

ヒープとBSTの違いは何ですか?

ヒープを使用する場合とBSTを使用する場合

要素を並べ替えて取得したい場合、BSTはヒープよりも優れていますか?


13
この質問はコンピューターサイエンスに関するものであり、cs.stackexchange.comで質問する必要があるため、トピックから外れているように見えます
Flow


3
スタック交換とスタックオーバーフローの両方に関係しているような気がします。だから、ここに
置い

回答:


191

概要

          Type      BST (*)   Heap
Insert    average   log(n)    1
Insert    worst     log(n)    log(n) or n (***)
Find any  worst     log(n)    n
Find max  worst     1 (**)    1
Create    worst     n log(n)  n
Delete    worst     log(n)    log(n)

この表の平均時間はすべて、挿入を除いて最悪の時間と同じです。

  • *:この回答のすべての箇所で、BST ==バランスBST。アンバランスは漸近的に吸引するため
  • **:この回答で説明されている簡単な変更を使用
  • ***log(n)ポインターツリーヒープ、n動的配列ヒープ

BSTに対するバイナリヒープの利点

  • O(1)BSTの場合、バイナリヒープへの平均挿入時間はO(log(n))です。これはヒープのキラー機能です。

    フィボナッチヒープO(1)などの償却済み(より強い)に到達する他のヒープもありますが、最悪の場合はBrodalキューですが、漸近的なパフォーマンスがないため実用的ではない可能性があります。

  • バイナリヒープは、動的配列またはポインターベースのツリー(BSTのみのポインターベースのツリー)の上に効率的に実装できます。したがって、ヒープの場合、サイズ変更のレイテンシを時々許容できるのであれば、よりスペース効率の高い配列の実装を選択できます。

  • BSTの場合、バイナリヒープの作成O(n)最悪の場合O(n log(n))です。

バイナリヒープに対するBSTの利点

  • 任意の要素の検索はO(log(n))です。これはBSTのキラー機能です。

    ヒープの場合O(n)、最大の要素であるを除いて、それは一般的にですO(1)

BSTに対するヒープの「誤った」利点

  • ヒープはO(1)最大値、BSTを見つけることO(log(n))です。

    これはよくある誤解です。BSTを変更して最大の要素を追跡し、その要素が変更される可能性がある場合はいつでも更新することは簡単です。大きな1つのスワップを挿入すると、2番目に大きいものを見つけます。バイナリ検索ツリーを使用してヒープ操作をシミュレートできますか?Yeoが言及)。

    実際、これはBSTと比較したヒープの制限です。最も効率的な検索は、最大の要素の検索のみです。

平均バイナリヒープ挿入は O(1)

出典:

直感的な議論:

  • 最下位ツリーレベルには最上位レベルよりも指数関数的に多くの要素があるため、新しい要素は最下位に行くことがほぼ確実です
  • ヒープの挿入は下から始まり、BSTは上から始まる必要があります

バイナリヒープでO(1)は、同じ理由で特定のインデックスの値を増やすこともできます。しかし、それを行う場合は、ヒープ操作で追加のインデックスを最新に保つ必要がある可能性があります。最小ヒープベースの優先度キューにO(logn)の減少キー操作を実装する方法は?ダイクストラなど。追加の時間コストなしで可能です。

実際のハードウェアでのGCC C ++標準ライブラリ挿入ベンチマーク

C ++ std::setRed-black tree BST)とstd::priority_queuedynamic array heap)の挿入をベンチマークして、挿入時間が適切かどうかを確認しました。

ここに画像の説明を入力してください

  • ベンチマークコード
  • プロットスクリプト
  • データのプロット
  • Ubuntu 19.04、CPU搭載Lenovo ThinkPad P51ラップトップのGCC 8.3.0でテスト済み:Intel Core i7-7820HQ CPU(4コア/ 8スレッド、2.90 GHzベース、8 MBキャッシュ)、RAM:2x Samsung M471A2K43BB1-CRC(2x 16GiB 、2400 Mbps)、SSD:Samsung MZVLB512HAJQ-000L7(512 GB、3,000 MB /秒)

明らかに:

  • ヒープ挿入時間は基本的に一定です。

    動的な配列のサイズ変更ポイントをはっきりと見ることができます。システムノイズを超えて何でも見ることができるように、 1万回の挿入ごとに平均化しているため、これらのピークは実際には表示されている値よりも約10倍大きくなっています。

    ズームされたグラフは、基本的に配列のサイズ変更ポイントのみを除外し、ほとんどすべての挿入が25ナノ秒未満であることを示しています。

  • BSTは対数です。すべての挿入は、平均的なヒープ挿入よりもはるかに低速です。

  • BSTとハッシュマップの詳細な分析:C ++のstd :: map内にはどのようなデータ構造がありますか?

GCC C ++標準ライブラリのgem5でのベンチマーク挿入

gem5は完全なシステムシミュレータであるため、で無限に正確なクロックを提供しm5 dumpstatsます。そのため、個々の挿入のタイミングを推定するためにそれを使用しようとしました。

ここに画像の説明を入力してください

解釈:

  • ヒープはまだ一定ですが、行がいくつかあり、より高い各行がより疎であることがより詳細にわかります。

    これは、メモリアクセスのレイテンシに対応している必要があり、挿入回数が多くなるほど処理が遅くなります。

  • TODO BSTは対数的ではなく、やや一定しているように見えないため、BSTを完全に解釈することはできません。

    この詳細を使用すると、いくつかの明確な線を確認することもできますが、それらが何を表しているのかわかりません。

aarch64 HPI CPUでのこのBuildrootセットアップでベンチマーク。

BSTはアレイに効率的に実装できません

ヒープ操作では、単一のツリーブランチをバブルアップまたはダウンするだけでよいので、O(log(n))最悪の場合はスワップがO(1)平均的です。

BSTのバランスを保つには、ツリーの回転が必要です。これにより、最上位の要素を別の要素に変更でき、配列全体を移動する必要があります(O(n))。

ヒープはアレイに効率的に実装できます

親と子のインデックスは、次のように現在のインデックスから計算できます

BSTのようなバランシング操作はありません。

削除分はトップダウンである必要があるため、最も心配な操作です。ただし、ここで説明するように、ヒープの1つのブランチを「浸透」させることによって、常にそれを行うことができます。ヒープは常にバランスが取れているため、これはO(log(n))の最悪のケースにつながります。

削除するノードごとに1つのノードを挿入する場合、削除が支配するためヒープが提供する漸近的なO(1)平均挿入の利点が失われ、BSTを使用することもできます。ただし、ダイクストラは削除ごとにノードを数回更新するため、問題ありません。

動的配列ヒープとポインターツリーヒープ

ヒープはポインターヒープの上に効率的に実装できます。効率的なポインターベースのバイナリヒープ実装を作成することは可能ですか?

動的配列の実装は、よりスペース効率的です。各ヒープ要素にaへのポインタのみが含まれているとしますstruct

  • ツリーの実装は、要素ごとに3つのポインタ(親、左の子、右の子)を格納する必要があります。したがって、メモリ使用量は常に4n(3つのツリーポインター+ 1つのstructポインター)です。

    Tree BSTには、さらに黒赤みなどのバランス情報も必要です。

  • 動的配列の実装は2n、2倍した直後のサイズにすることができます。したがって、平均的にはそうなります1.5n

一方、ツリーヒープはワーストケースの挿入の方が優れています。これは、バッキングダイナミック配列をコピーしてサイズを2倍にするのがO(n)最悪の場合であり、ツリーヒープは各ノードに新しい小さな割り当てを行うだけだからです。

それでも、バッキングアレイの倍増はO(1)償却されるため、レイテンシを最大にすることになります。ここで言及

哲学

  • BSTは、親とすべての子孫の間のグローバルプロパティを維持します(左に小さい、右に大きい)。

    BSTの最上位ノードは中央の要素であり、維持するためにグローバルな知識が必要です(そこに存在するより小さな要素とより大きな要素の数を知っています)。

    このグローバルプロパティは維持にコストがかかります(log n insert)が、より強力な検索(log n search)を提供します。

  • ヒープは、親と直接の子(親>子)の間のローカルプロパティを維持します。

    ヒープの最上位ノードは大きな要素であり、維持するためにローカルの知識のみが必要です(親を知っている)。

BSTとヒープとハッシュマップの比較:

  • BST:次のいずれかになります。

    • 順序なしセット(要素が以前に挿入されたかどうかを決定する構造)。しかし、ハッシュマップは、O(1)の償却挿入により、より良い傾向にあります。
    • 選別機。ただし、ヒープの方が一般的には優れています。そのため、ヒープソートツリーソートよりもはるかに広く知られています。
  • ヒープ:単なる選別機です。最小/最大の要素のみを高速でチェックできるため、効率的な順序なしセットにすることはできません。

  • ハッシュマップ:順序付けを混同するため、効率的なソーティングマシンではなく、順序付けされていないセットのみを使用できます。

二重リンクリスト

二重にリンクされたリストは、最初の項目が最も優先されるヒープのサブセットと見なすことができるため、ここでもそれらを比較してみましょう。

  • 挿入:
    • ポジション:
      • 二重にリンクされたリスト:挿入された項目は最初または最後のいずれかでなければなりません。これらの要素へのポインターしかないからです。
      • バイナリヒープ:挿入されたアイテムは任意の位置に配置できます。リンクリストよりも制限が少ない。
    • 時間:
      • 二重にリンクされたリスト:O(1)アイテムへのポインターがあり、更新が非常に簡単であるため、最悪の場合
      • バイナリヒープ:O(1)平均、リンクリストよりも悪い。より一般的な挿入位置を持つことのトレードオフ。
  • 検索:O(n)両方

この使用例は、ヒープのキーが現在のタイムスタンプである場合です。その場合、新しいエントリは常にリストの先頭に移動します。そのため、正確なタイムスタンプを完全に忘れて、リスト内の位置を優先度として保持することさえできます。

これは、LRUキャッシュの実装に使用できます。同じようにダイクストラのようなヒープのアプリケーションのために、あなたはすぐに更新するノードを見つけるために、リストの対応するノードの鍵から追加HashMapを維持したいと思うでしょう。

さまざまなバランスBSTの比較

これまで見てきた「バランスBST」として一般的に分類されるすべてのデータ構造の漸近的な挿入時間と検索時間は同じですが、BBSTによってトレードオフは異なります。私はまだこれを完全に研究していませんが、これらのトレードオフをここで要約するとよいでしょう:

  • 赤黒木。2019年の時点で最も一般的に使用されるBBSTであると思われます。たとえば、これはGCC 8.3.0 C ++実装で使用されるものです。
  • AVLツリー。BSTよりも少しバランスが取れているように見えます。そのため、わずかに高価な検索を犠牲にして、検索の待機時間を改善することができます。Wikiの要約:「AVLツリーは、基本的に同じ操作セットをサポートし、[同じ]時間を要するため、赤黒木と比較されることがよくあります。ルックアップ集約型のアプリケーションでは、AVLツリーは赤黒木よりも高速です。赤黒木と同様に、AVLツリーは高さのバランスがとれています。一般に、どちらもmu <1/2の場合、重みのバランスもmuのバランスも取れていません。つまり、兄弟ノードは、異なる数の子孫。」
  • WAVL原稿用紙は、リバランスと回転操作の境界面でそのバージョンの利点を言及しています。

こちらもご覧ください

CSでの同様の質問:https : //cs.stackexchange.com/questions/27860/whats-the-difference-between-a-binary-search-tree-and-a-binary-heap


4
私は+1しましたが、平均のO(1)バイナリヒープ挿入を正当化する「紙」はデッドリンクになり、「スライド」は証拠なしで主張を述べています。また、ここで「平均的なケース」とは、挿入された値が特定の分布からのものであると仮定した場合の平均意味することを明確にするのに役立つと思うので、この機能が実際に「キラー」かどうかはわかりません。
j_random_hacker

3
BSTとバランスBSTは互換的に使用されているようです。混乱を避けるために、回答はバランスのとれたBSTに言及していることを明確にする必要があります。
gkalpak

2
-私たちは少しdigressingしているが、我々は両方を同時に最大と最小にしたい場合は、我々は注意しないなら、我々は2つのヒープを維持することで問題に遭遇するかもしれないと感じ@Bulat stackoverflow.com/a/1098454/7154924。この目的のために特別に設計されたmax-minヒープ(Atkinsonらによる)を使用することをお勧めします。
flow2k 2018

1
@CiroSantilli新疆改造中心六四事件法轮功:バイナリヒープの削除操作がO(log n)である理由がわかりません。これは、ヒープ内の要素へのポインターがある場合にのみ機能しますが、ほとんどの場合、キーがあり、O(n)を取得する要素を最初に見つける必要があります。
リコラ

5
ヒープの挿入はo(1)ではなくlog(n)です
Bobo

78

ヒープは、より高いレベルの要素がより低いレベルの要素よりも大きい(max-heapの場合)または小さい(min-heapの場合)ことを保証しますが、BSTは順序(「左」から「右」へ)を保証します。要素をソートしたい場合は、BSTを使用してください。


8
「ヒープは、より高いレベルの要素がより低いレベルの要素よりも大きい(max-heapの場合)または小さい(min-heapの場合)ことを保証するだけです...」–ヒープはレベルごとはなく、親子でのみこれを強制しますチェーン。[1, 5, 9, 7, 15, 10, 11]有効な最小ヒープを表しますが、7レベル3は9レベル2 よりも小さくなっています。視覚化については、たとえば、ヒープのサンプルWikipedia画像の25および19要素を参照してください。(要素は必ずしも一意ではないため、要素間の不等関係は厳密ではないことにも注意してください。)
Daniel Andersson

エントリーが遅れて申し訳ありませんが、わかりやすくしたいだけです。Binary Heapがソートされている場合、検索の最悪のケースはlog n rightになります。したがって、その場合は、バイナリヒープをバイナリサーチツリー(赤黒BST)よりも適切にソートします。ありがとう
クリシュナ

50

ヒープを使用する場合とBSTを使用する場合

ヒープはfindMin / findMax(O(1))で優れていますが、BSTはすべての find()で優れていますO(logN)。挿入はO(logN)両方の構造のためです。findMin / findMax(たとえば、優先度関連)のみに関心がある場合は、ヒープを使用してください。すべてをソートしたい場合は、BSTを使用してください。

ここからの最初の数枚のスライドでは、非常に明確に説明しています。


3
最悪の場合、挿入はどちらも対数ですが、平均ヒープ挿入には一定の時間がかかります。(既存の要素のほとんどが下部にあるため、ほとんどの場合、新しい要素は、1つまたは2つのレベルまで浮上するだけで済みます。)
johncip

1
@xysun BSTはfindMinとfindMaxで優れていると思いますstackoverflow.com/a/27074221/764592
Yeo

2
@Yeo:ヒープは、findMin xor findMaxに適しています。両方が必要な場合は、BSTの方が適しています。
Mooing Duck、2015

1
これはよくある誤解です。バイナリツリーは、Yeoがポイントする最小値と最大値を見つけるように簡単に変更できます。これは実際にはヒープの制限です。唯一の効率的な検索は最小または最大です。ヒープの本当の利点は、O(1)平均インサート Iを説明するとおりstackoverflow.com/a/29548834/895245
チロSantilli郝海东冠状病六四事件法轮功

1
Ciro Santilliの答えははるかに優れています:stackoverflow.com/a/29548834/2873507
Vic Seedoubleyew

9

他の人が述べたように、ヒープを行うことができfindMin 、または findMax同一のデータ構造に双方O(1)ではありません。ただし、findMin / findMaxの方がヒープの方が優れているとは思いません。実際、わずかな変更で、BSTはO O(1)の両方 findMin 実行できますfindMax

この変更されたBSTでは、データ構造を変更する可能性のある操作を実行するたびに、最小ノードと最大ノードを追跡します。たとえば、挿入操作では、最小値が新しく挿入された値より大きいかどうかを確認し、新しく追加されたノードに最小値を割り当てることができます。同じ手法を最大値に適用できます。したがって、このBSTにはO(1)で取得できるこれらの情報が含まれています。(バイナリヒープと同じ)

このBST(バランスBST)では、pop minまたはのpop max場合、次に割り当てられる最小値は最小ノードの後続であり、次に割り当てられる最大値は最大ノードの先行です。したがって、O(1)で実行されます。ただし、ツリーのバランスを再調整する必要があるため、引き続きO(log n)が実行されます。(バイナリヒープと同じ)

以下のコメントであなたの考えを聞きたいです。ありがとう:)

更新

同様の質問への相互参照バイナリ検索ツリーを使用してヒープ操作をシミュレートできますか?BSTを使用したヒープのシミュレーションに関する詳細については、


なぜあなたは反対しますか?以下の考えを共有してもらえますか?
Yeo

BSTの最大値または最小値、あるいはその両方を確実に格納できますが、それをポップしたい場合はどうなりますか?ツリーを検索して削除してから、新しい最大値/最小値をもう一度検索する必要があります。どちらもO(log n)操作です。これは優先順位ヒープでの挿入と削除と同じ順序ですが、定数は悪化します。
ジャスティン

@JustinLardinois申し訳ありませんが、私の回答でこれを強調するのを忘れています。BSTでは、pop minを実行すると、次に割り当てられるmin値はminノードの後継です。最大値をポップした場合、次に割り当てられる最大値は最大ノードの先行ノードです。したがって、それでもO(1)で実行されます。
Yeo

修正:for popMinまたはpopMaxO(1)ではありませんが、すべての削除操作を再調整する必要があるバランスBSTである必要があるため、O(log n)です。したがって、バイナリヒープと同じpopMinpopMax、O(log n)を実行します
Yeo

2
最初の最小/最大を取得できますが、k番目の最小/最大を取得すると、通常のBSTの複雑さに戻ります。
カオス

3

二分探索木は定義を使用します。つまり、すべてのノードについて、その左側のノードの値(キー)は小さく、その右側のノードの値(キー)は大きくなります。

ヒープと同様に、バイナリツリーの実装は次の定義を使用します。

AとBがノードであり、BがAの子ノードである場合、Aの値(キー)はBの値(キー)以上でなければなりません。つまり、key(A)≥key(B )。

http://wiki.answers.com/Q/Difference_between_binary_search_tree_and_heap_tree

今日も同じ質問に出題され、正解しました。笑顔... :)


「ヒープ、バイナリツリーの実装である」-ヒープが一種のBSTではなくバイナリツリーの一種であることを指摘するだけ
Saad

3

ヒープ上のBSTの別の使用法。重要な違いがあるため:

  • BSTで後続および先行を見つけるにはO(h)時間かかります。(バランスBSTのO(logn))
  • ヒープ内では、ある要素の後続または先行を見つけるのにO(n)時間かかります。

ヒープを介したBSTの使用:次に、データ構造を使用してフライトの着陸時間を格納するとします。着陸時間の差が「d」未満の場合、着陸へのフライトをスケジュールできません。そして、多くのフライトがデータ構造(BSTまたはヒープ)に着陸するようにスケジュールされていると想定します。

ここで、tに着陸する別のフライトをスケジュールします。したがって、tとその後続および先行(dである必要があります)との差を計算する必要があります。 したがって、我々は速いそれをしないいる、このためにBSTが必要になりますつまりバランスがあればO(LOGN)で。

編集:

ソート ヒープはO(N LOGN)でそれを行うことができながら、BSTは、時間、ソート順(INORDERトラバーサル)の要素を印刷するためのO(N)時間を要します。ヒープは最小要素を抽出し、配列を再度ヒープ化します。これにより、O(n logn)時間でソートが行われます。


1
はい。ソートされていないものからソートされたものまでです。ソートされたシーケンスを提供するBSTの順序トラバーサルのO(n)時間。ヒープでは、最小要素を抽出し、O(log n)時間で再ヒープします。つまり、n個の要素を抽出するにはO(n logn)が必要です。そして、ソートされたシーケンスが残ります。
CODError

from unsorted to sorted sequence. O(n) time for inorder traversal of a BST, which gives sorted sequence.まあ、ソートされていないシーケンスからBSTまで、O(n logn)時間未満のキー比較に基づく方法はわかりません。これは、BSTをシーケンス部分に支配します。(一方、O(n)ヒープ構造があります。)ヒープがソートされていないこととBSTがソートされていることを示すのは(無意味な場合でも)公平だと思います。
greybeard 2015

私がここで説明しようとしているのは、BSTとn要素のヒープがある場合、すべての要素が両方のデータ構造からソートされた順序で印刷され、BSTがO(n)時間で実行できることです(順序トラバーサル) )、ヒープはO(n logn)時間かかります。ここで何を言おうとしているのか理解できません。How do you say BSTは、O(n logn)で並べ替えられた順序を提供します。
CODError

BSTとヒープの構築にかかる時間も検討していると思います。しかし、私はあなたがすでにそれを持っていると思います、あなたはそれを時間をかけて構築し、今あなたはソートされた結果を得たいと思っています。要点がわかりませんか?
CODError

1
編集済み...満足していただければ幸いです。; p正しい場合は+1してください。
CODError

1

配列からすべてのn要素をBSTに挿入すると、O(n logn)がかかります。配列内のn個の要素は、O(n)時間でヒープに挿入できます。ヒープに明確な利点があります


0

ヒープは、高レベルの要素が低レベルの要素よりも大きい(最大ヒープの場合)または小さい(最小ヒープの場合)ことを保証するだけです。

私は上記の答えが好きで、私のコメントを自分のニーズと使用法にもっと具体的に当てはめます。n個の場所のリストを取得して、各場所から特定のポイント(0,0)までの距離を検索し、距離が短いamの場所を返す必要がありました。私はヒープである優先キューを使用しました。距離を見つけてヒープを配置するために、挿入ごとにn(log(n))n-locations log(n)かかりました。次に、最短距離でmを取得するために、m(log(n))m-locations log(n)の山積みの削除が行われました。

これをBSTで行う必要がある場合、n(n)の最悪の場合の挿入が必要になります(最初の値は非常に小さく、他のすべての値は順次長くなり、ツリーは右の子のみまたは左の子にまたがるとしますどんどん小さくなった場合、最小値はO(1)時間かかったはずですが、再びバランスを取る必要がありました。したがって、私の状況と上記のすべての答えから、最小または最大の優先度ベースの値が後になって初めて、ヒープ用。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.