ハッシュテーブルに対するバイナリ検索ツリーの利点


101

ハッシュテーブルよりもバイナリ検索ツリーの利点は何ですか?

ハッシュテーブルはTheta(1)時間で任意の要素を検索でき、要素を追加するのも同じくらい簡単です。


ハッシュテーブルの場合、find()、insert()、およびremove()の実行時間はどれくらいですか?theta(1)theta(1)とtheta(1)でしょ?
2010年

8
ほとんどの場合、はい。多くの衝突に遭遇した場合、それらの時間はO(n)まで成長する可能性があります。
クリスチャンマン

1
これらの時間は、ハッシュ関数にも依存します。なんらかの奇妙な理由でO(1)でない場合、明らかに、ハッシュ関数の実行効率の最小範囲は操作にあります。
クリスチャンマン

BSTの最大の利点は、ソートされたデータ構造であることです。すでにここにリストさている詳細なユースケース。
Yuantao 2015

回答:


93

バイナリ検索ツリー(参照ベース)はメモリ効率が良いことに注意してください。必要以上にメモリを予約することはありません。

たとえば、ハッシュ関数に範囲があるR(h) = 0...100場合、たとえ20要素をハッシュしている場合でも、100(points-to)要素の配列を割り当てる必要があります。バイナリ検索ツリーを使用して同じ情報を格納する場合は、必要なだけのスペースとリンクに関するメタデータを割り当てるだけです。


33
ハッシュ関数出力の全範囲が配列内に存在しなければならないというのは真実ではありません。ハッシュ値は、配列の長さで単純に変更して、配列を小さくすることができます。もちろん、追加される要素の最終的な数はわからない可能性があるため、ハッシュテーブルは必要以上の領域を割り当てる可能性があります。ただし、二分探索木は、同じ量以上のメモリを浪費する可能性があります。リンクされた実装は、要素ごとに少なくとも2つの追加のポインター(親ポインターを使用する場合は3つ)のスペースを必要とし、配列ベースのBSTは、ツリーの満たされていない部分のために大量のメモリを浪費する可能性があります。
Solaraeus

4
@Solaraeus:配列ベースのBSTはハッシュテーブルと比較するのに最適であり、ハッシュテーブルよりも無駄がありません。また、テーブル全体を再計算する場合と比較して、メモリコピーだけでBSTを拡張することもできます。
Guvante 2012

125

他の誰も指摘していない利点の1つは、バイナリ検索ツリーを使用すると、範囲検索を効率的に実行できることです。

私の考えを説明するために、私は極端なケースを作りたいと思います。キーが0〜5000のすべての要素を取得したいとします。実際には、そのような要素は1つだけで、キーが範囲内にない他の要素は10000だけです。BSTは、答えを得ることのできないサブツリーを検索しないため、範囲検索を非常に効率的に実行できます。

一方、ハッシュテーブルで範囲検索を実行するにはどうすればよいですか?O(n)であるすべてのバケットスペースを反復処理する必要があるか、または1,2,3,4 ... 5000までのそれぞれが存在するかどうかを調べる必要があります。(0から5000までのキーは無限セットですか?たとえば、キーは10進数にすることができます)


11
BSTは範囲検索を効率的に行います!私にとっては、これが実用的でアルゴリズム的なアプローチという点で最良の答えです。
13

4
これは、ツリーがデータベースに関連付けられている理由を本当に説明しています。これらの利点は、キーベースのフィルタリングを実行する必要がある場合に最も顕著です。ハッシュマップを使用すると、「1000から3290までのキーを持つすべてのアイテムを見つける」を解決するためにすべてのキーをループする必要があります
Dmitry

77

バイナリツリーの1つの「利点」は、すべての要素を順番にリストするためにトラバースできることです。これはハッシュテーブルでは不可能ではありませんが、通常の操作ではありません。


3
どの順序でトラバースして、ハッシュテーブルではおそらく意味がありません。
FrustratedWithFormsDesigner


