二分探索

ソート済み配列を半分ずつ絞り込んで O(log n) で要素を検索するアルゴリズム

アルゴリズムデータ構造

二分探索とは

二分探索 (Binary Search) は、ソート済み配列の中央要素と比較し、探索範囲を半分ずつ絞り込んで O(log n) で要素を検索するアルゴリズムである。線形探索の O(n) と比べて、100 万件のデータでも最大 20 回の比較で見つかる。

計算量の比較

データ件数 線形探索 (最悪) 二分探索 (最悪)
100 100 回 7 回
10,000 10,000 回 14 回
1,000,000 1,000,000 回 20 回

TypeScript での実装

function binarySearch(arr: number[], target: number): number {
  let left = 0;
  let right = arr.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1; // 見つからない
}

binarySearch([1, 3, 5, 7, 9, 11], 7); // 3 (インデックス)

動作の流れ

配列: [1, 3, 5, 7, 9, 11, 13]  target: 9

Step 1: left=0, right=6, mid=3 → arr[3]=7 < 9left=4
Step 2: left=4, right=6, mid=5 → arr[5]=11 > 9right=4
Step 3: left=4, right=4, mid=4 → arr[4]=9 = 9 → found!

前提条件

二分探索は配列がソート済みであることが前提。ソートされていない配列には使えない。

// ❌ ソートされていない配列
binarySearch([3, 1, 7, 5, 9], 7); // 正しく動作しない

// ✅ ソート済み配列
binarySearch([1, 3, 5, 7, 9], 7); // 3

応用: lower_bound / upper_bound

// target 以上の最小インデックスを返す
function lowerBound(arr: number[], target: number): number {
  let left = 0, right = arr.length;
  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] < target) left = mid + 1;
    else right = mid;
  }
  return left;
}

実際の利用場面

場面 説明
DB インデックス B-Tree の各ノード内で二分探索
Git bisect バグが混入したコミットを二分探索で特定
API のページネーション カーソルベースのページネーション
配列の検索 Array.prototype.findIndex の高速版

git bisect

git bisect start
git bisect bad          # 現在のコミットはバグあり
git bisect good abc123  # このコミットはバグなし
# Git が中間のコミットをチェックアウト → テスト → good/bad を繰り返す
# O(log n) でバグの混入コミットを特定

基礎から学ぶなら関連書籍が手がかりになる。

関連用語