ハッシュテーブル(ハッシュ表)における衝突(コリジョン)の説明として正しいものはどれか。
- Aハッシュ値から元のキーが求められてしまうこと
- B異なるキーから同じハッシュ値(格納する位置)が求まること
- C同じキーから、実行するたびに違うハッシュ値が求まること
キーをハッシュ関数で表の位置に対応させて、ほぼ一定の時間で探せるようにするのがハッシュ表です。このツールでは、キーを順に入れたときにどの位置で衝突が起き、どこに格納されるかを表で示します。衝突の処理方法を切り替えて、探す手間がどう変わるかを確かめられます。
お気に入りと最近使ったツールは、このブラウザ内(localStorage)にのみ保存されます。
探索 –
探査回数は、キーを格納する(探す)までに調べた位置の数です。チェイン法では、同じ位置のリストの何番目に入るかを数えます。二次探査法では、表の大きさや負荷率によって空きがあっても見つけられないことがあります。
ほかの入力は今の値のまま、選んだ入力だけを今の値の1/4〜4倍の範囲で変えたときの結果です。赤い点が今の値です。
ハッシュ表は、キーからハッシュ関数で表の位置を計算し、その位置にデータを置くデータ構造です。うまく散らばれば、キーの数によらずほぼ一定の時間で探せます。しかし、違うキーが同じ位置になることがあり、これを衝突(コリジョン)といいます。大きさ7の表に h(k) = k mod 7 で 10、17、24、3 を入れると、4つとも位置3になって衝突します。衝突を避けることはできないので、どう処理するかが性能を左右します。
チェイン法は、同じ位置になったキーを連結リストでつなぐ方法です。表があふれることはなく、削除も簡単ですが、リストをたどる分だけ探索に時間がかかり、メモリのあちこちを参照します。オープンアドレス法は、衝突したら表の中の別の空き位置を探して入れる方法で、ずらし方によって線形探査法(1つずつ)、二次探査法(1、4、9…ずつ)、ダブルハッシュ法(キーごとに決まる幅ずつ)があります。表の大きさを超えるキーは入れられず、削除するときは「削除済み」の印が必要です。
キーの数を表の大きさで割った値を負荷率(ロードファクタ)といいます。負荷率が高くなるほど衝突が増え、オープンアドレス法では特に探査回数が急に増えます。線形探査法では、埋まった位置が連続した固まり(クラスタ)ができやすく、そこにぶつかったキーはさらに固まりを伸ばします。二次探査法やダブルハッシュ法は、ずらし方を変えることでこの固まりを減らします。例「線形探査で固まりができる例」と「二次探査とダブルハッシュで散らす」で、平均探査回数を比べてみてください。実際のハッシュ表は、負荷率がおよそ0.5〜0.75を超えると表を大きくして入れ直します(再ハッシュ)。
よいハッシュ関数は、キーを表全体に均等に散らします。k mod m では、m に素数を選ぶと、キーに規則性(偶数ばかり、10の倍数ばかりなど)があっても偏りにくくなります。文字列の文字コードを単純に足すと、例の cat・act・tac のように文字の並びを入れ替えただけのキーが必ず衝突するので、実際には文字ごとに重みを掛けて足すなどの工夫をします。ハッシュ表は、プログラミング言語の辞書(連想配列)、データベースのハッシュ索引、キャッシュの検索など、さまざまな所で使われています。
正解:C. 位置6
4つのキーはどれも7で割った余りが3なので、すべて位置3を最初に調べます。10は位置3に入り、17は位置3が埋まっているので位置4、24は位置3、4が埋まっているので位置5に入ります。3は位置3、4、5が埋まっているので、位置6に格納されます。このように線形探査法では、埋まった位置が固まって並び(クラスタ)、後から来るキーほど調べる回数が増えます。
正解:A. チェイン法では負荷率が1を超えても格納できるが、オープンアドレス法では表の大きさを超えて格納できず、負荷率が1に近づくと探索の回数が急に増える
チェイン法は同じ位置の要素をリストにつなぐので、要素数が表の大きさを超えても格納できますが、平均のリストの長さは負荷率に比例して伸び、探索の手間も増えます。オープンアドレス法は表の中だけに格納するので要素数は表の大きさまでで、負荷率が1に近づくと空きを探す回数が急に増えます。どちらの方法でも、負荷率が決めた値(例えば0.5〜0.75程度)を超えたら表を大きくして全要素を入れ直す(リハッシュ)のが普通です。オープンアドレス法でも衝突は起こり、空いた位置を探すことで対処しています。
正解:A. 衝突したときに調べる次の位置が隣の要素なので、同じキャッシュラインやすぐ次のラインに載っていることが多く、メモリへのアクセスが少なく済むから
現在のCPUでは、キャッシュミスで主記憶を読む時間が、比較などの計算よりずっと長くかかります。線形探査では衝突しても隣の位置を調べるので、多くの場合同じキャッシュラインの中で済み、プリフェッチも効きます。チェイン法では、リストの次の要素がメモリ上の離れた場所にあることが多く、ポインタをたどるたびにキャッシュミスが起こりやすくなります。計算量のオーダーが同じでも、メモリのアクセスの仕方によって実際の速さは大きく変わります。ただし、線形探査は負荷率が高くなると埋まった位置が固まって急に遅くなるので、負荷率を低めに保ちます。
負荷率が低いうちは、オープンアドレス法(特に線形探査法)のほうが、表の連続した位置を順に調べるのでキャッシュに乗りやすく、速いことが多くあります。負荷率が高くなると、オープンアドレス法の探査回数は急に増えるので、チェイン法のほうが安定します。
k mod m で、キーと m が共通の約数を持つと、使われる位置が偏るからです。たとえば m = 10 で偶数のキーばかりだと、偶数の位置しか使われません。m が素数なら、このような偏りが起きにくくなります。二次探査法でも、m が素数で負荷率が0.5以下なら、空き位置を必ず見つけられることが知られています。
キーがうまく散らばっていれば、平均で O(1)(データ数によらず一定)です。ただし、すべてのキーが同じ位置に衝突するような最悪の場合は、O(n) になります。
基本情報技術者試験などでは、ハッシュ関数と衝突の処理方法が与えられ、キーを順に入れたときにあるキーが格納される位置を答える問題がよく出ます。例を選んで、表の「調べた位置」を自分の答えと比べてみてください。