リンクをありがとう、それは干渉する考えです!私はその実装を見たことも、使ったこともないと思います(少なくとも、知らないうちに)。
FrustratedWithFormsDesigner 2010年

1
記事のウェイバックマシンのリンク- web.archive.org/web/20100323091632/http://www.concentric.net/...
rahulroy9202

51

他のすべての良いコメントに加えて:

一般に、ハッシュテーブルは、バイナリツリーと比較して、より少ないメモリ読み取りを必要とするより良いキャッシュ動作を備えています。ハッシュテーブルの場合、通常は、データを保持している参照にアクセスする前に、1回の読み取りのみが発生します。バイナリツリーは、それがバランスのとれたバリアントである場合、定数kに対してk * lg(n)のメモリ読み取りのオーダーで何かを必要とします。

一方、敵がハッシュ関数を知っている場合、敵はハッシュテーブルを強制的に衝突させ、パフォーマンスを大幅に低下させる可能性があります。回避策は、ハッシュ関数をファミリからランダムに選択することですが、BSTにはこの欠点はありません。また、ハッシュテーブルのプレッシャーが大きくなりすぎると、ハッシュテーブルを拡大して再割り当てする傾向があり、コストのかかる操作になる可能性があります。ここではBSTの動作が単純であり、突然大量のデータを割り当ててリハッシュ操作を行う傾向はありません。

ツリーは、最終的な平均データ構造になる傾向があります。それらはリストとして機能し、並列処理のために簡単に分割でき、O(lg n)のオーダーで高速な削除、挿入、検索ができます。彼らは特に何もしませんが、過度に悪い振る舞いもしません。

最後に、BSTはハッシュテーブルと比較して(純粋な)関数型言語で実装する方がはるかに簡単であり、破壊的な更新を実装する必要はありません(上記のPascalによる永続化の引数)。


3
BSTs are much easier to implement in (pure) functional languages compared to hash-tables- 本当に?今、関数型言語を学びたい!
nawfal 14年

1
ハッシュテーブルは、関数型言語で永続化する必要があります。これはしばしば実装を複雑にします。
私は、クラップの答えを2014年

詳しく説明すると、大統領のデータ構造を関数型言語で作成した場合、実際に行うのはアセンブリで行うのと同じコードを書くことだけです。ただし、各操作では、メモリ/レジスターの配列を明示的に変換するか、サーバーに話しかけてふりをしますそれをするために。すべてはあなたの状態を認識していることを意味しますが、正しく行われれば命令型アプローチに同型です(実際の各変換で大量のデータを現実的にコピーすることはできず、チートする必要があります)。
ドミトリー

27

ハッシュテーブルに対するバイナリツリーの主な利点は、ハッシュツリーでは実行できない(簡単に、すばやく)2つの操作がバイナリツリーに追加されることです。

  • 任意のキー値に最も近い(必ずしも等しいとは限らない)要素を見つける(または、上または下に最も近い要素)

  • ソートされた順序でツリーのコンテンツを反復処理します

2つは接続されています。バイナリツリーはその内容をソートされた順序で保持するため、ソートされた順序を必要とするものは簡単に実行できます。


BSTは、完全一致が存在しない場合にのみ、最も近い一致を見つけます。ルート自体が完全に一致する場合はどうなりますか?
developer747

2
@ developer747:次に、次に上下に最も近いのは、左サブツリーの右端の葉と右サブツリーの左端の葉です。
Chris Dodd、2015

16

(バランスのとれた)バイナリ検索ツリーには、その漸近的な複雑さが実際には上限であるという利点もありますが、ハッシュテーブルの「一定」の時間は償却されます。不適切なハッシュ関数がある場合、最終的に線形時間に低下する可能性があります、定数ではなく。


3
この点を理解するために、縮退したケースは、コレクションに1つのキーのコピーが多数含まれている場合です。BSTでは、挿入はO(log n)、ハッシュテーブルでは、挿入はO(n)です
SingleNegationElimination

