ヒープとBSTの違いは何ですか?
ヒープを使用する場合とBSTを使用する場合
ソートされた方法で要素を取得したい場合、BSTはヒープよりも優れていますか?
概要
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のみのポインターベースのツリー)の上に効率的に実装できます。そのため、時折のサイズ変更の遅延を許容できる場合は、ヒープに対して、よりスペース効率の良いアレイ実装を選択できます。
バイナリヒープ作成 O(n)
最悪の場合 、BSTの場合はO(n log(n))
.
バイナリヒープ上のBSTの利点
任意の要素の検索はO(log(n))
です。 これはBSTのキラー機能です。
ヒープについては、O(n)
である最大の要素を除いて、一般にO(1)
です。
「偽」のBSTよりもヒープの利点
ヒープはO(1)
で最大値、BST O(log(n))
を見つけます。
これは一般的な誤解です。なぜなら、BSTを変更して最大の要素を追跡し、その要素を変更できるたびに更新するのは簡単だからです。 ヒープ検索をシミュレートするためにバイナリ検索ツリーを使用できますか? (前述の by Yeo )。
実際には、これはBSTと比較したヒープの制限です。only効率的な検索は、最大の要素に対するものです。
平均バイナリヒープ挿入はO(1)
ソース:
直感的な議論:
バイナリヒープでは、同じ理由で、特定のインデックスで値を増やすこともO(1)
です。ただし、それを行う場合は、ヒープ操作に関する追加のインデックスを最新の状態に保ちたいと思う可能性があります O(logn)ヒープベースの優先度キュー? 例ダイクストラ。追加費用なしで可能。
実際のハードウェアでのGCC C++標準ライブラリ挿入ベンチマーク
C++ std::set
( 赤黒木BST )およびstd::priority_queue
( 動的配列ヒープ )挿入のベンチマークを行って、正しいかどうかを確認しました時間を挿入し、これは私が得たものです:
だから明らかに:
ヒープ挿入時間は基本的に一定です。
動的な配列のサイズ変更ポイントを明確に見ることができます。 10kの挿入ごとに平均化するため システムノイズを超えてすべてを見ることができるように であるため、これらのピークは実際には表示されているものの約1万倍です!
ズームされたグラフは、本質的に配列のサイズ変更ポイントのみを除外し、ほとんどすべての挿入が25ナノ秒未満であることを示しています。
BSTは対数です。すべての挿入は、平均的なヒープ挿入よりもはるかに遅いです。
BSTとハッシュマップの詳細な分析: C++のstd :: map内のデータ構造は?
gem5のGCC C++標準ライブラリ挿入ベンチマーク
gem5 は完全なシステムシミュレータであるため、m5 dumpstats
で無限に正確なクロックを提供します。そこで、それを使用して個々の挿入のタイミングを推定しようとしました。
解釈:
ヒープはまだ一定ですが、ここで詳細に見ると、いくつかの行があり、それぞれの上位行がより疎です。
これは、より高い挿入で行われるメモリアクセスのレイテンシに対応する必要があります。
TODO BSTを完全に解釈することはできません。BSTが対数的に見えず、多少一定しているためです。
ただし、この詳細を見ると、いくつかの明確な行も表示されますが、それらが何を表しているのかわかりません。上から下に挿入するので、下の行が細くなると思いますか?
これでベンチマーク Buildroot setup aarch64 HPI CP で。
BSTはアレイに効率的に実装できません
ヒープ操作は、単一のツリーブランチをバブルアップまたはダウンするだけでよいため、O(log(n))
最悪の場合のスワップ、O(1)
の平均。
BSTのバランスを保つには、ツリーの回転が必要です。これにより、最上部の要素を別の要素に変更できます。また、配列全体を移動する必要があります(O(n)
)。
ヒープを配列に効率的に実装できます
親インデックスと子インデックスは、現在のインデックスから計算できます ここに示すように 。
BSTのようなバランシング操作はありません。
Delete minは、トップダウンである必要があるため、最も心配な操作です。しかし、ヒープの単一ブランチを「パーコレート」することで常に実行できます ここで説明したように 。これは、ヒープのバランスが常に取れているため、O(log(n))最悪の場合につながります。
削除するノードごとに1つのノードを挿入する場合、削除が支配するためヒープが提供する漸近的なO(1)平均挿入の利点を失い、BSTを使用することもできます。ただし、Dijkstraは削除するたびにノードを数回更新するため、問題ありません。
動的配列ヒープとポインタツリーヒープ
ヒープは、ポインターヒープの上に効率的に実装できます。 効率的なポインターベースのバイナリヒープ実装を行うことは可能ですか?
動的配列の実装は、スペース効率がより高くなります。各ヒープ要素にstruct
へのポインタのみが含まれるとします。
ツリーの実装は、各要素に対して3つのポインター、親、左の子、および右の子を格納する必要があります。したがって、メモリ使用量は常に4n
(3つのツリーポインタ+ 1 struct
ポインタ)です。
ツリーBSTには、さらにバランスの取れた情報も必要です。黒赤さ。
動的配列の実装は、倍増直後のサイズ2n
にすることができます。したがって、平均して1.5n
になります。
一方、バッキングダイナミック配列をそのサイズを2倍にコピーするにはO(n)
ワーストケースが必要であるのに対し、ツリーヒープは各ノードに新しい小さな割り当てを行うため、ツリーヒープにはワーストケースの挿入が適しています。
それでも、バッキング配列の倍増はO(1)
償却されるため、最大遅延の考慮事項になります。 ここに記載 。
哲学
BSTは、親とすべての子孫の間でグローバルプロパティを維持します(左に小さく、右に大きく)。
BSTの最上位ノードは中央の要素であり、これを維持するにはグローバルな知識が必要です(そこには、より小さな要素とより大きな要素がいくつあるかがわかります)。
このグローバルプロパティは、メンテナンス(log n insert)がより高価ですが、より強力な検索(log n search)を提供します。
ヒープは、親と直接の子(親>子)の間のローカルプロパティを維持します。
ヒープの最上位ノードは大きな要素であり、維持するにはローカルの知識(親の知識)のみが必要です。
二重リンクリスト
二重にリンクされたリストは、最初の項目が最も優先されるヒープのサブセットと見なすことができるため、ここでも比較してみましょう。
O(1)
最悪の場合、アイテムへのポインタがあり、更新は本当に簡単です。O(1)
平均、したがってリンクリストよりも悪い。より一般的な挿入位置を持つためのトレードオフ。O(n)
両方この使用例は、ヒープのキーが現在のタイムスタンプである場合です。その場合、新しいエントリは常にリストの先頭に移動します。したがって、正確なタイムスタンプを完全に忘れて、リスト内の位置を優先順位として保持することさえできます。
これを使用して LRUキャッシュ を実装できます。 Dijkstraのようなヒープアプリケーションの場合 のように、キーからリストの対応するノードへの追加のハッシュマップを保持して、どのノードを迅速に更新するかを見つけます。
こちらもご覧ください
CSに関する同様の質問: https://cs.stackexchange.com/questions/27860/whats-the-difference-between-a-binary-search-tree-and-a-binary-heap
ヒープは、高レベルの要素が低レベルの要素よりも大きい(最大ヒープの場合)または小さい(最小ヒープの場合)ことを保証するだけですが、BSTは順序(「左」から「右」)を保証します。ソートされた要素が必要な場合は、BSTを使用します。
ヒープを使用する場合とBSTを使用する場合
ヒープはfindMin/findMax(O(1)
)で優れていますが、BSTはallの検索(O(logN)
)で優れています。挿入は、両方の構造に対してO(logN)
です。 findMin/findMax(優先順位関連など)のみを重視する場合は、ヒープを使用します。すべてを並べ替えるには、BSTを使用します。
here の最初のいくつかのスライドは、物事を非常に明確に説明しています。
他の人が述べたように、ヒープはO(1)でfindMin
orfindMax
を実行できますが、同じデータ構造で両方を実行することはできません。しかし、ヒープがfindMin/findMaxで優れていることに同意しません。実際、わずかな変更で、BSTはbothfindMin
andfindMax
in O(1)。
この変更されたBSTでは、データ構造を潜在的に変更できる操作を行うたびに、最小ノードと最大ノードを追跡します。たとえば、挿入操作では、最小値が新しく挿入された値よりも大きいかどうかを確認し、最小値を新しく追加されたノードに割り当てることができます。同じ手法を最大値に適用できます。したがって、このBSTにはO(1)で取得できるこれらの情報が含まれています。 (バイナリヒープと同じ)
このBST(バランスBST)では、pop min
またはpop max
を使用する場合、次に割り当てられる最小値は、最小ノードのsuccessorです。割り当てられる次の最大値は、最大ノードのpredecessorです。したがって、O(1)で実行されます。ただし、ツリーのバランスを再調整する必要があるため、引き続きO(log n)が実行されます。 (バイナリヒープと同じ)
以下のコメントであなたの考えを聞いてみたいです。ありがとう:)
同様の質問への相互参照 バイナリ検索ツリーを使用してヒープ操作をシミュレートできますか? BSTを使用してヒープをシミュレートする詳細については.
ヒープ上のBSTの別の使用法。重要な違いのため:
ヒープ上でのBSTの使用:では、データ構造を使用してフライトの着陸時刻を保存するとしましょう。着陸時間の差が「d」より小さい場合、着陸するフライトをスケジュールできません。また、多くのフライトがデータ構造(BSTまたはヒープ)に着陸するようにスケジュールされているとします。
ここで、tに着陸する別のフライトをスケジュールします。したがって、tとその後続および先行(> d)の差を計算する必要があります。 したがって、このためにBSTが必要になります。これは高速に実行しますie in O(logn)バランスが取れている場合。
編集済み:
並べ替え BSTは、ソートされた順序で要素を出力するのにO(n)時間(順序走査)を要しますが、ヒープはO(n logn)時間でそれを行います。ヒープはmin要素を抽出し、配列を再ヒープ化します。これにより、O(n logn)時間で並べ替えが行われます。
バイナリ検索ツリーは定義を使用します。つまり、すべてのノードについて、その左側のノードの値(キー)は小さく、右側のノードの値(キー)は大きくなります。
ヒープとして、バイナリツリーの実装は次の定義を使用します。
AとBがノードで、BがAの子ノードである場合、Aの値(キー)はBの値(キー)以上でなければなりません。つまり、key(A)≥key(B )。
http://wiki.answers.com/Q/Difference_between_binary_search_tree_and_heap_tree
私は今日、試験のために同じ質問に出くわし、正解しました。笑顔... :)
配列からn個の要素をすべてBSTに挿入すると、O(n logn)がかかります。配列内のn個の要素は、O(n)時間でヒープに挿入できます。ヒープに明確な利点があります
ヒープは、高レベルの要素が低レベルの要素よりも大きい(最大ヒープの場合)または小さいこと(最小ヒープの場合)を保証するだけです。
私は上記の答えが大好きで、自分のニーズと使用法にさらに具体的なコメントを入れています。 n個のロケーションリストを取得して、各ロケーションから特定のポイント(0,0)までの距離を見つけてから、より小さな距離を持つa m個のロケーションを返す必要がありました。ヒープである優先度キューを使用しました。距離を見つけてヒープに入れるには、挿入ごとにn(log(n)) n個の場所log(n)が必要でした。次に、最短距離でmを取得するために、m(log(n)) m-locations log(n)のヒープの削除が必要でした。
私はこれをBSTで行う必要がある場合、n(n)最悪の場合の挿入が必要になります。(最初の値が非常に小さく、他のすべてが順次長くなり、ツリーが右側の子のみ、または小さい場合は左側の子。最小時間はO(1)時間かかりましたが、再びバランスを取る必要がありました。最小または最大優先度ベースの値がヒープに移行した後にのみです。