mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-20 02:26:11 +00:00
Re-translate the Japanese version (#1871)
* Retranslate Japanese docs with GPT-5.4 * Retranslate Japanese code with GPT-5.4
This commit is contained in:
@@ -1,27 +1,27 @@
|
||||
# 二分探索
|
||||
|
||||
<u>二分探索</u>は分割統治戦略を用いる効率的な探索アルゴリズムです。配列内の要素の整列順序を利用し、各反復で探索区間を半分に減らしながら、目標要素が見つかるか探索区間が空になるまで続行します。
|
||||
<u>二分探索(binary search)</u>は分割統治法に基づく効率的な探索アルゴリズムです。データが整列済みである性質を利用し、各ラウンドで探索範囲を半分に縮小し、目標要素を見つけるか探索区間が空になるまで続けます。
|
||||
|
||||
!!! question
|
||||
|
||||
長さ$n$の配列`nums`が与えられ、要素は重複なしで昇順に配列されています。この配列内の要素`target`のインデックスを見つけて返してください。配列に要素が含まれていない場合は$-1$を返してください。例を下図に示します。
|
||||
長さ $n$ の配列 `nums` が与えられます。要素は小さい順に並んでおり、重複しません。要素 `target` がこの配列内にある場合はそのインデックスを返し、含まれない場合は $-1$ を返してください。例を次の図に示します。
|
||||
|
||||

|
||||

|
||||
|
||||
下図に示すように、まず$i = 0$と$j = n - 1$でポインタを初期化し、それぞれ配列の最初と最後の要素を指します。これらはまた全体の探索区間$[0, n - 1]$を表します。角括弧は閉区間を示し、境界値自身も含むことに注意してください。
|
||||
次の図に示すように、まずポインタ $i = 0$ と $j = n - 1$ を初期化し、それぞれ配列の先頭要素と末尾要素を指すようにして、探索区間 $[0, n - 1]$ を表します。角括弧は閉区間を表し、境界値自体を含むことに注意してください。
|
||||
|
||||
そして、以下の2つのステップをループで実行する可能性があります。
|
||||
次に、以下の 2 つの手順を繰り返します。
|
||||
|
||||
1. 中点インデックス$m = \lfloor {(i + j) / 2} \rfloor$を計算します。ここで$\lfloor \: \rfloor$は床関数を表します。
|
||||
2. `nums[m]`と`target`の比較に基づいて、以下の3つのケースのうち1つを選択して実行します。
|
||||
1. `nums[m] < target`の場合、`target`は区間$[m + 1, j]$にあることを示すため、$i = m + 1$とします。
|
||||
2. `nums[m] > target`の場合、`target`は区間$[i, m - 1]$にあることを示すため、$j = m - 1$とします。
|
||||
3. `nums[m] = target`の場合、`target`が見つかったことを示すため、インデックス$m$を返します。
|
||||
1. 中央のインデックス $m = \lfloor {(i + j) / 2} \rfloor$ を計算します。ここで $\lfloor \: \rfloor$ は切り捨てを表します。
|
||||
2. `nums[m]` と `target` の大小関係を判定し、次の 3 つの場合に分かれます。
|
||||
1. `nums[m] < target` のとき、`target` は区間 $[m + 1, j]$ にあるため、$i = m + 1$ を実行します。
|
||||
2. `nums[m] > target` のとき、`target` は区間 $[i, m - 1]$ にあるため、$j = m - 1$ を実行します。
|
||||
3. `nums[m] = target` のとき、`target` が見つかったので、インデックス $m$ を返します。
|
||||
|
||||
配列に目標要素が含まれていない場合、探索区間は最終的に空になり、$-1$を返して終了します。
|
||||
配列に目標要素が含まれない場合、探索区間は最終的に空まで縮小されます。このとき $-1$ を返します。
|
||||
|
||||
=== "<1>"
|
||||

|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
@@ -41,43 +41,43 @@
|
||||
=== "<7>"
|
||||

|
||||
|
||||
$i$と$j$が両方とも`int`型であるため、**$i + j$は`int`型の範囲を超える可能性がある**ことは注目に値します。大きな数のオーバーフローを避けるため、通常は式$m = \lfloor {i + (j - i) / 2} \rfloor$を使用して中点を計算します。
|
||||
注意すべき点として、$i$ と $j$ はどちらも `int` 型であるため、**$i + j$ が `int` 型の範囲を超える可能性があります**。大きな数によるオーバーフローを避けるため、通常は式 $m = \lfloor {i + (j - i) / 2} \rfloor$ を用いて中点を計算します。
|
||||
|
||||
コードは以下の通りです:
|
||||
コードは次のとおりです。
|
||||
|
||||
```src
|
||||
[file]{binary_search}-[class]{}-[func]{binary_search}
|
||||
```
|
||||
|
||||
**時間計算量は$O(\log n)$です**:二分ループにおいて、区間は各ラウンドで半分に減少するため、反復回数は$\log_2 n$となります。
|
||||
**時間計算量は $O(\log n)$** :二分探索のループでは各ラウンドで区間が半分になるため、ループ回数は $\log_2 n$ です。
|
||||
|
||||
**空間計算量は$O(1)$です**:ポインタ$i$と$j$は定数サイズの空間を占有します。
|
||||
**空間計算量は $O(1)$** :ポインタ $i$ と $j$ に必要なのは定数サイズの空間だけです。
|
||||
|
||||
## 区間表現方法
|
||||
## 区間の表し方
|
||||
|
||||
上記の閉区間の他に、もう一つの一般的な区間表現は「左閉右開」区間で、$[0, n)$として定義され、左境界は自身を含み、右境界は含みません。この表現では、$i = j$のとき区間$[i, j)$は空になります。
|
||||
上記の両閉区間のほかに、一般的な区間表現として「左閉右開」区間があり、$[0, n)$ と定義されます。つまり左端は含み、右端は含みません。この表現では、区間 $[i, j)$ は $i = j$ のとき空です。
|
||||
|
||||
この表現に基づいて同じ機能を持つ二分探索アルゴリズムを実装できます:
|
||||
この表現に基づいて、同じ機能を持つ二分探索アルゴリズムを実装できます。
|
||||
|
||||
```src
|
||||
[file]{binary_search}-[class]{}-[func]{binary_search_lcro}
|
||||
```
|
||||
|
||||
下図に示すように、2つの区間表現タイプにおいて、二分探索アルゴリズムの初期化、ループ条件、区間縮小操作が異なります。
|
||||
次の図に示すように、2 種類の区間表現では、二分探索アルゴリズムの初期化、ループ条件、区間の縮小操作がそれぞれ異なります。
|
||||
|
||||
「閉区間」表現では両方の境界が包含的であるため、ポインタ$i$と$j$による区間縮小操作も対称的です。これによりエラーが発生しにくくなるため、**一般的に「閉区間」アプローチの使用が推奨されます**。
|
||||
「両閉区間」の表現では左右の境界がどちらも閉区間として定義されるため、ポインタ $i$ とポインタ $j$ による区間縮小の操作も対称になります。このほうがミスをしにくいため、**一般には「両閉区間」の書き方を推奨します**。
|
||||
|
||||

|
||||

|
||||
|
||||
## 利点と制限
|
||||
## 利点と限界
|
||||
|
||||
二分探索は時間と空間の両方の面で良好な性能を示します。
|
||||
二分探索は時間と空間の両面で優れた性能を持ちます。
|
||||
|
||||
- 二分探索は時間効率が良いです。大きなデータセットでは、対数時間計算量が大きな利点を提供します。例えば、サイズ$n = 2^{20}$のデータセットが与えられた場合、線形探索は$2^{20} = 1048576$回の反復が必要ですが、二分探索は$\log_2 2^{20} = 20$回のループのみで済みます。
|
||||
- 二分探索には追加の空間が必要ありません。追加の空間に依存する探索アルゴリズム(ハッシュ探索など)と比較して、二分探索はより空間効率的です。
|
||||
- 二分探索は時間効率が高いです。データ量が大きい場合、対数時間計算量は大きな優位性を持ちます。たとえば、データサイズ $n = 2^{20}$ のとき、線形探索では $2^{20} = 1048576$ 回のループが必要ですが、二分探索では $\log_2 2^{20} = 20$ 回で済みます。
|
||||
- 二分探索は追加の空間を必要としません。追加領域を要する探索アルゴリズム(たとえばハッシュ探索)と比べて、二分探索はより省メモリです。
|
||||
|
||||
しかし、二分探索は以下の懸念により、すべてのシナリオに適しているとは限りません。
|
||||
しかし、二分探索があらゆる状況に適しているわけではなく、主な理由は次のとおりです。
|
||||
|
||||
- 二分探索はソート済みデータにのみ適用できます。未ソートのデータは二分探索を適用する前にソートする必要があり、ソートアルゴリズムは通常$O(n \log n)$の時間計算量を持つため、これは価値がないかもしれません。このコストは線形探索よりも高く、二分探索自体は言うまでもありません。頻繁な挿入があるシナリオでは、配列を順序に保つコストは非常に高く、特定の位置に新しい要素を挿入する時間計算量は$O(n)$です。
|
||||
- 二分探索は配列のみを使用できます。二分探索には非連続(ジャンプ)要素アクセスが必要で、これは連結リストでは非効率的です。そのため、連結リストや連結リストに基づくデータ構造はこのアルゴリズムに適していない可能性があります。
|
||||
- 線形探索は小さなデータセットでより良い性能を示します。線形探索では各反復で1つの判定操作のみが必要ですが、二分探索では1つの加算、1つの除算、1つから3つの判定操作、1つの加算(減算)を含み、合計4つから6つの操作が必要です。そのため、データサイズ$n$が小さい場合、線形探索は二分探索よりも高速です。
|
||||
- 二分探索は整列済みデータにしか適用できません。入力データが無秩序な場合、二分探索を使うためだけにソートするのは割に合いません。ソートアルゴリズムの時間計算量は通常 $O(n \log n)$ であり、線形探索や二分探索よりも高いからです。要素を頻繁に挿入する場面では、配列の整列性を保つために特定位置へ挿入する必要があり、その時間計算量は $O(n)$ と高コストです。
|
||||
- 二分探索は配列にしか適していません。二分探索では要素へ飛び飛びにアクセスする必要がありますが、連結リストでそのようなアクセスを行う効率は低いため、連結リストやそれを基に実装されたデータ構造には向きません。
|
||||
- データ量が小さい場合は線形探索のほうが高性能です。線形探索では各ラウンドで 1 回の比較だけで済みますが、二分探索では 1 回の加算、1 回の除算、1 ~ 3 回の比較、1 回の加算(減算)が必要で、合計 4 ~ 6 個の基本操作になります。したがって、データ量 $n$ が小さいときは、線形探索のほうがかえって速くなります。
|
||||
|
||||
Reference in New Issue
Block a user