This commit is contained in:
krahets
2026-04-03 18:46:15 +08:00
parent 377736b1bd
commit 9d21ca86b0
352 changed files with 46563 additions and 11262 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -3,19 +3,19 @@ comments: true
icon: material/table-search
---
# 第 6 章   ハッシュ
# 第 6 章   ハッシュテーブル
![ハッシュ](../assets/covers/chapter_hashing.jpg){ class="cover-image" }
![ハッシュテーブル](../assets/covers/chapter_hashing.jpg){ class="cover-image" }
!!! abstract
コンピューティングの世界において、ハッシュ表は賢い司書のようなものです。
インデックス番号の計算方法を理解し、目的の本を迅速に取得することを可能にします。
コンピュータの世界では、ハッシュテーブルは聡明な図書館員のような存在です。
彼は請求記号の計算方法を知っており、そのため目的の本を素早く見つけられます。
## 章の内容
- [6.1   ハッシュ](hash_map.md)
- [6.1   ハッシュテーブル](hash_map.md)
- [6.2   ハッシュ衝突](hash_collision.md)
- [6.3   ハッシュアルゴリズム](hash_algorithm.md)
- [6.4   まとめ](summary.md)
+32 -28
View File
@@ -4,48 +4,52 @@ comments: true
# 6.4   まとめ
### 1.   重要ポイント
### 1.   重要ポイントの振り返り
- 入力`key`が与えられると、ハッシュ表は$O(1)$時間で対応する`value`を取得でき、非常に効率的です
- 一般的なハッシュの操作には、クエリ、キー値ペアの追加、キーペアの削除、ハッシュ表の走査があります
- ハッシュ関数は`key`を配列インデックスにマッピングし、対応するバケットにアクセスして`value`を取得できるようにします。
- 2つの異なるキーがハッシュ化後に同じ配列インデックスになる場合があり、誤ったクエリ結果につながります。この現象ハッシュ衝突として知られています
- ハッシュの容量が大きいほど、ハッシュ衝突の確率は低くなります。したがって、ハッシュ表のリサイズはハッシュ衝突を緩和できます。配列のリサイズと同様に、ハッシュ表のリサイズはコストが高いです
- 要素数をバケット数で割った負荷率は、ハッシュ衝突の深刻を反映し、しばしばハッシュ表リサイズのトリガー条件として使用されます
- 連鎖法は各要素を連結リストに変換し、衝突するすべての要素を同じリストに格納することでハッシュ衝突に対処します。ただし、過度に長いリストはクエリ効率低下させる可能性があり、リストを赤黒木に変換することで改善できます
- オープンアドレス法は複数回のプローブを通してハッシュ衝突を処理します。線形プローブは固定ステップサイズを使用しますが、要素を削除できず、クラスタリングを起こしやすい傾向があります。多重ハッシュはプローブに複数のハッシュ関数を使用し、線形プローブと比較してクラスタリングを減らしますが、計算オーバーヘッドが増加します
- 異なるプログラミング言語はさまざまなハッシュ表実装採用しています。例えば、Java`HashMap`は連鎖を使用し、Python`dict`はオープンアドレス法を採用しています
- ハッシュ表では、決定性、高効率、均等分散を持つハッシュアルゴリズムが望まれます。暗号では、ハッシュアルゴリズムは衝突性と雪崩効果も持つべきで
- ハッシュアルゴリズムは通常、ハッシュ値の均等分散を保証しハッシュ衝突を減らすために、大きな素数を剰余として使用します
- 一般的なハッシュアルゴリズムにはMD5、SHA-1、SHA-2、SHA-3があります。MD5はファイル整合性チェックによく使用され、SHA-2は安全なアプリケーションとプロトコルで一般的に使用されます
- プログラミング言語は通常、ハッシュ表のバケットインデックスを計算するために、データ型に対して組み込みのハッシュアルゴリズムを提供します。一般的に、不変オブジェクトのみがハッシュ可能です
- `key` を入力すると、ハッシュテーブルは $O(1)$ 時間で `value` を検索でき、非常に効率である
- 一般的なハッシュテーブルの操作には、検索、キーと値のペアの追加、キーと値のペアの削除、ハッシュテーブルの走査などがある
- ハッシュ関数は `key` を配列インデックスに写像し、それによって対応するバケットにアクセスして `value` を取得す
- 異なる 2 つの `key` が、ハッシュ関数を通した後に同じ配列インデックスになることがあり、検索結果の誤りを引き起こす。この現象ハッシュ衝突と呼ぶ
- ハッシュテーブルの容量が大きいほど、ハッシュ衝突の確率は低くなる。そのため、ハッシュテーブルを拡張することでハッシュ衝突を緩和でき。配列の拡張と同様に、ハッシュテーブルの拡張操作のコストは大きい
- 負荷率は、ハッシュテーブル内の要素数をバケット数で割ったものと定義され、ハッシュ衝突の深刻を反映する。ハッシュテーブル拡張を発動する条件としてよく用いられる
- 連鎖方式では、単一要素を連結リストに変換し、衝突したすべての要素を同じ連結リストに格納する。しかし、連結リストが長すぎると検索効率低下するため、さらに連結リストを赤黒木に変換して効率を高めることができる
- オープンアドレス法は複数回の探索によってハッシュ衝突を処理する。線形探索は固定ステップ幅を用いるが、要素を削除できず、クラスタリングが発生しやすいという欠点がある。二重ハッシュは複数のハッシュ関数を用いて探索するため、線形探索に比べてクラスタリングが起きにくいが、複数のハッシュ関数によって計算量が増える
- プログラミング言語ごとに、異なるハッシュテーブル実装採用されている。たとえば、Java`HashMap` は連鎖方式を使用し、Python`Dict` はオープンアドレス法を採用してい
- ハッシュテーブルでは、ハッシュアルゴリズムに決定性、高効率、均一分布という特徴が求められる。暗号では、ハッシュアルゴリズムはさらに耐衝突性とアバランシェ効果も備えるべきである
- ハッシュアルゴリズムは通常、大きな素数を法として用い、ハッシュ値の均一分布を最大限に保証しハッシュ衝突を減らす。
- 一般的なハッシュアルゴリズムには MD5、SHA-1、SHA-2、SHA-3 などがある。MD5 はファイル完全性の検証によく用いられ、SHA-2 はセキュリティ用途やプロトコルでよく用いられる
- プログラミング言語は通常、データ型に対して組み込みのハッシュアルゴリズムを提供し、ハッシュテーブル内のバケットインデックスの計算に用いる。通常、ハッシュ可能なのは不変オブジェクトだけである
### 2.   Q & A
**Q**: ハッシュの時間計算量が$O(n)$に悪化するのはいつですか?
**Q**ハッシュテーブルの時間計算量が $O(n)$ になるのはどのような場合ですか?
ハッシュ表の時間計算量は、ハッシュ衝突が深刻な場合に$O(n)$に悪化する可能性があります。ハッシュ関数が適切に設計され、容量が適切に設定され、衝突が均等に分散されている場合、時間計算量は$O(1)$です。プログラミング言語組み込みハッシュ表を使用する場合、通常は時間計算量を$O(1)$と考えます。
ハッシュ衝突が深刻な場合、ハッシュテーブルの時間計算量は $O(n)$ に劣化する。ハッシュ関数の設計が適切で、容量設定が合理的で、衝突が比較的均等な場合、時間計算量は $O(1)$ である。プログラミング言語組み込みハッシュテーブルを使うとき、通常は時間計算量を $O(1)$ とみなす。
**Q**: なぜハッシュ関数$f(x) = x$を使用しないのですか?これなら衝突を排除できます
**Q**なぜハッシュ関数 $f(x) = x$ を使ないのですか? そうすれば衝突は起きません
ハッシュ関数$f(x) = x$では、各要素一意のバケットインデックスに対応し、これは配列と同等です。しかし、入力空間は通常出力空間(配列長)よりはるかに大きいため、ハッシュ関数の最後のステップは配列長の剰余を取ることがよくあります。言い換えると、ハッシュの目は、$O(1)$のクエリ効率を提供しながら、より大きな状態空間をより小さなものにマッピングすることで
$f(x) = x$ というハッシュ関数では、各要素一意のバケットインデックスに対応し、これは配列と等価である。しかし、入力空間は通常出力空間(配列長)よりはるかに大きいため、ハッシュ関数の最後のステップはたいてい配列長の剰余になる。言い換えると、ハッシュテーブルの目は、大きな状態空間をより小さな空間に写像し、$O(1)$ の検索効率を提供することである
**Q**: ハッシュ表がこれらの構造を使って実装されているにもかかわらず、なぜ配列、連結リスト、二分木より効率になるのですか?
**Q**ハッシュテーブルの基礎実装は配列、連結リスト、二分木なのに、なぜそれらより効率になり得るのですか?
まず、ハッシュは時間効率が高いですが、空間効率は低いです。ハッシュ表のメモリの大部分未使用のままです
まず、ハッシュテーブルは時間効率が高くなる一方で、空間効率は低くなる。ハッシュテーブルには、かなりの部分未使用のメモリが存在する
次に、ハッシュ表は特定のユースケースでのみ時間効率が高いです。配列や連結リストを使用して同じ時間計算量で機能を実装できる場合、通常はハッシュ表を使用するよりも高速です。これは、ハッシュ関数の計算がオーバーヘッドを発生させ、時間計算量の定数因子が大きくなるためです
次に、時間効率が高くなるのは特定の利用場面に限られる。ある機能が同じ時間計算量で配列や連結リストによって実装できるなら、通常はハッシュテーブルより速い。これは、ハッシュ関数の計算にコストがかかり、時間計算量の定数項がより大きいからである
最後に、ハッシュの時間計算量は化する可能性があります。例えば連鎖では、連結リストや赤黒木で検索操作を実行し、これは依然として$O(n)$時間に化するリスクがあります
最後に、ハッシュテーブルの時間計算量は化する可能性がある。たとえば連鎖方式では、連結リストや赤黒木で検索操作を行うため、なお $O(n)$ 時間に化するリスクがあ
**Q**: 多重ハッシュにも要素を直接削除できないという欠陥がありますか?削除としてマークされた空間は再利用できますか?
**Q**:二重ハッシュにも要素を直接削除できない欠点がありますか? 削除済みとマークした領域は再利用できますか?
重ハッシュはオープンアドレス法の一形態であり、すべてのオープンアドレス法は要素を直接削除できないという欠点があります。要素を削除済みとしてマークする必要があります。マークされた空間は再利用できます。ハッシュ表に新しい要素を挿入する際、ハッシュ関数削除済みとしてマークされた位置を指している場合、その位置は新しい要素によって使用できます。これにより、ハッシュ表のプローブシーケンスを維持しながら、空間の効率的な使用が保証されます
重ハッシュはオープンアドレス法の一であり、オープンアドレス法はいずれも要素を直接削除できないという欠点があるため、削除のマーク付けが必要になる。削除済みとマークされた領域は再利用できる。新しい要素をハッシュテーブルに挿入し、ハッシュ関数によって削除済みとマークされた位置を見つけた場合、その位置は新しい要素に使用できる。こうすることで、ハッシュテーブルの探索系列を変えずに保ちつつ、空間利用率も確保できる
**Q**: なぜ線形プローブの検索プロセス中にハッシュ衝突が発生するのですか?
**Q**なぜ線形探索では、要素を探すときにハッシュ衝突が発生するのですか?
検索プロセス中、ハッシュ関数対応するバケットとキー値ペアを指します。`key`が一致しない場合、ハッシュ衝突を示します。したがって、線形プローブは正しいキーペア見つるか検索が失敗するまで、事前に決められたステップサイズで下方向に検索します
探索時には、ハッシュ関数対応するバケットとキーと値のペアを見つけ、`key` が一致しないことが分かると、それはハッシュ衝突を意味する。そのため、線形探索法では事前に設定したステップ幅に従って順に探索し、正しいキーと値のペア見つるか、見つからずに終了するまで続ける
**Q**: なぜハッシュ表のリサイズがハッシュ衝突を緩和できるのですか?
**Q**なぜハッシュテーブルの拡張でハッシュ衝突を緩和できるのですか?
ハッシュ関数の最後のステップは、出力を配列インデックス範囲内に保つために、配列長$n$の剰余を取ることがよくあります。リサイズ時、配列長$n$が変化し、キーに対応するインデックスも変化する可能性があります。以前に同じバケットにマッピングされていたキーが、リサイズ後に複数のバケットに分散される可能性があり、それによってハッシュ衝突が緩和されます
ハッシュ関数の最後のステップは、たいてい配列長 $n$ での剰余を取り、出力を配列インデックス範囲内に収めることである。拡張後は配列長 $n$ が変化し、`key` に対応するインデックスも変化する可能性がある。もともと同じバケットに入っていた複数の `key` は、拡張後に複数のバケットに割り当てられる可能性があり、それによってハッシュ衝突が緩和され
**Q**:高効率な読み書きのためなら、配列を直接使えばよいのではないですか?
データの `key` が連続した小範囲の整数であれば、配列を直接使えばよく、単純で高効率である。しかし `key` が別の型(たとえば文字列)の場合は、ハッシュ関数を用いて `key` を配列インデックスに写像し、さらにバケット配列を通じて要素を格納する必要がある。このような構造がハッシュテーブルである。