This commit is contained in:
krahets
2026-03-30 08:17:41 +08:00
parent 68cafe99dd
commit 46bccf0065
484 changed files with 60193 additions and 20315 deletions
+60 -58
View File
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="アニメーション図解ワンクリック実行データ構造とアルゴリズムチュートリアル">
<meta name="description" content="アニメーション図解ワンクリック実行コードで学べるデータ構造とアルゴリズムの入門書">
<meta name="author" content="krahets">
@@ -117,7 +117,7 @@
<path
d="M480 32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9L381.7 53c-48 48-113.1 75-181 75H192 160 64c-35.3 0-64 28.7-64 64v96c0 35.3 28.7 64 64 64l0 128c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V352l8.7 0c67.9 0 133 27 181 75l43.6 43.6c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V300.4c18.6-8.8 32-32.5 32-60.4s-13.4-51.6-32-60.4V32zm-64 76.7V240 371.3C357.2 317.8 280.5 288 200.7 288H192V192h8.7c79.8 0 156.5-29.8 215.3-83.3z" />
</svg>
<span>日本語版審閱者を募集しています!詳細<a href="https://github.com/krahets/hello-algo/blob/main/ja/CONTRIBUTING.md">CONTRIBUTING.md</a>参照してください。</span>
<span>日本語版のレビュアーを募集しています。詳しく<a href="/ja/chapter_appendix/contribution/">こちら</a>ご覧ください。</span>
</div>
</div>
@@ -341,7 +341,7 @@
<span class="md-ellipsis">
はじめに
@@ -358,7 +358,7 @@
<span class="md-nav__icon md-icon"></span>
はじめに
</label>
@@ -618,7 +618,7 @@
<span class="md-ellipsis">
1.1 &nbsp; アルゴリズムはどこにでもある
1.1 &nbsp; アルゴリズムは至るところにある
@@ -646,7 +646,7 @@
<span class="md-ellipsis">
1.2 &nbsp; アルゴリズムとは何か
1.2 &nbsp; アルゴリズムとは
@@ -783,7 +783,7 @@
<span class="md-ellipsis">
2.1 &nbsp; アルゴリズム効率評価
2.1 &nbsp; アルゴリズム効率評価
@@ -1060,7 +1060,7 @@
<span class="md-ellipsis">
3.3 &nbsp; 数値の符号化 *
3.3 &nbsp; 数値エンコーディング *
@@ -1088,7 +1088,7 @@
<span class="md-ellipsis">
3.4 &nbsp; 文字の符号化 *
3.4 &nbsp; 文字エンコーディング *
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
第 6 章 &nbsp; ハッシュ
第 6 章 &nbsp; ハッシュテーブル
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
第 6 章 &nbsp; ハッシュ
第 6 章 &nbsp; ハッシュテーブル
</label>
@@ -1637,7 +1637,7 @@
<span class="md-ellipsis">
6.1 &nbsp; ハッシュ
6.1 &nbsp; ハッシュテーブル
@@ -1778,7 +1778,7 @@
<a href="#1" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; 重要ポイント
1. &nbsp; 重要ポイントの振り返り
</span>
</a>
@@ -2014,7 +2014,7 @@
<span class="md-ellipsis">
7.5 &nbsp; AVL木 *
7.5 &nbsp; AVL 木 *
@@ -2177,7 +2177,7 @@
<span class="md-ellipsis">
8.2 &nbsp; ヒープ構築操作
8.2 &nbsp; ヒープ構築
@@ -2563,7 +2563,7 @@
<span class="md-ellipsis">
10.2 &nbsp; 二分探索の挿入
10.2 &nbsp; 二分探索の挿入位置
@@ -2619,7 +2619,7 @@
<span class="md-ellipsis">
10.4 &nbsp; ハッシュ最適化戦略
10.4 &nbsp; ハッシュによる最適化戦略
@@ -2647,7 +2647,7 @@
<span class="md-ellipsis">
10.5 &nbsp; 探索アルゴリズムの再認識
10.5 &nbsp; 探索アルゴリズム再考
@@ -3185,7 +3185,7 @@
<span class="md-ellipsis">
12.1 &nbsp; 分割統治アルゴリズム
12.1 &nbsp; 分割統治
@@ -3241,7 +3241,7 @@
<span class="md-ellipsis">
12.3 &nbsp; 木の構築問題
12.3 &nbsp; 二分木の構築問題
@@ -3269,7 +3269,7 @@
<span class="md-ellipsis">
12.4 &nbsp; ハノイの塔問題
12.4 &nbsp; ハノイの塔問題
@@ -3462,7 +3462,7 @@
<span class="md-ellipsis">
13.3 &nbsp; 部分集合和問題
13.3 &nbsp; 部分和問題
@@ -3490,7 +3490,7 @@
<span class="md-ellipsis">
13.4 &nbsp; Nクイーン問題
13.4 &nbsp; n クイーン問題
@@ -3631,7 +3631,7 @@
<span class="md-ellipsis">
14.1 &nbsp; 動的計画法の初歩
14.1 &nbsp; 動的計画法入門
@@ -3659,7 +3659,7 @@
<span class="md-ellipsis">
14.2 &nbsp; DP 問題特性
14.2 &nbsp; 動的計画法の問題特性
@@ -3687,7 +3687,7 @@
<span class="md-ellipsis">
14.3 &nbsp; DP の解法の考え方
14.3 &nbsp; 動的計画法の問題解決の考え方
@@ -3715,7 +3715,7 @@
<span class="md-ellipsis">
14.4 &nbsp; 0-1ナップサック問題
14.4 &nbsp; 0-1 ナップサック問題
@@ -3908,7 +3908,7 @@
<span class="md-ellipsis">
15.1 &nbsp; 貪欲アルゴリズム
15.1 &nbsp; 貪欲
@@ -4153,7 +4153,7 @@
<span class="md-ellipsis">
16.2 &nbsp; 一緒に作に参加する
16.2 &nbsp; 一緒に作に参加しましょう
@@ -4299,7 +4299,7 @@
<a href="#1" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; 重要ポイント
1. &nbsp; 重要ポイントの振り返り
</span>
</a>
@@ -4355,37 +4355,39 @@
<!-- Page content -->
<h1 id="64">6.4 &nbsp; まとめ<a class="headerlink" href="#64" title="Permanent link">&para;</a></h1>
<h3 id="1">1. &nbsp; 重要ポイント<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<h3 id="1">1. &nbsp; 重要ポイントの振り返り<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<ul>
<li>入力<code>key</code>が与えられると、ハッシュ表は<span class="arithmatex">\(O(1)\)</span>時間で対応する<code>value</code>を取得でき、非常に効率的です</li>
<li>一般的なハッシュの操作には、クエリ、キー値ペアの追加、キーペアの削除、ハッシュ表の走査があります</li>
<li>ハッシュ関数は<code>key</code>を配列インデックスにマッピングし、対応するバケットにアクセスして<code>value</code>を取得できるようにします。</li>
<li>2つの異なるキーがハッシュ化後に同じ配列インデックスになる場合があり、誤ったクエリ結果につながります。この現象ハッシュ衝突として知られています</li>
<li>ハッシュの容量が大きいほど、ハッシュ衝突の確率は低くなります。したがって、ハッシュ表のリサイズはハッシュ衝突を緩和できます。配列のリサイズと同様に、ハッシュ表のリサイズはコストが高いです</li>
<li>要素数をバケット数で割った負荷率は、ハッシュ衝突の深刻を反映し、しばしばハッシュ表リサイズのトリガー条件として使用されます</li>
<li>連鎖法は各要素を連結リストに変換し、衝突するすべての要素を同じリストに格納することでハッシュ衝突に対処します。ただし、過度に長いリストはクエリ効率低下させる可能性があり、リストを赤黒木に変換することで改善できます</li>
<li>オープンアドレス法は複数回のプローブを通してハッシュ衝突を処理します。線形プローブは固定ステップサイズを使用しますが、要素を削除できず、クラスタリングを起こしやすい傾向があります。多重ハッシュはプローブに複数のハッシュ関数を使用し、線形プローブと比較してクラスタリングを減らしますが、計算オーバーヘッドが増加します</li>
<li>異なるプログラミング言語はさまざまなハッシュ表実装採用しています。例えば、Java<code>HashMap</code>は連鎖を使用し、Python<code>dict</code>はオープンアドレス法を採用しています</li>
<li>ハッシュ表では、決定性、高効率、均等分散を持つハッシュアルゴリズムが望まれます。暗号では、ハッシュアルゴリズムは衝突性と雪崩効果も持つべきで</li>
<li>ハッシュアルゴリズムは通常、ハッシュ値の均等分散を保証しハッシュ衝突を減らすために、大きな素数を剰余として使用します</li>
<li>一般的なハッシュアルゴリズムにはMD5、SHA-1、SHA-2、SHA-3があります。MD5はファイル整合性チェックによく使用され、SHA-2は安全なアプリケーションとプロトコルで一般的に使用されます</li>
<li>プログラミング言語は通常、ハッシュ表のバケットインデックスを計算するために、データ型に対して組み込みのハッシュアルゴリズムを提供します。一般的に、不変オブジェクトのみがハッシュ可能です</li>
<li><code>key</code> を入力すると、ハッシュテーブルは <span class="arithmatex">\(O(1)\)</span> 時間で <code>value</code> を検索でき、非常に効率である</li>
<li>一般的なハッシュテーブルの操作には、検索、キーと値のペアの追加、キーと値のペアの削除、ハッシュテーブルの走査などがある</li>
<li>ハッシュ関数は <code>key</code> を配列インデックスに写像し、それによって対応するバケットにアクセスして <code>value</code> を取得す</li>
<li>異なる 2 つの <code>key</code> が、ハッシュ関数を通した後に同じ配列インデックスになることがあり、検索結果の誤りを引き起こす。この現象ハッシュ衝突と呼ぶ</li>
<li>ハッシュテーブルの容量が大きいほど、ハッシュ衝突の確率は低くなる。そのため、ハッシュテーブルを拡張することでハッシュ衝突を緩和でき。配列の拡張と同様に、ハッシュテーブルの拡張操作のコストは大きい</li>
<li>負荷率は、ハッシュテーブル内の要素数をバケット数で割ったものと定義され、ハッシュ衝突の深刻を反映する。ハッシュテーブル拡張を発動する条件としてよく用いられる</li>
<li>連鎖方式では、単一要素を連結リストに変換し、衝突したすべての要素を同じ連結リストに格納する。しかし、連結リストが長すぎると検索効率低下するため、さらに連結リストを赤黒木に変換して効率を高めることができる</li>
<li>オープンアドレス法は複数回の探索によってハッシュ衝突を処理する。線形探索は固定ステップ幅を用いるが、要素を削除できず、クラスタリングが発生しやすいという欠点がある。二重ハッシュは複数のハッシュ関数を用いて探索するため、線形探索に比べてクラスタリングが起きにくいが、複数のハッシュ関数によって計算量が増える</li>
<li>プログラミング言語ごとに、異なるハッシュテーブル実装採用されている。たとえば、Java<code>HashMap</code> は連鎖方式を使用し、Python<code>Dict</code> はオープンアドレス法を採用してい</li>
<li>ハッシュテーブルでは、ハッシュアルゴリズムに決定性、高効率、均一分布という特徴が求められる。暗号では、ハッシュアルゴリズムはさらに耐衝突性とアバランシェ効果も備えるべきである</li>
<li>ハッシュアルゴリズムは通常、大きな素数を法として用い、ハッシュ値の均一分布を最大限に保証しハッシュ衝突を減らす。</li>
<li>一般的なハッシュアルゴリズムには MD5、SHA-1、SHA-2、SHA-3 などがある。MD5 はファイル完全性の検証によく用いられ、SHA-2 はセキュリティ用途やプロトコルでよく用いられる</li>
<li>プログラミング言語は通常、データ型に対して組み込みのハッシュアルゴリズムを提供し、ハッシュテーブル内のバケットインデックスの計算に用いる。通常、ハッシュ可能なのは不変オブジェクトだけである</li>
</ul>
<h3 id="2-q-a">2. &nbsp; Q &amp; A<a class="headerlink" href="#2-q-a" title="Permanent link">&para;</a></h3>
<p><strong>Q</strong>: ハッシュの時間計算量が<span class="arithmatex">\(O(n)\)</span>に悪化するのはいつですか?</p>
<p>ハッシュ表の時間計算量は、ハッシュ衝突が深刻な場合に<span class="arithmatex">\(O(n)\)</span>に悪化する可能性があります。ハッシュ関数が適切に設計され、容量が適切に設定され、衝突が均等に分散されている場合、時間計算量は<span class="arithmatex">\(O(1)\)</span>です。プログラミング言語組み込みハッシュ表を使用する場合、通常は時間計算量を<span class="arithmatex">\(O(1)\)</span>と考えます。</p>
<p><strong>Q</strong>: なぜハッシュ関数<span class="arithmatex">\(f(x) = x\)</span>を使用しないのですか?これなら衝突を排除できます</p>
<p>ハッシュ関数<span class="arithmatex">\(f(x) = x\)</span>では、各要素一意のバケットインデックスに対応し、これは配列と同等です。しかし、入力空間は通常出力空間(配列長)よりはるかに大きいため、ハッシュ関数の最後のステップは配列長の剰余を取ることがよくあります。言い換えると、ハッシュの目は、<span class="arithmatex">\(O(1)\)</span>のクエリ効率を提供しながら、より大きな状態空間をより小さなものにマッピングすることで</p>
<p><strong>Q</strong>: ハッシュ表がこれらの構造を使って実装されているにもかかわらず、なぜ配列、連結リスト、二分木より効率になるのですか?</p>
<p>まず、ハッシュは時間効率が高いですが、空間効率は低いです。ハッシュ表のメモリの大部分未使用のままです</p>
<p>次に、ハッシュ表は特定のユースケースでのみ時間効率が高いです。配列や連結リストを使用して同じ時間計算量で機能を実装できる場合、通常はハッシュ表を使用するよりも高速です。これは、ハッシュ関数の計算がオーバーヘッドを発生させ、時間計算量の定数因子が大きくなるためです</p>
<p>最後に、ハッシュの時間計算量は化する可能性があります。例えば連鎖では、連結リストや赤黒木で検索操作を実行し、これは依然として<span class="arithmatex">\(O(n)\)</span>時間に化するリスクがあります</p>
<p><strong>Q</strong>: 多重ハッシュにも要素を直接削除できないという欠陥がありますか?削除としてマークされた空間は再利用できますか?</p>
<p>重ハッシュはオープンアドレス法の一形態であり、すべてのオープンアドレス法は要素を直接削除できないという欠点があります。要素を削除済みとしてマークする必要があります。マークされた空間は再利用できます。ハッシュ表に新しい要素を挿入する際、ハッシュ関数削除済みとしてマークされた位置を指している場合、その位置は新しい要素によって使用できます。これにより、ハッシュ表のプローブシーケンスを維持しながら、空間の効率的な使用が保証されます</p>
<p><strong>Q</strong>: なぜ線形プローブの検索プロセス中にハッシュ衝突が発生するのですか?</p>
<p>検索プロセス中、ハッシュ関数対応するバケットとキー値ペアを指します。<code>key</code>が一致しない場合、ハッシュ衝突を示します。したがって、線形プローブは正しいキーペア見つるか検索が失敗するまで、事前に決められたステップサイズで下方向に検索します</p>
<p><strong>Q</strong>: なぜハッシュ表のリサイズがハッシュ衝突を緩和できるのですか?</p>
<p>ハッシュ関数の最後のステップは、出力を配列インデックス範囲内に保つために、配列長<span class="arithmatex">\(n\)</span>の剰余を取ることがよくあります。リサイズ時、配列長<span class="arithmatex">\(n\)</span>が変化し、キーに対応するインデックスも変化する可能性があります。以前に同じバケットにマッピングされていたキーが、リサイズ後に複数のバケットに分散される可能性があり、それによってハッシュ衝突が緩和されます</p>
<p><strong>Q</strong>ハッシュテーブルの時間計算量が <span class="arithmatex">\(O(n)\)</span> になるのはどのような場合ですか?</p>
<p>ハッシュ衝突が深刻な場合、ハッシュテーブルの時間計算量は <span class="arithmatex">\(O(n)\)</span> に劣化する。ハッシュ関数の設計が適切で、容量設定が合理的で、衝突が比較的均等な場合、時間計算量は <span class="arithmatex">\(O(1)\)</span> である。プログラミング言語組み込みハッシュテーブルを使うとき、通常は時間計算量を <span class="arithmatex">\(O(1)\)</span> とみなす。</p>
<p><strong>Q</strong>なぜハッシュ関数 <span class="arithmatex">\(f(x) = x\)</span> を使ないのですか? そうすれば衝突は起きません</p>
<p><span class="arithmatex">\(f(x) = x\)</span> というハッシュ関数では、各要素一意のバケットインデックスに対応し、これは配列と等価である。しかし、入力空間は通常出力空間(配列長)よりはるかに大きいため、ハッシュ関数の最後のステップはたいてい配列長の剰余になる。言い換えると、ハッシュテーブルの目は、大きな状態空間をより小さな空間に写像し、<span class="arithmatex">\(O(1)\)</span> の検索効率を提供することである</p>
<p><strong>Q</strong>ハッシュテーブルの基礎実装は配列、連結リスト、二分木なのに、なぜそれらより効率になり得るのですか?</p>
<p>まず、ハッシュテーブルは時間効率が高くなる一方で、空間効率は低くなる。ハッシュテーブルには、かなりの部分未使用のメモリが存在する</p>
<p>次に、時間効率が高くなるのは特定の利用場面に限られる。ある機能が同じ時間計算量で配列や連結リストによって実装できるなら、通常はハッシュテーブルより速い。これは、ハッシュ関数の計算にコストがかかり、時間計算量の定数項がより大きいからである</p>
<p>最後に、ハッシュテーブルの時間計算量は化する可能性がある。たとえば連鎖方式では、連結リストや赤黒木で検索操作を行うため、なお <span class="arithmatex">\(O(n)\)</span> 時間に化するリスクがあ</p>
<p><strong>Q</strong>:二重ハッシュにも要素を直接削除できない欠点がありますか? 削除済みとマークした領域は再利用できますか?</p>
<p>重ハッシュはオープンアドレス法の一であり、オープンアドレス法はいずれも要素を直接削除できないという欠点があるため、削除のマーク付けが必要になる。削除済みとマークされた領域は再利用できる。新しい要素をハッシュテーブルに挿入し、ハッシュ関数によって削除済みとマークされた位置を見つけた場合、その位置は新しい要素に使用できる。こうすることで、ハッシュテーブルの探索系列を変えずに保ちつつ、空間利用率も確保できる</p>
<p><strong>Q</strong>なぜ線形探索では、要素を探すときにハッシュ衝突が発生するのですか?</p>
<p>探索時には、ハッシュ関数対応するバケットとキーと値のペアを見つけ、<code>key</code> が一致しないことが分かると、それはハッシュ衝突を意味する。そのため、線形探索法では事前に設定したステップ幅に従って順に探索し、正しいキーと値のペア見つるか、見つからずに終了するまで続ける</p>
<p><strong>Q</strong>なぜハッシュテーブルの拡張でハッシュ衝突を緩和できるのですか?</p>
<p>ハッシュ関数の最後のステップは、たいてい配列長 <span class="arithmatex">\(n\)</span>の剰余を取り、出力値を配列インデックスの範囲内に収めることである。拡張後は配列長 <span class="arithmatex">\(n\)</span> が変化し、<code>key</code> に対応するインデックスも変化する可能性がある。もともと同じバケットに入っていた複数の <code>key</code> は、拡張後に複数のバケットに割り当てられる可能性があり、それによってハッシュ衝突が緩和され</p>
<p><strong>Q</strong>:高効率な読み書きのためなら、配列を直接使えばよいのではないですか?</p>
<p>データの <code>key</code> が連続した小範囲の整数であれば、配列を直接使えばよく、単純で高効率である。しかし <code>key</code> が別の型(たとえば文字列)の場合は、ハッシュ関数を用いて <code>key</code> を配列インデックスに写像し、さらにバケット配列を通じて要素を格納する必要がある。このような構造がハッシュテーブルである。</p>
<!-- Source file information -->