2
ハッシュテーブルに1つのキーのコピーが多数含まれている場合、挿入は(まだ)O(1)であり、O(n)ではありません。ハッシュテーブルの問題は、同じハッシュを持つ多くの異なるキーがある場合です。これは、衝突が多い場合に別のハッシュ関数に切り替える動的ハッシュ方式によって回避できます。
クリス・ドッド

不均衡なツリーはリストに退化し、O(n)ルックアップも持つ可能性があることに注意してください。
awiebe

9

ハッシュテーブルは、最初に作成されたときにより多くのスペースを必要とします-(まだ挿入されているかどうかにかかわらず)まだ挿入されていない要素用の利用可能なスロットがあり、バイナリ検索ツリーは必要なだけ大きくなりますあります。また、ハッシュテーブルにさらにスペースが必要な場合、別の構造への拡張に時間がかかる可能性がありますが、それは実装に依存する場合があります。


8

バイナリ検索ツリーは、新しいツリーが返されても古いツリーが存在し続ける永続的なインターフェイスで実装できます。注意深く実装すると、新旧のツリーはノードのほとんどを共有します。これを標準のハッシュテーブルで行うことはできません。


6

バイナリツリーは検索と挿入が低速ですが、中置走査の非常に優れた機能を備えています。つまり、ソートされた順序でツリーのノードを反復処理できるということです。

ハッシュテーブルのエントリを反復処理しても、すべてがメモリに分散しているため、あまり意味がありません。


6

コーディングインタビューの解読から、第6版

バランスのとれた二分探索木(BST)でハッシュテーブルを実装できます。これにより、O(log n)ルックアップ時間が得られます。これの利点は、大きな配列を割り当てないため、使用するスペースが少なくなる可能性があることです。キーを順番に繰り返し処理することもできます。


5

BSTは、O(logn)時間で "findPredecessor"および "findSuccessor"操作(次に小さい要素と次に大きい要素を見つける)も提供します。これも非常に便利な操作です。ハッシュテーブルはその時間効率を提供できません。


「findPredecessor」および「findSuccessor」操作を探している場合、HashTableはそもそもデータ構造にとって不適切な選択です。
AKDesai 2017年

1

ソートされた方法でデータにアクセスする場合は、ソートされたリストをハッシュテーブルと並行して維持する必要があります。良い例は、.Netの辞書です。(http://msdn.microsoft.com/en-us/library/3fcwy8h6.aspxを参照してください)。

これには、挿入が遅くなるだけでなく、Bツリーよりも多くのメモリが消費されるという副作用があります。

さらに、bツリーがソートされるため、結果の範囲を見つけたり、ユニオンやマージを実行したりするのは簡単です。


1

また、用途によって異なりますが、ハッシュを使用すると、完全一致を見つけることができます。範囲を照会する場合は、BSTが最適です。大量のデータe1、e2、e3 ..... enがあるとします。

ハッシュテーブルを使用すると、一定の時間内に任意の要素を見つけることができます。

e41より大きくe8より小さい範囲の値を検索する場合、BSTはそれをすばやく見つけることができます。

重要なのは、衝突を回避するために使用されるハッシュ関数です。もちろん、衝突を完全に回避することはできません。その場合は、チェーンまたはその他の方法を使用します。これにより、最悪の場合、検索が一定の時間ではなくなります。

満杯になると、ハッシュテーブルはバケットサイズを増やし、すべての要素を再度コピーする必要があります。これは、BSTにはない追加のコストです。


1

ハッシュテーブルはインデックス作成には適していません。範囲を検索する場合は、BSTの方が適しています。これが、ほとんどのデータベースインデックスがハッシュテーブルではなくB +ツリーを使用する理由です。


データベースのインデックスは、ハッシュとB +ツリーの両方のタイプです。より大きいまたはより小さいなどの比較を行う場合は、B +ツリーインデックスが役立ちます。それ以外の場合は、ハッシュインデックスがルックアップに役立ちます。また、データが比較できない場合や、インデックスを作成する場合、dbはB +ツリーインデックスではなくハッシュインデックスを作成することも考えてください。@ssD
Sukhmeet Singh

1

バイナリ検索ツリーは、キーにいくつかの合計順序(キーは比較可能)が定義されていて、順序情報を保持したい場合、辞書を実装するのに適しています。

BSTは注文情報を保持するため、ハッシュテーブルを使用して(効率的に)実行できない4つの追加の動的セット操作を提供します。これらの操作は次のとおりです。

  1. 最大
  2. 最小
  3. 後継
  4. 前任者

すべてのBST操作と同様に、これらすべての操作はO(H)の時間の複雑さを持っています。さらに、格納されているすべてのキーはBSTでソートされたままなので、ツリーを順番にたどるだけで、ソートされたキーのシーケンスを取得できます。

要約すると、挿入、削除、削除の操作だけが必要な場合、ハッシュテーブルは(ほとんどの場合)パフォーマンスにおいて無敵です。ただし、上記の操作の一部またはすべてが必要な場合は、BST、できれば自己バランスBSTを使用する必要があります。


0

ハッシュテーブルの主な利点は、〜= O(1)のほぼすべての操作を実行できることです。そして、その理解と実装は非常に簡単です。多くの「面接問題」を効率的に解決します。だからあなたがコーディングのインタビューを解読したいのなら、ハッシュテーブルで親友を作ってください;-)


