ハッシュテーブルの値はどのように物理的にメモリに格納されますか?


7

質問:

効率的に使用され、値を頻繁に再配置する必要がない場合に、ハッシュテーブルの値はどのようにメモリに格納されますか?

私の現在の理解(間違っている可能性があります):

ハッシュテーブルに3つのオブジェクトが格納されているとしましょう。それらのハッシュ関数はこれらの値を生成します:

  • 0
  • 10
  • 20

これらのオブジェクトのポインタは、次のメモリアドレスに格納されません。これらのオブジェクト間には大きなギャップがあるためです。

  • startOfHashTable + 0
  • startOfHashTable + 10
  • startOfHashTable + 20

ハッシュテーブル上のWikipediaの記事は、「インデックス」とは、のように計算されていることを述べています:

hash = hashfunc(key)
index = hash % array_size 

したがって、私の例では、インデックスは次のようになります。

  • 0%3 = 0
  • 10%3 = 1
  • 20%3 = 2

これにより、前述の大きなギャップが解消されます。このモジュロスキームを使用しても、ハッシュテーブルにさらにオブジェクトを追加すると問題が発生します。ハッシュテーブルに4番目のオブジェクトを追加すると、インデックスを取得するために%4を適用する必要があります。これで、過去に行った%3はすべて無効になりませんか?以前の%3のすべてを%4の場所に再配置する必要がありますか?

回答:


15

ハッシュテーブルのエントリは配列に格納されます。ただし、ハッシュ値へのモジュロ演算子の適用を誤解しています。ハッシュテーブルがサイズの配列に格納されている場合 nの場合、ハッシュ関数はモジュロで計算されます n、現在テーブルに保存されているアイテムの数に関係なく。したがって、この例では、サイズ6の配列にアイテムを格納する場合、ハッシュ値0、10、20の3つのアイテムは、それぞれ場所0、4、2に格納されます。たとえばハッシュ値が31である4番目の要素を追加した場合、最初の3つのアイテムを移動する必要なく、ロケーション1に格納されます。ハッシュテーブルがいっぱいになり、それをより大きな配列に移動したい場合は、テーブル内のすべての項目の場所を再計算し、適切に移動する必要があります。


1
つまり、ハッシュテーブルは予想される潜在的なサイズで作成され、サイズを増やす必要がある場合にのみアイテムが再配置されるということです...したがって、ハッシュ関数の分布が均一であるかどうかは問題ではありません。たとえば、0、5、および10のハッシュ値は均一に分散されますが、潜在的なサイズ5のハッシュテーブルに挿入されると、すべてがバケット0で衝突します。hash % table sizeハッシュではなく、均一に分散されると言う方が良いでしょう。自体。
Pwner

@Pwnerはい、そうです。
David Richerby、2015

1
hash % tableSizetableSizeが変化する可能性がある場合、どのように均一に分散して作成することができますか?テーブルサイズが20である場合、0,5、および10のハッシュ値には衝突をテーブルのサイズが5である場合、多くの衝突を作成しないが、持っている
Pwner

1
@Pwnerその場合、ハッシュテーブルは一定の時間の操作しか期待していないことに注意してください。ただし、ハッシュ関数が(ほぼ)均一である場合に限ります。
ラファエル

1
@Pwner分布は文字通り均一ではありませんが、均一に近いものを目指します。
David Richerby、2015

7

ハッシュテーブルは通常、スペースを無駄にします。時間と空間のトレードオフが一般的であるため、多くのアルゴリズムが実行しますが、通常はより適切に非表示になります:)。他のアルゴリズムと同様に、ハッシュテーブルは、より良い時間パフォーマンスを得るためにそれを行います。

最初のポイントは、ハッシュテーブルでの衝突を回避しようとすることです。これにより、アクセス時間のコストが一定に保たれます(ただし、衝突は通常許可され、処理できるため、時間をかけて複数のアイテムを同じエントリに入れることができます。 )。2番目のポイントは、メモリを消費するため、未使用の大きなギャップを回避しようとすることです。3番目のポイントは、ハッシュ関数(したがってテーブルサイズ)の変更を回避することです。これは、テーブル全体を再編成する必要があるため、時間コストが大きくなります。

