* Retranslate the complete Japanese online edition from Chinese * Fit Japanese homepage labels and localize heading permalinks * Point Japanese contribution instructions to the Japanese preview * Replace locale conditionals with multilingual UI tables
5.9 KiB
まとめ
要点の振り返り
- ハッシュテーブルは、
keyを入力するとO(1)時間でvalueを照会でき、非常に効率的です。 - 主な操作には、照会、キーと値のペアの追加・削除、テーブルの走査などがあります。
- ハッシュ関数は
keyを配列のインデックスに写し、対応するバケットにアクセスしてvalueを取得できるようにします。 - 異なる二つの
keyが同じインデックスになり、誤った照会結果を生む場合があります。これをハッシュ衝突と呼びます。 - 容量が大きいほど衝突の確率が下がるため、拡張によって衝突を緩和できます。ただし、配列と同様、容量拡張の負担は大きいものです。
- 負荷率は要素数をバケット数で割った値で、衝突の程度を反映します。容量拡張を開始する条件によく使います。
- チェイン法は単一の要素を連結リストに置き換え、衝突した要素を同じリストに格納します。ただし、長すぎると照会効率が下がるため、さらに赤黒木へ変換して改善できます。
- オープンアドレス法は複数回の探索で衝突に対処します。線形探索法は一定の間隔で探しますが、要素を直接削除できず、クラスタリングが起きやすいという欠点があります。複数のハッシュ関数を使う方法はクラスタリングが起きにくい一方、計算量が増えます。
- 言語ごとに異なる実装を採用しています。例えば Java の
HashMapはチェイン法、Python のdictはオープンアドレス法です。 - ハッシュテーブルのハッシュアルゴリズムには、決定性、高い効率、均等な分布が求められます。暗号分野では、衝突耐性と雪崩効果も必要です。
- 通常は大きな素数を法として、ハッシュ値の分布をできるだけ均等にし、衝突を減らします。
- 主なアルゴリズムには MD5、SHA-1、SHA-2、SHA-3 などがあります。MD5 はファイルの完全性の確認、SHA-2 はセキュリティ用途やプロトコルによく使われます。
- 言語は通常、データ型ごとの組み込みハッシュアルゴリズムを用意し、バケットのインデックスの計算に使います。一般には、不変オブジェクトだけがハッシュ化可能です。
Q & A
Q:ハッシュテーブルの時間計算量は、どのような場合に O(n) になりますか?
衝突がひどいと、時間計算量は O(n) に悪化します。ハッシュ関数の設計、容量の設定、衝突の分布が適切なら O(1) です。言語の組み込みハッシュテーブルを使う場合、通常は O(1) と考えます。
Q:ハッシュ関数を f(x) = x にすれば、衝突しないのではありませんか?
f(x) = x なら、各要素は一意のバケットに対応し、配列と同じになります。しかし、入力空間は通常、出力空間である配列の長さよりはるかに大きいため、最後に配列の長さで剰余を取ることが一般的です。つまり、ハッシュテーブルは大きな状態空間を小さな空間へ写し、O(1) の照会効率を提供することを目指します。
Q:内部が配列、連結リスト、二分木なのに、なぜそれらより高い効率を得られるのですか?
まず、時間効率は上がりますが、空間効率は下がります。ハッシュテーブルでは、かなりのメモリが未使用です。
次に、時間効率が上がるのは特定の用途だけです。同じ時間計算量で配列や連結リストを使えるなら、通常はそのほうが速くなります。ハッシュ関数の計算に負担がかかり、定数項が大きいためです。
最後に、時間計算量は悪化する可能性があります。例えばチェイン法では、連結リストや赤黒木で探索しており、O(n) 時間に退化するリスクが残ります。
Q:複数のハッシュ関数を使う方法でも、要素を直接削除できませんか? 削除済みの場所は再利用できますか?
これはオープンアドレス法の一種なので、同じく直接は削除できず、削除済みの印が必要です。その場所は再利用できます。新しい要素を挿入する際、ハッシュ関数で見つけた位置に削除済みの印があれば、そこを使えます。探索する位置の列を保ちながら、領域の利用効率も確保できます。
Q:線形探索法では、なぜ要素を探すときにもハッシュ衝突が起きるのですか?
ハッシュ関数で対応するバケットとペアに到達しても、key が一致しなければ衝突があるということです。そのため、あらかじめ決めた間隔で後ろへ探し、正しいペアを見つけるか、見つからないと分かるまで続けます。
Q:容量を拡張すると、なぜ衝突を緩和できるのですか?
ハッシュ関数の最後の手順は通常、配列の長さ n で剰余を取り、出力をインデックスの範囲内に収めることです。拡張後は n が変わるため、key に対応するインデックスも変わる可能性があります。同じバケットにあった複数の key が別々に割り当てられれば、衝突が緩和されます。
Q:効率よく読み書きしたいなら、配列を直接使えばよいのではありませんか?
key が狭い範囲の連続した整数なら、配列を直接使うのが単純で効率的です。ただし、文字列などの型なら、ハッシュ関数でインデックスへ写してから、バケットの配列に要素を格納する必要があります。その構造がハッシュテーブルです。