14 二分探索

「半分ずつ捨てる」ことで、探す回数を一気に減らす。

電話帳や辞書で目的の言葉を探すとき、最初から1ページずつ見る人はほとんどいません。 だいたい真ん中を開き、目的の言葉が前にあるか後ろにあるかを判断して、探す範囲を狭めます。

二分探索は、この考え方を配列に対して行う探索アルゴリズムです。 線形探索より高速ですが、配列が整列済みであることが大前提です。
この資料では、配列の要素番号は1から始まるものとします。 配列は昇順(小さい順)に整列済みとして説明します。

1. 二分探索の考え方

二分探索では、探す範囲の左端を low、右端を high、 中央の位置を mid として持ちます。 中央の値 a[mid] と探したい値 key を比較し、不要な半分を捨てます。

比較結果次に探す範囲更新
a[mid] = key見つかったmid を返す
a[mid] < key右半分だけ探すlow ← mid + 1
a[mid] > key左半分だけ探すhigh ← mid - 1
重要: 二分探索は、配列が整列されていないと使えません。 整列されていない配列で中央の値を見ても、左側に小さい値、右側に大きい値があるとは限らないためです。

仕組みの図解(a = {3, 8, 12, 17, 21, 28, 35}、key = 21)

ステップ1:全体を探す。mid = 4 [1] 3 [2] 8 [3] 12 [4] 17 [5] 21 [6] 28 [7] 35 low mid high 17 < 21 右半分だけ探す ステップ2:low = 5, high = 7。mid = 6 3 8 12 17 [5] 21 [6] 28 [7] 35 low mid high 28 > 21 左半分だけ探す ステップ3:low = high = mid = 5 3 8 12 17 [5] 21 28 35 low=mid=high 21 = 21 位置5を返す

2. コード

div は整数除算を表します。 例えば 7 div 23 です。

○整数型: binary_search(整数型の配列: a, 整数型: key)
    整数型: low, high, mid

    low ← 1
    high ← a の要素数

    while ( low が high 以下 )
        mid ← (low + high) div 2

        if ( a[mid] が key と等しい )
            return mid
        elseif ( a[mid] が key より小さい )
            low ← mid + 1
        else
            high ← mid - 1
        endif
    endwhile

    return -1
low が high 以下 の間だけ繰り返す理由:
lowhigh の間が、まだ探す候補が残っている範囲です。 lowhigh を超えたら、候補がなくなったので「見つからなかった」と判断します。

トレース表(a = {3, 8, 12, 17, 21, 28, 35}、key = 21)

回数lowhighmida[mid]比較次の動き
11741717 < 21low ← 5
25762828 > 21high ← 5
35552121 = 215 を返す

3. 見つからない場合

探したい値が配列に存在しない場合も、同じように範囲を半分ずつ狭めます。 最後に lowhigh を超えたら、探索範囲が空になったという意味です。

例(a = {3, 8, 12, 17, 21, 28, 35}、key = 10)

回数lowhighmida[mid]比較次の動き
11741717 > 10high ← 3
213288 < 10low ← 3
33331212 > 10high ← 2
終了32--low > high-1 を返す
-1 を返す意味: この資料では、見つからなかったことを表す値として -1 を使います。 配列の要素番号は1から始まるため、-1 は実在する位置と混同しません。

4. 線形探索との比較

項目線形探索二分探索
配列の条件整列されていなくてもよい整列済みである必要がある
調べ方先頭から順番に調べる中央を見て半分ずつ範囲を捨てる
最大比較回数の目安要素数 n 回約 log2 n 回
向いている場面小さい配列、未整列のデータ大きく、整列済みのデータ
要素数が1,024個でも、二分探索なら最大10〜11回程度。
1,024 → 512 → 256 → 128 → 64 → 32 → 16 → 8 → 4 → 2 → 1 のように、 探す候補が半分ずつ減るためです。

5. 試験でよく出る注意点

low ← midhigh ← mid にしない理由: mid の位置はすでに調べ終わっています。 mid + 1 または mid - 1 にしないと、同じ位置を何度も調べて無限ループになることがあります。

6. 理解度チェック問題

問題14-1

二分探索を使うための前提条件として、最も適切なものはどれか。

解説を表示 正解:イ
二分探索では、中央の値を見て左半分か右半分を捨てます。 その判断ができるのは、配列が昇順または降順に整列されている場合だけです。

問題14-2

要素番号が1から始まる配列で、low = 1high = 9 のとき、mid ← (low + high) div 2 の結果はどれか。

解説を表示 正解:ウ
(1 + 9) div 2 = 10 div 2 = 5 です。 div は整数除算なので、整数の位置を求めるときに使います。

問題14-3 空欄補充

次の二分探索のコードで、空欄 [ A ] に入る条件として適切なものはどれか。

low ← 1
high ← a の要素数

while ( [ A ] )
    mid ← (low + high) div 2
    ...
endwhile
解説を表示 正解:ア
low から high までが探索範囲です。 lowhigh 以下なら、まだ調べる候補が残っています。

問題14-4 空欄補充

昇順に整列された配列で a[mid]key より小さいとき、次に行う更新として正しいものはどれか。

elseif ( a[mid] が key より小さい )
    [ B ]
endif
解説を表示 正解:エ
昇順では a[mid] より右側に、より大きな値があります。 a[mid] はすでに調べたので、次の探索範囲の左端は mid + 1 になります。

問題14-5

配列 {2, 5, 9, 13, 18, 21} から 13 を二分探索で探す。最初の比較で調べる値はどれか。要素番号は1から始まる。

解説を表示 正解:イ
low = 1high = 6 なので、mid = (1 + 6) div 2 = 3。 最初に調べる値は a[3] = 9 です。

問題14-6

配列 {4, 7, 10, 15, 19, 23, 30} から 19 を二分探索で探す。次のトレースの空欄 [ C ] に入る値はどれか。

1回目: low = 1, high = 7, mid = 4, a[mid] = 15
        15 < 19 なので low ← 5
2回目: low = 5, high = 7, mid = 6, a[mid] = 23
        23 > 19 なので high ← 5
3回目: low = 5, high = 5, mid = 5, a[mid] = [ C ]
解説を表示 正解:ウ
要素番号5の値は 19 です。 3回目で a[5] = key となるため、位置5を返します。

問題14-7

二分探索で、探す値が配列に存在しないと判断する状態として適切なものはどれか。

解説を表示 正解:ア
low から high までが探索範囲です。 low > high になると、探索範囲が空になったことを表します。

問題14-8

次の説明として正しいものはどれか。

解説を表示 正解:エ
二分探索の特徴は、中央の値を使って探索範囲を半分ずつ狭めることです。 そのため、整列済みの大きな配列に対して有効です。