web-dev-qa-db-ja.com

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

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

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

91
Devoted

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

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

84
Christian Mann

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

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

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

113
Alex

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

75
NealB

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

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

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

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

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

51

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

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

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

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

26
Chris Dodd

(バランスの取れた)バイナリ検索ツリーには、その漸近的な複雑さが実際には上限であるという利点がありますが、ハッシュテーブルの「一定の」時間は償却時間です。 、定数ではなく。

16
jamesnvc

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

バイナリ検索ツリーはpersistentインターフェイスで実装できます。この場合、新しいツリーが返されますが、古いツリーは引き続き存在します。慎重に実装すると、古いツリーと新しいツリーはほとんどのノードを共有します。標準のハッシュテーブルではこれを行えません。

8
Pascal Cuoq

バイナリツリーは検索と挿入に時間がかかりますが、基本的にはツリーのノードをソートされた順序で反復処理できることを意味する、中置走査の非常に素晴らしい機能があります。

ハッシュテーブルのエントリを繰り返し処理することは、メモリに散在しているため、あまり意味がありません。

6

コーディングインタビューのクラック、第6版

平衡二分探索木(BST)を使用してハッシュテーブルを実装できます。これにより、O(log n)ルックアップ時間が得られます。これの利点は、大きな配列を割り当てなくなったため、潜在的に使用するスペースが少なくなることです。キーを順番に繰り返すこともできます。これは便利な場合があります。

4
Guy Kahlon

BSTは、O(logn) time)で「findPredecessor」および「findSuccessor」操作(次の最小要素および次の最大要素を見つけるため)も提供します。これは非常に便利な操作です。その時間の効率を提供しません。

4
Balaji

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

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

さらに、Bツリーはソートされているため、結果の範囲を見つけたり、結合やマージを実行したりするのは簡単です。

1
IamIC

また、使用法にも依存します。Hashは完全一致を見つけることができます。範囲を照会する場合は、BSTが選択です。多数のデータe1、e2、e3 ..... enがあるとします。

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

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

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

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

1
sreeprasad

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

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

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

すべてのBST操作のようなこれらすべての操作には、O(H)の時間の複雑さがあります。さらに、保存されているすべてのキーはBSTでソートされたままなので、ツリーを順番に走査するだけでキーのソートされたシーケンスを取得できます。

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

0
mightyWOZ

バイナリ検索ツリーは、文字列キーと共に使用すると高速になります。特に文字列が長い場合。

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

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

0
Calmarius

クラス HashSet および Table は、順序付けられていないコレクションです。インターフェイスからは明らかではありません(そうでない場合もあります)が、ハッシュテーブルはAVLツリーを使用して実装されています。これは、ハッシュコードが配列のモジュロ(コリジョンの減少)によって削減されないことを意味します。また、実行される配列の再ハッシュがないことも意味します(よりスムーズなパフォーマンス)。それらが順序付けられていないコレクションであるということは、equals関数とhashCode関数のみを提供することを意味します-ツリーの完全な比較ではありません。したがって、ハッシュテーブルTable <K、T>を使用するか、バイナリツリーTree <K、T>を使用するかは、クラスKに依存します-完全に比較可能か、同等のみに比較可能か。

文字列のように、データ型が同等であり同等である場合があります。つまり、HashSet <String>とSet <String>の両方が可能です。文字列のハッシュセットでの検索は、順序付けされた文字列のセットでの検索よりも約10倍高速になる傾向があります。コンパレータが高価な場合、ツリーはHashTablesと比較して遅くなります。コンパレータが高速の場合(整数や浮動小数点数など)、ハッシュテーブルよりもツリーの実行速度が速くなります。

0

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

0
ssD

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

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

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

  1. 要素をバケットにハッシュする-すべての実装のように通常
  2. バケットO(N)操作の最後の要素を見つけるのに時間がかかります。

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

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

0