mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-11 19:30:59 +00:00
deploy
This commit is contained in:
@@ -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 アルゴリズムはどこにでもある
|
||||
1.1 アルゴリズムは至るところにある
|
||||
|
||||
|
||||
|
||||
@@ -646,7 +646,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
1.2 アルゴリズムとは何か
|
||||
1.2 アルゴリズムとは
|
||||
|
||||
|
||||
|
||||
@@ -783,7 +783,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
2.1 アルゴリズムの効率評価
|
||||
2.1 アルゴリズム効率の評価
|
||||
|
||||
|
||||
|
||||
@@ -1060,7 +1060,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
3.3 数値の符号化 *
|
||||
3.3 数値エンコーディング *
|
||||
|
||||
|
||||
|
||||
@@ -1088,7 +1088,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
3.4 文字の符号化 *
|
||||
3.4 文字エンコーディング *
|
||||
|
||||
|
||||
|
||||
@@ -1591,7 +1591,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
第 6 章 ハッシュ表
|
||||
第 6 章 ハッシュテーブル
|
||||
|
||||
|
||||
|
||||
@@ -1613,7 +1613,7 @@
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
|
||||
第 6 章 ハッシュ表
|
||||
第 6 章 ハッシュテーブル
|
||||
|
||||
|
||||
</label>
|
||||
@@ -1635,7 +1635,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
6.1 ハッシュ表
|
||||
6.1 ハッシュテーブル
|
||||
|
||||
|
||||
|
||||
@@ -1942,7 +1942,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
7.5 AVL木 *
|
||||
7.5 AVL 木 *
|
||||
|
||||
|
||||
|
||||
@@ -2105,7 +2105,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
8.2 ヒープ構築操作
|
||||
8.2 ヒープ構築
|
||||
|
||||
|
||||
|
||||
@@ -2491,7 +2491,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
10.2 二分探索の挿入点
|
||||
10.2 二分探索の挿入位置
|
||||
|
||||
|
||||
|
||||
@@ -2547,7 +2547,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
10.4 ハッシュ最適化戦略
|
||||
10.4 ハッシュによる最適化戦略
|
||||
|
||||
|
||||
|
||||
@@ -2575,7 +2575,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
10.5 探索アルゴリズムの再認識
|
||||
10.5 探索アルゴリズム再考
|
||||
|
||||
|
||||
|
||||
@@ -3063,7 +3063,7 @@
|
||||
<a href="#1" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
1. 重要な復習
|
||||
1. 重要なポイントの振り返り
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -3185,7 +3185,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
12.1 分割統治アルゴリズム
|
||||
12.1 分割統治法
|
||||
|
||||
|
||||
|
||||
@@ -3241,7 +3241,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
12.3 木の構築問題
|
||||
12.3 二分木の構築問題
|
||||
|
||||
|
||||
|
||||
@@ -3269,7 +3269,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
12.4 ハノイの塔問題
|
||||
12.4 ハノイの塔の問題
|
||||
|
||||
|
||||
|
||||
@@ -3462,7 +3462,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
13.3 部分集合和問題
|
||||
13.3 部分和問題
|
||||
|
||||
|
||||
|
||||
@@ -3490,7 +3490,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
13.4 Nクイーン問題
|
||||
13.4 n クイーン問題
|
||||
|
||||
|
||||
|
||||
@@ -3631,7 +3631,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
14.1 動的計画法の初歩
|
||||
14.1 動的計画法入門
|
||||
|
||||
|
||||
|
||||
@@ -3659,7 +3659,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
14.2 DP 問題の特性
|
||||
14.2 動的計画法の問題特性
|
||||
|
||||
|
||||
|
||||
@@ -3687,7 +3687,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
14.3 DP の解法の考え方
|
||||
14.3 動的計画法の問題解決の考え方
|
||||
|
||||
|
||||
|
||||
@@ -3715,7 +3715,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
14.4 0-1ナップサック問題
|
||||
14.4 0-1 ナップサック問題
|
||||
|
||||
|
||||
|
||||
@@ -3908,7 +3908,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
15.1 貪欲アルゴリズム
|
||||
15.1 貪欲法
|
||||
|
||||
|
||||
|
||||
@@ -4153,7 +4153,7 @@
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
16.2 一緒に創作に参加する
|
||||
16.2 一緒に制作に参加しましょう
|
||||
|
||||
|
||||
|
||||
@@ -4299,7 +4299,7 @@
|
||||
<a href="#1" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
1. 重要な復習
|
||||
1. 重要なポイントの振り返り
|
||||
|
||||
</span>
|
||||
</a>
|
||||
@@ -4355,37 +4355,37 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="1111">11.11 まとめ<a class="headerlink" href="#1111" title="Permanent link">¶</a></h1>
|
||||
<h3 id="1">1. 重要な復習<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<h3 id="1">1. 重要なポイントの振り返り<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<ul>
|
||||
<li>バブルソートは隣接する要素を交換することで動作します。フラグを追加して早期リターンを可能にすることで、バブルソートの最良ケースの時間計算量を <span class="arithmatex">\(O(n)\)</span> に最適化できます。</li>
|
||||
<li>挿入ソートは、未ソート区間から要素を取り出してソート済み区間の正しい位置に挿入することで各ラウンドをソートします。挿入ソートの時間計算量は <span class="arithmatex">\(O(n^2)\)</span> ですが、単位あたりの操作が比較的少ないため、少量のデータのソートでは非常に人気があります。</li>
|
||||
<li>クイックソートは歩哨分割操作に基づいています。歩哨分割では、常に最悪のピボットを選ぶ可能性があり、時間計算量が <span class="arithmatex">\(O(n^2)\)</span> に劣化する可能性があります。中央値やランダムピボットを導入することで、そのような劣化の確率を減らすことができます。末尾再帰は再帰の深さを効果的に減らし、空間計算量を <span class="arithmatex">\(O(\log n)\)</span> に最適化します。</li>
|
||||
<li>マージソートには分割とマージの2つの段階があり、通常分割統治戦略を体現しています。マージソートでは、配列のソートには補助配列の作成が必要で、空間計算量は <span class="arithmatex">\(O(n)\)</span> になります。しかし、リストのソートの空間計算量は <span class="arithmatex">\(O(1)\)</span> に最適化できます。</li>
|
||||
<li>バケットソートは3つの手順から構成されます:データをバケットに分散、各バケット内でのソート、バケット順での結果のマージ。これも分割統治戦略を体現し、非常に大きなデータセットに適しています。バケットソートの鍵はデータの均等分散です。</li>
|
||||
<li>計数ソートはバケットソートの変形で、各データポイントの出現回数をカウントすることでソートします。計数ソートは限られた範囲のデータを持つ大きなデータセットに適しており、データを正の整数に変換する必要があります。</li>
|
||||
<li>基数ソートは桁ごとにソートすることでデータを処理し、データが固定長の数値として表現される必要があります。</li>
|
||||
<li>全体的に、私たちは高効率、安定性、インプレース操作、適応性を持つソートアルゴリズムを求めています。しかし、他のデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たすソートアルゴリズムは存在しません。実際の応用では、データの特性に基づいて適切なソートアルゴリズムを選択する必要があります。</li>
|
||||
<li>以下の図は、効率性、安定性、インプレース性、適応性の観点から主流のソートアルゴリズムを比較しています。</li>
|
||||
<li>バブルソートは隣接する要素を交換することで整列を行います。フラグを追加して早期リターンを可能にすると、バブルソートの最良時間計算量を <span class="arithmatex">\(O(n)\)</span> に最適化できます。</li>
|
||||
<li>挿入ソートは各ラウンドで未整列区間の要素を整列済み区間の正しい位置に挿入することで整列を完了します。挿入ソートの時間計算量は <span class="arithmatex">\(O(n^2)\)</span> ですが、基本操作が比較的少ないため、小規模データのソート処理で非常に人気があります。</li>
|
||||
<li>クイックソートは番兵分割操作に基づいて整列を行います。番兵分割では毎回最悪の基準値を選んでしまう可能性があり、その結果、時間計算量は <span class="arithmatex">\(O(n^2)\)</span> まで劣化することがあります。中央値の基準値やランダムな基準値を導入すると、この劣化の確率を下げられます。短い部分配列を優先して再帰すれば、再帰の深さを効果的に抑え、空間計算量を <span class="arithmatex">\(O(\log n)\)</span> に最適化できます。</li>
|
||||
<li>マージソートは分割とマージという 2 つの段階からなり、分割統治戦略を典型的に体現しています。マージソートでは配列を整列する際に補助配列の作成が必要で、空間計算量は <span class="arithmatex">\(O(n)\)</span> です。一方、連結リストを整列する場合の空間計算量は <span class="arithmatex">\(O(1)\)</span> まで最適化できます。</li>
|
||||
<li>バケットソートはデータのバケット分配、バケット内ソート、結果の結合という 3 つの手順を含みます。これも分割統治戦略を体現しており、データ量が非常に大きい場合に適しています。バケットソートの鍵は、データを平均的に分配することにあります。</li>
|
||||
<li>カウントソートはバケットソートの特例であり、データの出現回数を数えることで整列を行います。カウントソートはデータ量が大きく、かつデータ範囲が限られている場合に適しており、データを正の整数に変換できることが前提です。</li>
|
||||
<li>基数ソートは各桁ごとの整列によってデータを整列し、データが固定桁数の数値として表せることを前提とします。</li>
|
||||
<li>総じて言えば、私たちは高効率で、安定で、インプレースで、さらに適応的であるといった利点を備えたソートアルゴリズムを見つけたいと考えます。しかし、ほかのデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たせるソートアルゴリズムは存在しません。実際の応用では、データの特性に応じて適切なソートアルゴリズムを選ぶ必要があります。</li>
|
||||
<li>下図では、主流のソートアルゴリズムについて、効率、安定性、インプレース性、適応性などを比較しています。</li>
|
||||
</ul>
|
||||
<p><img alt="ソートアルゴリズムの比較" class="animation-figure" src="../summary.assets/sorting_algorithms_comparison.png" /></p>
|
||||
<p align="center"> 図 11-19 ソートアルゴリズムの比較 </p>
|
||||
|
||||
<h3 id="2-q-a">2. Q & A<a class="headerlink" href="#2-q-a" title="Permanent link">¶</a></h3>
|
||||
<p><strong>Q</strong>: ソートアルゴリズムの安定性はいつ必要ですか?</p>
|
||||
<p>実際には、オブジェクトの一つの属性に基づいてソートする場合があります。例えば、学生は名前と身長の属性を持ち、多段階ソートを実装することを目指します:最初に名前で <code>(A, 180) (B, 185) (C, 170) (D, 170)</code> を取得し、次に身長で。ソートアルゴリズムが不安定なため、<code>(D, 170) (C, 170) (A, 180) (B, 185)</code> になってしまう可能性があります。</p>
|
||||
<p>学生DとCの位置が交換され、名前の順序性が破られているのが分かります。これは望ましくありません。</p>
|
||||
<p><strong>Q</strong>: 歩哨分割での「右から左への検索」と「左から右への検索」の順序を交換できますか?</p>
|
||||
<p>いいえ、最左要素をピボットとして使用する場合、最初に「右から左への検索」を行い、次に「左から右への検索」を行う必要があります。この結論はやや直観に反するので、理由を分析してみましょう。</p>
|
||||
<p>歩哨分割 <code>partition()</code> の最後のステップは <code>nums[left]</code> と <code>nums[i]</code> を交換することです。交換後、ピボットの左側の要素はすべてピボット以下になります。<strong>これには最後の交換前に <code>nums[left] >= nums[i]</code> が成り立つ必要があります</strong>。「左から右への検索」を最初に行い、ピボットより大きい要素が見つからない場合、<strong><code>i == j</code> でループを終了し、<code>nums[j] == nums[i] > nums[left]</code> となる可能性があります</strong>。つまり、最終交換操作はピボットより大きい要素を配列の左端に交換し、歩哨分割を失敗させます。</p>
|
||||
<p>例えば、配列 <code>[0, 0, 0, 0, 1]</code> が与えられた場合、最初に「左から右への検索」を行うと、歩哨分割後の配列は <code>[1, 0, 0, 0, 0]</code> となり、これは正しくありません。</p>
|
||||
<p>さらに考えると、<code>nums[right]</code> をピボットとして選択する場合、まったく逆で、最初に「左から右への検索」を行う必要があります。</p>
|
||||
<p><strong>Q</strong>: 末尾再帰最適化について、短い配列を選択することで再帰の深さが <span class="arithmatex">\(\log n\)</span> を超えないことを保証するのはなぜですか?</p>
|
||||
<p>再帰の深さは現在リターンしていない再帰メソッドの数です。歩哨分割の各ラウンドは元の配列を2つの副配列に分割します。末尾再帰最適化により、再帰的に続行する副配列の長さは最大でも元の配列長の半分です。最悪の場合常に長さを半分にすると仮定すると、最終的な再帰の深さは <span class="arithmatex">\(\log n\)</span> になります。</p>
|
||||
<p>元のクイックソートを見直すと、より大きな配列を継続的に再帰処理する可能性があり、最悪の場合 <span class="arithmatex">\(n\)</span>、<span class="arithmatex">\(n - 1\)</span>、...、<span class="arithmatex">\(2\)</span>、<span class="arithmatex">\(1\)</span> で、再帰の深さは <span class="arithmatex">\(n\)</span> になります。末尾再帰最適化はこのシナリオを回避できます。</p>
|
||||
<p><strong>Q</strong>: 配列のすべての要素が等しい場合、クイックソートの時間計算量は <span class="arithmatex">\(O(n^2)\)</span> ですか?この劣化ケースをどう処理すべきですか?</p>
|
||||
<p>はい。この状況については、歩哨分割を使用して配列をピボットより小さい、等しい、大きいの3つの部分に分割することを検討してください。小さい部分と大きい部分のみを再帰的に進めます。この方法では、すべての入力要素が等しい配列を1ラウンドの歩哨分割だけでソートできます。</p>
|
||||
<p><strong>Q</strong>: なぜバケットソートの最悪ケース時間計算量は <span class="arithmatex">\(O(n^2)\)</span> ですか?</p>
|
||||
<p>最悪の場合、すべての要素が同じバケットに配置されます。これらの要素をソートするために <span class="arithmatex">\(O(n^2)\)</span> アルゴリズムを使用する場合、時間計算量は <span class="arithmatex">\(O(n^2)\)</span> になります。</p>
|
||||
<p><strong>Q</strong>:ソートアルゴリズムの安定性は、どのような場合に必須ですか?</p>
|
||||
<p>現実には、オブジェクトのある属性に基づいて整列することがあります。たとえば、学生には氏名と身長という 2 つの属性があり、多段階のソートを行いたいとします。まず氏名で整列して <code>(A, 180) (B, 185) (C, 170) (D, 170)</code> を得て、その後に身長で整列します。ソートアルゴリズムが不安定である場合、結果は <code>(D, 170) (C, 170) (A, 180) (B, 185)</code> になる可能性があります。</p>
|
||||
<p>このように、学生 D と C の位置が入れ替わり、氏名に関する順序性が壊れてしまいます。これは望ましくありません。</p>
|
||||
<p><strong>Q</strong>:番兵分割において、「右から左へ探索する」順序と「左から右へ探索する」順序は入れ替えられますか?</p>
|
||||
<p>できません。最も左端の要素を基準値とする場合は、必ず先に「右から左へ探索する」を行い、その後に「左から右へ探索する」を行う必要があります。この結論はやや直感に反するので、理由を分析してみましょう。</p>
|
||||
<p>番兵分割 <code>partition()</code> の最後の手順は、<code>nums[left]</code> と <code>nums[i]</code> を交換することです。交換が終わると、基準値の左側にある要素はすべて基準値 <code><=</code> になります。<strong>したがって、最後の交換の前に <code>nums[left] >= nums[i]</code> が必ず成り立っていなければなりません</strong>。仮に先に「左から右へ探索する」を行うと、基準値より大きい要素が見つからない場合、<strong><code>i == j</code> の時点でループを抜け、このとき <code>nums[j] == nums[i] > nums[left]</code> となる可能性があります</strong>。つまり、この最後の交換によって、基準値より大きい要素が配列の最左端へ移されてしまい、番兵分割は失敗します。</p>
|
||||
<p>たとえば、配列 <code>[0, 0, 0, 0, 1]</code> が与えられたとき、先に「左から右へ探索する」を行うと、番兵分割後の配列は <code>[1, 0, 0, 0, 0]</code> になります。これは誤った結果です。</p>
|
||||
<p>さらに考えると、<code>nums[right]</code> を基準値に選ぶ場合はちょうど逆になり、必ず先に「左から右へ探索する」を行う必要があります。</p>
|
||||
<p><strong>Q</strong>:クイックソートの再帰深度最適化について、短い配列を選ぶとなぜ再帰深度が <span class="arithmatex">\(\log n\)</span> を超えないと保証できるのですか?</p>
|
||||
<p>再帰深度とは、現在まだ戻っていない再帰呼び出しの数のことです。各ラウンドの番兵分割では、元の配列を 2 つの部分配列に分けます。再帰深度の最適化後は、下方向に再帰する部分配列の長さは最大でも元の配列長の半分です。最悪の場合でも毎回半分の長さになると仮定すれば、最終的な再帰深度は <span class="arithmatex">\(\log n\)</span> になります。</p>
|
||||
<p>元のクイックソートを振り返ると、長いほうの配列に対して連続して再帰してしまう可能性があり、最悪の場合は <span class="arithmatex">\(n\)</span>、<span class="arithmatex">\(n - 1\)</span>、<span class="arithmatex">\(\dots\)</span>、<span class="arithmatex">\(2\)</span>、<span class="arithmatex">\(1\)</span> と続き、再帰深度は <span class="arithmatex">\(n\)</span> になります。再帰深度の最適化により、このような状況を避けられます。</p>
|
||||
<p><strong>Q</strong>:配列内のすべての要素が等しい場合、クイックソートの時間計算量は <span class="arithmatex">\(O(n^2)\)</span> になりますか?このような退化はどう処理すべきですか?</p>
|
||||
<p>はい。この場合は、番兵分割によって配列を「基準値より小さい」「基準値に等しい」「基準値より大きい」の 3 つの部分に分ける方法を検討できます。下方向に再帰するのは、小さい部分と大きい部分だけです。この方法では、入力要素がすべて等しい配列は、1 回の番兵分割だけで整列を完了できます。</p>
|
||||
<p><strong>Q</strong>:バケットソートの最悪時間計算量が <span class="arithmatex">\(O(n^2)\)</span> なのはなぜですか?</p>
|
||||
<p>最悪の場合、すべての要素が同じバケットに振り分けられます。その要素群を整列するのに <span class="arithmatex">\(O(n^2)\)</span> のアルゴリズムを使えば、時間計算量は <span class="arithmatex">\(O(n^2)\)</span> になります。</p>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user