* 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
5.7 KiB
二分探索
二分探索(binary search)は、分割統治法に基づく効率的な探索アルゴリズムです。データが整列されていることを利用し、各反復で探索範囲を半分に縮め、目的の要素が見つかるか探索区間が空になるまで続けます。
!!! question
長さ $n$ の配列 `nums` が与えられます。要素は昇順に並び、重複しません。要素 `target` を探し、そのインデックスを返してください。配列にその要素が含まれない場合は $-1$ を返します。例を下図に示します。
下図のように、まずポインタ i = 0 と j = n - 1 を初期化し、配列の先頭要素と末尾要素をそれぞれ指すようにします。これらは探索区間 [0, n - 1] を表します。角括弧は閉区間を表し、境界値そのものを含むことに注意してください。
続いて、次の二つの手順を繰り返します。
- 中点のインデックス
m = \lfloor {(i + j) / 2} \rfloorを計算します。ここで\lfloor \: \rfloorは切り捨てを表します。 nums[m]とtargetの大小関係を判定します。次の三つの場合があります。nums[m] < targetなら、targetは区間[m + 1, j]にあるため、i = m + 1とします。nums[m] > targetなら、targetは区間[i, m - 1]にあるため、j = m - 1とします。nums[m] = targetなら、targetが見つかったので、インデックスmを返します。
配列に目的の要素が含まれない場合、探索区間は最終的に空になります。このときは -1 を返します。
i と j はともに int 型なので、i + j が int 型の値の範囲を超える可能性があることに注意してください。大きな値によるオーバーフローを避けるため、通常は m = \lfloor {i + (j - i) / 2} \rfloor という式で中点を計算します。
コードは次のとおりです。
[file]{binary_search}-[class]{}-[func]{binary_search}
時間計算量は $O(\log n)$:二分するループでは各反復で区間が半分になるため、反復回数は \log_2 n です。
空間計算量は $O(1)$:ポインタ i と j が使う領域は定数サイズです。
区間の表し方
前述の両端を含む閉区間のほか、「左閉右開」の区間もよく使われます。[0, n) と表し、左端は含みますが右端は含みません。この表し方では、区間 [i, j) は i = j のとき空になります。
この表し方でも、同じ機能を持つ二分探索を実装できます。
[file]{binary_search}-[class]{}-[func]{binary_search_lcro}
下図のように、区間の表し方が異なると、二分探索の初期化、ループ条件、区間を縮める操作も変わります。
両端を含む閉区間では、左右の境界がどちらも含まれるため、ポインタ i と j で区間を縮める操作も対称になります。そのほうが間違いにくいため、一般には両端を含む閉区間で書くことを勧めます。
長所と制約
二分探索は時間と空間の両面で優れた性能を持ちます。
- 二分探索は時間効率が高く、データが多い場合、対数時間の計算量が大きな利点となります。例えば、データの大きさが
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が小さいと、線形探索のほうが速くなります。