OPはハッシュよりもBSTの利点を求めたと思います。
スナイパー

0

ハッシュマップは、セット連想配列です。したがって、入力値の配列はバケットにプールされます。オープンアドレッシングスキームでは、バケットへのポインターがあり、バケットに新しい値を追加するたびに、バケットのどこに空きスペースがあるかがわかります。これを行うにはいくつかの方法があります。バケットの最初から開始し、毎回ポインタをインクリメントして、それが占有されているかどうかをテストします。これは線形プローブと呼ばれます。次に、addのようなバイナリ検索を実行できます。ここでは、バケットの先頭と、空きスペースを検索するたびに2倍または2倍の差を2倍にします。これは二次プロービングと呼ばれます。OK。これらの両方の方法の問題は、バケットが次のバケットアドレスにオーバーフローした場合に、

  1. 各バケットサイズを2倍にします-malloc(Nバケット)/ハッシュ関数を変更します-所要時間:mallocの実装によって異なります
  2. 以前のバケットデータをそれぞれ新しいバケットデータに転送/コピーします。これは、Nがデータ全体を表すO(N)操作です。

OK。しかし、もしあなたがリンクリストを使用するなら、そのような問題はないはずですよね?はい、リンクリストではこの問題は発生しません。各バケットがリンクリストで始まることを考慮し、バケットに100要素がある場合、リンクリストの最後に到達するためにそれらの100要素をトラバースする必要があるため、List.add(Element E)には時間がかかります

  1. 要素をバケットにハッシュする-すべての実装と同様に通常
  2. 上記のバケットO(N)操作の最後の要素を見つけるのに時間をかけてください。

linkedlist実装の利点は、オープンアドレス指定実装の場合のように、メモリ割り当て操作とすべてのバケットのO(N)転送/コピーが不要なことです。

したがって、O(N)操作を最小限に抑える方法は、実装をバイナリ検索ツリーの実装に変換することです。ここで、検索操作はO(log(N))であり、その値に基づいて要素をその位置に追加します。BSTの追加機能は、ソートされていることです。


0

バイナリ検索ツリーは、文字列キーと一緒に使用すると高速にできます。特に弦が長い場合。

文字列に対して高速である(等しいでない場合)less / greaterの比較を使用するバイナリ検索ツリー。したがって、BSTは文字列が見つからない場合にすばやく応答できます。それが見つかると、完全な比較を1つだけ実行する必要があります。

ハッシュテーブル。文字列のハッシュを計算する必要があります。これは、ハッシュを計算するために、少なくとも一度はすべてのバイトを調べる必要があることを意味します。次に、一致するエントリが見つかった場合。

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