web-dev-qa-db-ja.com

ハッシュテーブル-配列よりも高速なのはなぜですか?

各要素にキーがあり、要素の配列へのインデックスがわからない場合、ハッシュテーブルは配列よりも優れたパフォーマンスを発揮します(O(1)vs O(n))。

何故ですか?つまり、キーがあり、それをハッシュします。ハッシュを持っています。アルゴリズムは、このハッシュをすべての要素のハッシュと比較するべきではありませんか?記憶の性質の背後にあるトリックがあると思いますよね?

44
Johnny Pauling

各要素にキーがあり、要素の配列へのインデックスがわからない場合、ハッシュテーブルは配列よりも優れたパフォーマンスを発揮します(O(1)vs O(n))。

ハッシュテーブル検索は、平均ケースでO(1)を実行します。最悪の場合、ハッシュテーブル検索はO(n)を実行します。衝突があり、ハッシュ関数が常に同じを返す場合「これは遠隔の状況です」と考える人もいるかもしれませんが、適切な分析ではそれを考慮する必要があります。この場合、配列やリンクリスト(O(n))などのすべての要素を反復処理する必要があります。

何故ですか?つまり、キーがあり、それをハッシュします。ハッシュを持っています。アルゴリズムは、このハッシュをすべての要素のハッシュと比較するべきではありませんか?記憶の性質の背後にあるトリックがあると思いますよね?

キーがあり、ハッシュします。ハッシュがあります:要素が存在するハッシュテーブルのインデックス(以前に配置されていた場合)。この時点で、O(1)のハッシュテーブルレコードにアクセスできます。負荷係数が小さい場合、そこに複数の要素が表示されることはほとんどありません。したがって、最初に表示される要素は、探している要素でなければなりません。それ以外の場合、複数の要素がある場合、その位置にある要素と探している要素を比較する必要があります。この場合、O(1) + O(number_of_elements)があります。

平均的なケースでは、ハッシュテーブルの検索の複雑さはO(1) + O(load_factor)= O(1 + load_factor)です。

最悪の場合、load_factor = nを思い出してください。そのため、最悪の場合、検索の複雑度はO(n)です。

「記憶の性質の裏技」とはどういう意味かわかりません。いくつかの観点では、ハッシュテーブル(構造とチェーンによる衝突解決)は「スマートトリック」と見なすことができます。

もちろん、ハッシュテーブルの分析結果は数学で証明できます。

84
bitfox

配列あり:値がわかっている場合は、その場所を見つけるために(ソートされていない限り)平均して値の半分で検索する必要があります。

ハッシュ付き:値に基づいて場所が生成されます。したがって、その値を再度指定すると、挿入時に計算したのと同じハッシュを計算できます。複数の値が同じハッシュになる場合があるため、実際には、各「場所」自体が、その場所にハッシュするすべての値の配列(またはリンクリスト)です。この場合、この非常に小さい(悪いハッシュでない限り)配列のみを検索する必要があります。

33
Andy

ハッシュテーブルはもう少し複雑です。ハッシュ値に基づいて、異なるbucketsに要素を配置します。理想的な状況では、各バケットに保持されるアイテムは非常に少なく、空のバケットはあまり多くありません。

キーを知ったら、ハッシュを計算します。ハッシュに基づいて、どのバケットを探すべきかがわかります。また、前述のように、各バケット内のアイテムの数は比較的少なくする必要があります。

ハッシュテーブルは内部で多くの魔法を使って、空のバケツにあまり多くのメモリを消費しないように、バケツができる限り小さくなるようにします。また、キーの質に大きく依存します->ハッシュ関数。

ウィキペディアは ハッシュテーブルの非常に包括的な説明 を提供しています。

20

ハッシュテーブルは、ハッシュ内のすべての要素を比較する必要はありません。キーに従ってハッシュコードを計算します。たとえば、キーが4の場合、ハッシュコードは-4 * x * yになります。これで、ポインターは選択する要素を正確に認識します。

一方、配列であった場合、配列全体を走査してこの要素を検索する必要があります。

6
Sohil Jain

なぜ、[ハッシュテーブルはキーによるルックアップを配列よりも実行するのですか(O(1)vs O(n))]?つまり、キーがあり、それをハッシュします。ハッシュを持っています。アルゴリズムは、このハッシュをすべての要素のハッシュと比較するべきではありませんか?記憶の性質の背後にあるトリックがあると思いますよね?

ハッシュを取得すると、バケットの配列内の「理想的な」場所または予想される場所を計算できます。

理想的なバケット=ハッシュ%num_buckets

問題は、別の値がそのバケットに既にハッシュされている可能性があることです。この場合、ハッシュテーブルの実装には2つの主な選択肢があります。

1)別のバケットを試す

2)バケットに値のリンクされたリストへのポインタを保持させることにより、いくつかの異なる値を1つのバケットに「属する」ようにする

実装1の場合、 open addressingまたはclosed hashing 、あなたは他のバケットを飛び回る:あなたの価値を見つけたら、素晴らしい;使用されていないバケットが見つかった場合、挿入するとそこに値を保存できます。または、検索しても値が見つからないことがわかります。代替バケットをトラバースする方法が同じバケットを複数回検索することになった場合、たとえば、 を使用した場合、O(n)よりも検索がさらに悪化する可能性がありますquadratic probing 理想的なバケットインデックス+1、次に+4、次に+9、次に+16などを試します-ただし、 % num_bucketsなどを使用して範囲外のバケットアクセスを回避するため、たとえば12個のバケットがある場合、ideal + 4とideal + 16は同じバケットを検索します。いつ放棄するかを知るのも難しい場合があります:実装は楽観的であり、常に値または未使用のバケット(永遠に回転するリスク)を見つけると仮定することができ、カウンターを持ち、試行のしきい値後に放棄するか線形のバケットごとの検索を開始します。

実装2の場合、 closed addressingまたはseparate chaining 、理想的なバケットにすべてハッシュされた値のコンテナ/データ構造内を検索する必要があります。これがどれだけ効率的かは、使用するコンテナの種類によって異なります。一般に、1つのバケットで衝突する要素の数は少ないと予想されます。これは、非敵対的入力を使用した良好なハッシュ関数に当てはまり、通常、特に素数のバケットの平凡なハッシュ関数にも当てはまります。したがって、O(n)検索プロパティにもかかわらず、リンクリストまたは連続配列がよく使用されます。リンクリストは実装および操作が簡単で、配列はメモリキャッシュを改善するためにデータをまとめてパックします。ローカリティとアクセス速度。しかし、最悪のケースは、テーブル内のすべての値が同じバケットにハッシュされ、そのバケットのコンテナがすべての値を保持することです。ハッシュテーブル全体はバケットのコンテナ。一部のJava同じバケットにハッシュする要素の数がしきい値を超える場合、ハッシュテーブルの実装はバイナリツリーの使用を開始しました。複雑さがO(log2n)より悪くならないようにします。

Pythonハッシュは、1 =オープンアドレッシング=クローズハッシュの例です。 C++ std::unordered_set は、クローズドアドレス指定=個別チェーンの例です。

2
Tony Delroy