Files
hello-algo/ja/docs/chapter_searching/binary_search.md
Yudong Jin 7257641e0e Retranslate the complete Japanese online edition from Chinese (#2008)
* 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
2026-10-30 02:12:26 +08:00

84 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 二分探索
<u>二分探索(binary search)</u>は、分割統治法に基づく効率的な探索アルゴリズムです。データが整列されていることを利用し、各反復で探索範囲を半分に縮め、目的の要素が見つかるか探索区間が空になるまで続けます。
!!! question
長さ $n$ の配列 `nums` が与えられます。要素は昇順に並び、重複しません。要素 `target` を探し、そのインデックスを返してください。配列にその要素が含まれない場合は $-1$ を返します。例を下図に示します。
![二分探索の入力例](binary_search.assets/binary_search_example.png)
下図のように、まずポインタ $i = 0$ と $j = n - 1$ を初期化し、配列の先頭要素と末尾要素をそれぞれ指すようにします。これらは探索区間 $[0, n - 1]$ を表します。角括弧は閉区間を表し、境界値そのものを含むことに注意してください。
続いて、次の二つの手順を繰り返します。
1. 中点のインデックス $m = \lfloor {(i + j) / 2} \rfloor$ を計算します。ここで $\lfloor \: \rfloor$ は切り捨てを表します。
2. `nums[m]` と `target` の大小関係を判定します。次の三つの場合があります。
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>"
![二分探索の流れ](binary_search.assets/binary_search_step1.png)
=== "<2>"
![二分探索の手順 2](binary_search.assets/binary_search_step2.png)
=== "<3>"
![二分探索の手順 3](binary_search.assets/binary_search_step3.png)
=== "<4>"
![二分探索の手順 4](binary_search.assets/binary_search_step4.png)
=== "<5>"
![二分探索の手順 5](binary_search.assets/binary_search_step5.png)
=== "<6>"
![二分探索の手順 6](binary_search.assets/binary_search_step6.png)
=== "<7>"
![二分探索の手順 7](binary_search.assets/binary_search_step7.png)
$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(1)$**:ポインタ $i$ と $j$ が使う領域は定数サイズです。
## 区間の表し方
前述の両端を含む閉区間のほか、「左閉右開」の区間もよく使われます。$[0, n)$ と表し、左端は含みますが右端は含みません。この表し方では、区間 $[i, j)$ は $i = j$ のとき空になります。
この表し方でも、同じ機能を持つ二分探索を実装できます。
```src
[file]{binary_search}-[class]{}-[func]{binary_search_lcro}
```
下図のように、区間の表し方が異なると、二分探索の初期化、ループ条件、区間を縮める操作も変わります。
両端を含む閉区間では、左右の境界がどちらも含まれるため、ポインタ $i$ と $j$ で区間を縮める操作も対称になります。そのほうが間違いにくいため、**一般には両端を含む閉区間で書くことを勧めます**。
![二つの区間の定義](binary_search.assets/binary_search_ranges.png)
## 長所と制約
二分探索は時間と空間の両面で優れた性能を持ちます。
- 二分探索は時間効率が高く、データが多い場合、対数時間の計算量が大きな利点となります。例えば、データの大きさが $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$ が小さいと、線形探索のほうが速くなります。