配列を使用してハッシュテーブルを実装する場合、配列の一定時間のインデックスを継承します。 O(logn)を使用した検索を提供するため、バイナリ検索ツリーを使用してハッシュテーブルを実装する理由は何ですか?二分探索木を直接使用しないのはなぜですか?
要素に 全順序 がない場合(つまり、「より大きい」と「より小さい」がすべてのペアに対して定義されていないか、要素間で一貫性がない場合)、比較できません。すべてのペア、したがってBSTを直接使用することはできませんが、ハッシュ値でBSTにインデックスを付けることを妨げるものは何もありません-これは整数値であるため、明らかに全順序があります(ただし、それでも必要です 衝突を解決する 、つまり同じハッシュ値を持つ要素を処理する方法があります)。
ただし、ハッシュテーブルに対するBSTの最大の利点の1つは、要素が順番に並んでいるという事実です。ハッシュ値で並べ替えると、要素の順序は任意になります代わりに、この利点は適用されなくなります。
配列の代わりにBSTを使用してハッシュテーブルを実装することを検討する理由については、次のようになります。
配列のサイズを変更する必要があるという欠点はありません-配列の場合、通常、ハッシュ値を配列サイズで変更し、配列が取得された場合はサイズを変更しますフル、すべての要素を再挿入しますが、BSTを使用すると、変更されていないハッシュ値をBSTに直接挿入できます。
これは、個々の操作に一定の時間がかからないようにし(配列のサイズを変更する必要がある場合に非常によく発生する可能性があります)、全体的なパフォーマンスが二次的なものである場合に関連する可能性がありますが、解決するためのより良い方法がある可能性がありますこの問題。
配列サイズを変更しないため、ハッシュの衝突のリスクが軽減されます。したがって、可能なハッシュの数が大幅に増える可能性があります。これにより、ハッシュテーブルの最悪の場合のパフォーマンス(要素のかなりの部分が同じ値にハッシュされる場合)が発生するリスクが軽減されます。
実際の最悪の場合のパフォーマンスは、衝突をどのように解決するかによって異なります。これは通常、O(n)最悪の場合のパフォーマンスのリンクリストを使用して行われます。ただし、BSTを使用してO(log n)パフォーマンスを達成することもできます( Javaのハッシュで行われるように)テーブルの実装 ハッシュを持つ要素の数がしきい値を超えている場合)-つまり、各要素がすべての要素が同じハッシュ値を持つBSTを指すハッシュテーブル配列を作成します。
おそらくより少ないメモリを使用します-配列では必然的にいくつかの空のインデックスがありますが、BSTではこれらは単に存在する必要はありません。これは明確な利点ではありませんが、それがまったく利点である場合。
あまり一般的ではない 配列ベースのBST実装 を使用すると仮定すると、この配列にもいくつかの空のインデックスがあり、これには時々サイズ変更も必要になりますが、これは単にメモリコピーであり、更新されたハッシュですべての要素を再挿入します。
典型的なポインターベースのBST実装を使用する場合、ポインターの追加コストは、配列内にいくつかの空のインデックスを持つコストを上回るように見えます(配列が特にスパースである場合を除きます。これはハッシュテーブルの悪い兆候になる傾向があります)。とにかく)。
しかし、これが行われていることを個人的に聞いたことがないので、おそらく、期待されるO(1)からO(log n)への運用コストの増加に見合うだけのメリットはありません。
通常、選択は実際にBSTを直接使用する(ハッシュ値なし)か、ハッシュテーブルを使用する(配列あり)かのどちらかです。
長所:
短所:
ハッシュテーブルの要件はO(1)ルックアップであるため、対数ルックアップ時間があれば、ハッシュテーブルではありません。衝突はアレイ実装の問題であるため、許可されています。 可能性が高い問題)、BSTを使用すると、その点でメリットが得られる可能性がありますが、一般的にはトレードオフの価値はありません-保証されたくない状況は考えられませんO(1)ハッシュテーブルを使用する場合のルックアップ時間。
あるいは、配列内の各インデックスがBST内の対応するノードへの参照を持つ、BSTバリアントを介した対数挿入と削除を保証する基礎となる構造の可能性があります。そのような構造は、ある種複雑になる可能性がありますが、O(1)ルックアップとO(logn)挿入/削除を保証します。
私はこれが誰かがそれをしたかどうかを見るために探しているのを見つけました。多分そうではないと思います。
私は今朝、インデックスによって格納された行で構成される配列としてバイナリツリーを実装するというアイデアを思いつきました。行1には1があり、行2には2があり、行3には4があります(はい、2の累乗)。この構造の利点はビットシフトであり、双方向または単方向の参照を格納するために追加のメモリを使用する代わりに、加算または減算を使用してツリーをウォークできます。
これにより、ある種のハッシュ可能な入力に基づいてハッシュ値をすばやく検索し、その値が他のストアに存在するかどうかを検出できます。または、ハッシュ衝突(または部分衝突)検索の場合。他の多くの用途は考えられませんが、これらの用途では驚くほど高速です。多くのローテーション操作は完全にCPUキャッシュで行われ、ニースの線形ブロブでメインメモリに書き出される可能性が非常に高いです。
その主なユーティリティは、ランダムな性質の入力値をソートすることです。配列内のblobがハッシュと別のストアの識別子のように2つの部分である場合、比較を非常に高速に実行し、非常に高速に挿入して、ハッシュ値を持つアイテムが別の場所(UUIDなど)に保持されている場所を見つけることができます。ファイルシステムノード、あるいはファイル名、あるいは他の短い識別可能な文字列の)。
他の使い方を夢見るのは他の人に任せますが、カッコウサイクルの変形の部分的な衝突を特定するためのグラフ理論的なプルーフオブワーク検索テーブルに使用しています。
私はちょうど今ウォークフォーミュラに取り組んでいます、そしてここにそれがあります:
i =配列要素のインデックス
ウォークアップ(親に移動):
i>>1-(i+1)%2
(明らかに、iがゼロかどうかをテストする必要があります)
左に歩く(下と左):
i<<1+2
(これと次も構造の深さ2 ^に対してテストする必要があるため、エッジから外れてルートにフォールバックすることはありません)
右に歩く(右下):
i<<1+1
ご覧のとおり、各ウォークはインデックスに基づく短い式です。左右に移動するためのビットシフトと加算、および昇順のためのビットシフトと加算とモジュラス。下に移動する2つの命令、上に移動する4つの命令(アセンブラーで、または上記のCおよびその他のHLL演算子表記で)
編集:さらなる解説から、挿入時間を短縮することの利点は間違いなく有益であることがわかります。しかし、従来のベクトルベースのバイナリツリーが高密度バージョンほどのメリットをもたらすとは思いません。すべてのノードが連続した配列にある高密度バージョンは、検索されると、当然、メモリ内を直線的に移動します。これはshouldキャッシュミスを減らし、検索の待ち時間を大幅に短縮するのに役立ちます。また、ブロックを順番にストリーミングする場合と比較して、ランダムにアクセスする際にメモリに待ち時間が発生するという事実もあります。
https://github.com/calibrae-project/bast/blob/master/pkg/bast/bast.go
これは、私が分岐配列探索木と呼んでいるものを実装するためのWiPの現在の状態です。ソートされたハッシュのコレクションをすばやく挿入/削除し、検索をひどく遅くしないために、これは、構造を行き来する大量のデータがある場合、またはそれ以上の場合に非常に大きなメリットになると思います。ポイントは、よりリアルタイムのアプリケーションに有益です。