1

私のプログラムは、文字列 ID で参照したい要素の限定された完全なリストを取得します。Dictionary<string, MyClass>これらの要素を格納するために .Net を使用しています。個人的には、要素がいくつあるのかわかりません。それはいくつかかもしれません。それは数千かもしれません。

プログラムがハッシュテーブルに入れる要素の数を正確に知っている場合、テーブルの容量として何を指定する必要がありますか。明らかに、少なくとも含まれる要素の数である必要がありますが、その数だけを使用すると、多数の衝突が発生する可能性があります。

ハッシュの衝突とメモリの浪費のバランスを取るために、既知の要素数に対してハッシュ テーブルの容量を選択するためのガイドはありますか?

編集:ハッシュ テーブルのサイズが変更される可能性があることは承知しています。私が何よりも避けているのは、デフォルトの割り当てのままにし、すぐに何千もの要素を追加して無数のサイズ変更操作を引き起こすことです。要素が入力されたら、要素を追加または削除しません。何が入っているかがわかれば、事前に十分なスペースを確保できます。私の質問は、ハッシュの衝突とメモリの浪費のバランスに関するものです。

4

3 に答える 3

3

あなたの質問は、辞書の容量が固定されているという誤った仮定を暗示しているようです。そうではありません。

ディクショナリが少なくともいくつかの要素を保持することがわかっている場合は、その数をディクショナリの初期容量として指定できます。ディクショナリの容量は常に、少なくともその項目数と同じ大きさです (これは少なくとも .NET 2 から 4 に当てはまります。これは文書化されていない実装の詳細であり、変更される可能性があると思います)。

初期容量を指定すると、ディクショナリがデフォルトの初期容量から選択した容量に拡大するときに発生するメモリ割り当てがなくなるため、メモリ割り当ての数が減ります。

使用中のハッシュ関数が適切に選択されている場合、衝突の数は比較的少なく、パフォーマンスへの影響は最小限に抑えられます。過度に大きな容量を指定すると、不自然な状況で役立つ場合がありますが、辞書のルックアップがパフォーマンスに大きな影響を与えていることがプロファイリングによって示されない限り、これについてはまったく考えません。

(不自然な状況の例としてint、すべてのキーが 10007 の倍数である、容量が 10007 のキーを持つ辞書を考えてみましょう。現在の実装では、すべての項目が 1 つのバケットに格納されます。ハッシュコードを容量で割り、余りを取ることによって選択されます. この場合、辞書はリンクされたリストとして機能し、別の容量を使用するように強制することでそれを修正します.)

于 2012-07-04T05:20:25.193 に答える
2

これは少し主観的な質問ですが、これに答えるために最善を尽くします (CLR 4.0 の辞書に変更があったかどうかはまだ調査していないため、CLR 2.0 の観点からのみ)。

文字列をキーにした辞書を使用しています。考えられる文字列は無限に存在する可能性があるため、考えられるすべてのハッシュ コードは「可能性が等しい」と仮定するのが合理的です。言い換えれば、2^32 ハッシュ コード (int の範囲) のそれぞれが、文字列クラスに対して同様に可能性が高いということです。BCL の Dictionary の現在のバージョンは、このようにして取得した 32 ビット ハッシュ コードから 32 番目のビットを削除し、本質的に 31 ビット ハッシュ コードを取得します。したがって、私たちが扱っている範囲は、2^31 個の一意の可能性が等しいハッシュ コードです。

ハッシュ コードの範囲は、辞書に含まれる、または含まれる可能性のある要素の数に依存しないことに注意してください。

辞書クラスは、このハッシュ コードを使用してバケットを「Myclass」オブジェクトに割り当てます。したがって、基本的に、2 つの異なる文字列が同じ 31 ビットのハッシュ コードを返す場合 (BCL 設計者が文字列ハッシュ関数を非常に賢明に選択したと仮定すると、そのようなインスタンスはかなり分散するはずです)、両方に同じバケットが割り当てられます。このようなハッシュ衝突では、何もできません。

さて、Dictionary クラスの現在の実装では、異なるハッシュ コード (これも 31 ビット) でさえ、同じバケットで終わる可能性があります。バケット インデックスは次のように識別されます。

hash = <31 bit hash code>
pr = <least prime number greater than or equal to current dictionary capacity>
bucket_index = hash modulus pr

したがって、フォーム (pr*factor + bucket_index) のすべてのハッシュ コードは、factor 部分に関係なく、同じバケットになります。

可能性のあるすべての 31 ビット ハッシュ コードが最終的に異なるバケットになることを確実にしたい場合、唯一の方法は、pr を可能な限り最大の 31 ビット ハッシュ コード以上にすることです。つまり、すべてのハッシュ コードが (pr*0 + hash_code) の形式であることを確認してください。つまり、pr は 2^31 よりも大きくなければなりません。これは拡張により、辞書の容量が少なくとも 2^31 であることを意味します。

ハッシュの衝突を最小限に抑えるために必要な容量は、ディクショナリに格納する要素の数にはまったく依存せず、可能なハッシュ コードの範囲に依存することに注意してください。

ご想像のとおり、2^31 は非常に大きなメモリ割り当てです。実際、容量として 2^31 を指定しようとすると、長さ 2^31 の配列が 2 つになります。32 ビット マシンでは、RAM の可能な最大アドレスは 2^32 であることを考慮してください!!!!!

何らかの理由で、ディクショナリのデフォルトの動作が受け入れられず、ハッシュの衝突 (または、バケットの衝突と言うべきです) を最小限に抑えることが重要な場合は、独自のハッシュ コードを提供することだけを望みます (つまり、文字列をキーとして使用しないでください)。このようなハッシュ コードでは、バケット インデックスを取得するための式を念頭に置いて、可能なハッシュ コードの範囲を最小限に抑えるように努める必要があります。最も簡単な方法は、番号/インデックスを一意の MyClass インスタンスに段階的に割り当て、この番号をハッシュ コードとして使用することです。次に、辞書容量として MyClass インスタンスの総数を指定できます。ただし、このような場合、オブジェクトの「インデックス」とインデックスがインクリメンタルであることを知っているため、辞書の代わりに配列を簡単に維持できます。

最後に、「数え切れないほどのサイズ変更はありません」という他の人が言ったことを繰り返したいと思います。ディクショナリは、容量が不足するたびに容量を 2 倍にします (新しい容量以上の最も近い素数に丸めます)。いくらかの処理を節約するために、容量を MyClass インスタンスの数に適切に設定できます。いずれにせよ、辞書はインスタンスを格納するためにこの容量を必要としますが、これは「ハッシュ衝突」を最小化せず、通常の状況では十分に速い。

于 2012-07-04T11:04:16.073 に答える
1

HashTable のようなデータ構造は、動的メモリ割り当てを目的としています。ただし、一部の構造では初期サイズについて言及できます。ただし、新しい要素を追加すると、サイズが拡大します。サイズを暗黙的に制限することはできません。

利用可能なデータ構造は多数あり、それぞれに長所と短所があります。最適なものを選択する必要があります。サイズを制限しても、パフォーマンスには影響しません。パフォーマンスに違いをもたらす追加、削除、および検索に注意する必要があります。

于 2012-07-04T05:13:14.213 に答える