残念ながら、ギャップが少ないほど、新しいハッシュエントリが衝突を引き起こす可能性が高くなります。特定のデータセットに対して適切なハッシュ関数を使用すると、使用可能なインデックススペースをより有効に使用しても、衝突の可能性が制限されます。

実際には、静的なものと動的なものの2種類のハッシュテーブルがあることを考慮する必要があり ます。

静的なものの場合、ハッシュされるデータは変更されないため、そのデータセットに対してまったく衝突のないハッシュ関数を見つけることができます。これは完全ハッシュと呼ばれます。しかし、最善の方法は、ギャップのない結果を実現する最小限の完全ハッシュです。

ただし、ハッシュするデータが動的に変化する場合、可能性の大きなセット内では、これは実現できません。次に、衝突を回避することはできませんが、十分なギャップを設けて衝突を制限しようとします。

これをさまざまに管理するためのさまざまな手法があり、テーブルサイズをハッシュされる値の数に適合させ、衝突が多い場合はテーブルを大きくし、ギャップが大きすぎる場合はテーブルを減らします。ただし、ハッシュテーブルを使用する全体のコストに対するテーブルの再編成の影響を制限するために、指数テーブルのバリエーションを使用して、これは非常に注意深く処理する必要があります。

これは、直感的な導入を目的としています。技術的な詳細と参照については、この質問への回答をご覧ください。(いつ)ハッシュテーブルルックアップはO(1)ですか?。ハッシュテーブルとハッシュは、多くのバリエーションがある重要なトピックです。


3

ハッシュテーブルを確認する適切な方法は、インデックス範囲が無限のルックアップテーブルのようなものです(実際には無限ではなく、使用しているキーの値の制限によって制約されています)。

Xが整数であるルックアップテーブルにsqrt(x)の特定の値を格納しようとしているとしましょう。次のようになります。

[1] = 1
[3] = 1.732
[10000] = 100

高価な計算の代わりに、配列から値をフェッチするだけなので、これは非常に安価な平方根になります。ただし、[2]と[4-9999]が空であるため、メモリの使用は非常に非効率的です。

救いに来るのはハッシュ関数です。このコンテキストでのハッシュ関数の目的は、インデックスを適切なサイズの配列に実際に適合するものに変換することです。たとえば、次のようにすることができます。

(1) = [5] = 1
(3) = [2] = 1.732
(10000) = [3] = 100

3つの値すべてがサイズ6の配列に収まるようになりました。

ハッシュ関数はこれをどのように実現しますか?最も基本的なハッシュ関数は(Index%ArraySize)です。モジュロ演算子は、選択したインデックスを配列のサイズで割り、剰余を常に配列サイズよりも小さくします。

しかし、複数のインデックスが同じ結果にハッシュするとどうなりますか?これはハッシュ衝突と呼ばれ、それに対処するさまざまな方法があります。最も単純なのは、各値を元のインデックスと一緒に配列に格納することです。その配列スロットが取得された場合は、空のスロットが見つかるまで1ずつ進みます。値を取得するときは、ハッシュ関数で指定された場所に移動し、適切な元のインデックスを持つ要素が見つかるまで要素をループします。

これが優れたハッシュ関数がデータの分散にも優れている理由であり、着信するインデックスがシーケンシャルでもランダムでも、データへのアクセスのコストを比較的一定に保つために、ハッシュ結果をできるだけ広く分散させる必要があります。

もちろん、基礎となる配列が大きければ大きいほど、衝突が少なくなるので、速度とサイズ効率の間のトレードオフになります。最新のハッシュテーブルは通常、アクセスあたりの衝突が10未満である間に最大70%まで満たされます。ハッシュ関数に加えて、これは各データフェッチのコストが最大20サイクルであることを意味します。これは(目的によっては)速度(ルックアップテーブル)と効率(リスト)の間の適切な妥協点です。

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