ハッシュ表の計算ツール

キーをハッシュ関数で表の位置に対応させて、ほぼ一定の時間で探せるようにするのがハッシュ表です。このツールでは、キーを順に入れたときにどの位置で衝突が起き、どこに格納されるかを表で示します。衝突の処理方法を切り替えて、探す手間がどう変わるかを確かめられます。

キーと表

0以上の整数か、8文字までの文字列を60個まで。整数は h(k) = k mod m、文字列は文字コードの和を m で割った余りを使います。同じキーは1回だけ入れます。

結果

負荷率(キーの数 ÷ 表の大きさ)
–
衝突したキーの数
–
格納までの平均探査回数
–

キーごとの格納の様子

衝突の処理方法の比較

探査回数は、キーを格納する(探す)までに調べた位置の数です。チェイン法では、同じ位置のリストの何番目に入るかを数えます。二次探査法では、表の大きさや負荷率によって空きがあっても見つけられないことがあります。

ハッシュ表と衝突

ハッシュ表は、キーからハッシュ関数で表の位置を計算し、その位置にデータを置くデータ構造です。うまく散らばれば、キーの数によらずほぼ一定の時間で探せます。しかし、違うキーが同じ位置になることがあり、これを衝突(コリジョン)といいます。大きさ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 のように文字の並びを入れ替えただけのキーが必ず衝突するので、実際には文字ごとに重みを掛けて足すなどの工夫をします。ハッシュ表は、プログラミング言語の辞書(連想配列)、データベースのハッシュ索引、キャッシュの検索など、さまざまな所で使われています。

アルゴリズムの問題を解く

ハッシュテーブル(ハッシュ表)における衝突(コリジョン)の説明として正しいものはどれか。

  1. Aハッシュ値から元のキーが求められてしまうこと
  2. B異なるキーから同じハッシュ値(格納する位置)が求まること
  3. C同じキーから、実行するたびに違うハッシュ値が求まること
答えと解説を見る

正解:B. 異なるキーから同じハッシュ値(格納する位置)が求まること

ハッシュテーブルは、キーからハッシュ関数で格納する位置を計算し、平均 O(1) で探索・挿入・削除ができるデータ構造です。キーの種類は表の大きさよりはるかに多いので、異なるキーが同じ位置になること(衝突)は避けられません。そのため、同じ位置の要素をリストにつなぐチェイン法や、空いている別の位置を探すオープンアドレス法などで衝突を処理します。同じキーからは必ず同じハッシュ値が求まらなければ探索ができず、ハッシュ値から元のキーが分かるかどうかは暗号に使う場合の性質で、衝突とは別の話です。

大きさ7のハッシュテーブル(位置0〜6)に、ハッシュ関数 h(k) = k mod 7 でキー10、17、24、3をこの順に格納する。衝突したときは、位置を1つずつ後ろへずらして(6の次は0)空いている位置を探す線形探査法を使う。キー3が格納される位置はどれか。

  1. A位置3
  2. B位置4
  3. C位置6
答えと解説を見る

正解:C. 位置6

4つのキーはどれも7で割った余りが3なので、すべて位置3を最初に調べます。10は位置3に入り、17は位置3が埋まっているので位置4、24は位置3、4が埋まっているので位置5に入ります。3は位置3、4、5が埋まっているので、位置6に格納されます。このように線形探査法では、埋まった位置が固まって並び(クラスタ)、後から来るキーほど調べる回数が増えます。

ハッシュテーブルの衝突を処理する方法として、チェイン法(同じ位置の要素を連結リストでつなぐ)とオープンアドレス法(表の中の空いている別の位置を探す)を比べた説明として正しいものはどれか。負荷率は、格納した要素数を表の大きさで割った値とする。

  1. Aチェイン法では負荷率が1を超えても格納できるが、オープンアドレス法では表の大きさを超えて格納できず、負荷率が1に近づくと探索の回数が急に増える
  2. Bオープンアドレス法では衝突が起こらないので、負荷率によらず探索は1回で済む
  3. Cチェイン法では、負荷率がいくら高くなっても探索の手間は変わらない
答えと解説を見る

正解:A. チェイン法では負荷率が1を超えても格納できるが、オープンアドレス法では表の大きさを超えて格納できず、負荷率が1に近づくと探索の回数が急に増える

チェイン法は同じ位置の要素をリストにつなぐので、要素数が表の大きさを超えても格納できますが、平均のリストの長さは負荷率に比例して伸び、探索の手間も増えます。オープンアドレス法は表の中だけに格納するので要素数は表の大きさまでで、負荷率が1に近づくと空きを探す回数が急に増えます。どちらの方法でも、負荷率が決めた値(例えば0.5〜0.75程度)を超えたら表を大きくして全要素を入れ直す(リハッシュ)のが普通です。オープンアドレス法でも衝突は起こり、空いた位置を探すことで対処しています。

要素数が多く、負荷率を0.5程度に保ったハッシュテーブルで、線形探査のオープンアドレス法が、チェイン法(連結リストでつなぐ方法)より速くなりやすい理由として最も適切なものはどれか。

  1. A衝突したときに調べる次の位置が隣の要素なので、同じキャッシュラインやすぐ次のラインに載っていることが多く、メモリへのアクセスが少なく済むから
  2. Bオープンアドレス法では衝突がまったく起こらないから
  3. Cチェイン法ではハッシュ関数を2回以上計算する必要があるから
答えと解説を見る

正解:A. 衝突したときに調べる次の位置が隣の要素なので、同じキャッシュラインやすぐ次のラインに載っていることが多く、メモリへのアクセスが少なく済むから

現在のCPUでは、キャッシュミスで主記憶を読む時間が、比較などの計算よりずっと長くかかります。線形探査では衝突しても隣の位置を調べるので、多くの場合同じキャッシュラインの中で済み、プリフェッチも効きます。チェイン法では、リストの次の要素がメモリ上の離れた場所にあることが多く、ポインタをたどるたびにキャッシュミスが起こりやすくなります。計算量のオーダーが同じでも、メモリのアクセスの仕方によって実際の速さは大きく変わります。ただし、線形探査は負荷率が高くなると埋まった位置が固まって急に遅くなるので、負荷率を低めに保ちます。

よくある質問

チェイン法とオープンアドレス法はどちらが速いですか?

負荷率が低いうちは、オープンアドレス法(特に線形探査法)のほうが、表の連続した位置を順に調べるのでキャッシュに乗りやすく、速いことが多くあります。負荷率が高くなると、オープンアドレス法の探査回数は急に増えるので、チェイン法のほうが安定します。

表の大きさに素数を選ぶのはなぜですか?

k mod m で、キーと m が共通の約数を持つと、使われる位置が偏るからです。たとえば m = 10 で偶数のキーばかりだと、偶数の位置しか使われません。m が素数なら、このような偏りが起きにくくなります。二次探査法でも、m が素数で負荷率が0.5以下なら、空き位置を必ず見つけられることが知られています。

ハッシュ表の探索の計算量はいくつですか?

キーがうまく散らばっていれば、平均で O(1)(データ数によらず一定)です。ただし、すべてのキーが同じ位置に衝突するような最悪の場合は、O(n) になります。

試験ではどのように問われますか?

基本情報技術者試験などでは、ハッシュ関数と衝突の処理方法が与えられ、キーを順に入れたときにあるキーが格納される位置を答える問題がよく出ます。例を選んで、表の「調べた位置」を自分の答えと比べてみてください。

関連する用語

ハッシュテーブル 連結リスト 計算量

関連する計算ツール

半導体・電子回路・情報の計算ツール →