선형 vs 이진 탐색 — log n이 왜 마법인가
정렬된 데이터에서 절반씩 잘라 찾기. DB 인덱스·Git·Up to Date 검색의 공통 원리.
데이터 100만 건에서 선형 탐색은 최악 100만 번, 이진 탐색은 20번이면 끝난다. 이 개념이 B-Tree 인덱스·Git bisect·Array.sort 이진 검색의 바탕. 해시맵과 함께 AI 코드를 빠르게 만드는 2대 무기.
이진 탐색은 '업다운 게임'이다. 1~100 중 숫자 맞히기 게임에서 "50?" → "너무 커" → "25?" → "너무 작아" → "37?" ... 매 질문마다 후보가 절반으로 준다. 100만 건도 이 방식이면 20번이면 끝난다(`log₂ 1,000,000 ≈ 20`).
| 데이터 크기 | 선형 탐색 (최악) | 이진 탐색 (최악) |
|---|---|---|
| 100 | 100 | 7 |
| 10,000 | 10,000 | 14 |
| 1,000,000 | 1,000,000 | 20 |
| 10억 | 10억 | 30 |
| 전제조건 | 없음 | 정렬되어 있어야 함 |
// ❌ 선형 탐색 — O(n), 정렬 필요 없음
function linearSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i;
}
return -1;
}
// ✅ 이진 탐색 — O(log n), 반드시 정렬되어 있어야 함
function binarySearch(sortedArr, target) {
let lo = 0, hi = sortedArr.length - 1;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
if (sortedArr[mid] === target) return mid;
if (sortedArr[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1;
}
// 실전: 정렬 + 이진 탐색 자동 내장 (JS는 메서드 없음, 파이썬·Go는 있음)
// Python: import bisect; bisect.bisect_left(sorted_list, target)
// Go: sort.SearchInts(arr, target)JS는 표준 이진탐색 메서드가 없어서 직접 쓰거나 lodash의 `sortedIndexOf`를 쓴다. 단 배열이 정렬돼 있지 않으면 결과가 엉망이 되므로 주의.
바이브코더가 매일 쓰는 이진 탐색들. 직접 구현할 일은 드물지만, 내부 원리를 알면 AI 코드·도구 동작이 투명해진다.
| 어디에 | 이진 탐색을 어떻게 | 효과 |
|---|---|---|
| DB 인덱스 (B-Tree) | 정렬된 트리를 내려가며 탐색 | 100만 행에서 0.001초 |
| Git bisect | 커밋 히스토리 이분하여 버그 커밋 찾기 | 1000 커밋을 10번으로 |
| 파일 시스템 (정렬된 디렉토리) | readdir 결과를 이진 탐색 | `find`가 빠른 이유 |
| 자동완성·사전 | 정렬된 단어 사전에서 prefix 이분 | 즉시 제안 |
| k번째 원소, 범위 쿼리 | 이진 탐색 변형 (lower_bound) | 정렬 + O(log n) |
# Git bisect — 1000개 커밋 중 버그 심은 커밋 찾기
# 선형이면 1000개 commit checkout, 이진이면 log₂(1000) ≈ 10번
git bisect start
git bisect bad # 현재 (버그 있음)
git bisect good v1.0 # v1.0 시점은 정상
# Git이 자동으로 중간 커밋으로 이동
# 테스트 후:
git bisect good # 또는 bad
# → 계속 절반씩 범위를 좁힘
git bisect reset # 종료"언제부터 망가졌지?" 디버깅 상황의 최강 무기. 이진 탐색의 실전 예시.
이진 탐색의 변형 두 가지를 알면 실전에서 90% 상황이 커버된다. `lower_bound(x)` = `x 이상인 첫 번째 위치`, `upper_bound(x)` = `x보다 큰 첫 번째 위치`. 이 둘로 범위 카운팅과 정렬 유지 삽입이 모두 가능하다.
대규모 점수 랭킹·타임라인 조회에서 선형으로 세면 1초, bisect로는 즉시. JS는 `lodash.sortedIndex`로 유사 기능.
치명적 함정. ① 정렬되지 않은 배열에 이진 탐색 쓰면 결과가 완전 엉망이 된다 — 실패도 아니고 오답이 나옴. ② `mid = (lo + hi) / 2`는 큰 수에서 정수 오버플로 위험 → `lo + (hi - lo) / 2`가 안전. ③ 실수(float)에서는 `==` 비교 금지 → 부동소수점 오차(ch16-3 참고).
| 상황 | 해시맵 (O(1)) | 이진 탐색 (O(log n)) |
|---|---|---|
| 정확한 키로 단건 조회 | ✅ 더 빠름 | ✅ 되지만 느림 |
| 범위 검색 (60~80점) | ❌ 순서 없음 | ✅ 완벽 |
| k번째 원소 | ❌ | ✅ |
| 근사 검색 (가장 가까운 값) | ❌ | ✅ |
| 정렬 순회 | ❌ | ✅ |
| 추가/삭제 | O(1) | O(n) (배열이면) |
해시맵과 이진탐색은 경쟁이 아니라 파트너. 단건 조회는 해시맵, 범위·근사·k번째는 이진탐색. DB는 이 두 가지를 인덱스(B-Tree = 이진탐색)와 해시 인덱스로 제공한다(ch22-3에서 이어짐).
Claude에게 이렇게 말하면 바로 고쳐준다. "이 정렬된 리스트에서 매번 `indexOf`로 찾는 구간이 있어. 이진 탐색(`bisect_left` 또는 JS 구현)으로 바꿔서 O(log n)으로 만들어줘." — '정렬됨'을 명시하는 게 핵심. Claude가 확신을 갖고 구현한다.
한 줄 요약: 정렬된 데이터 + 이분 질문 = O(log n). 100만 건 데이터 조회를 0.02초로. 단건은 해시맵, 범위·근사·순서는 이진탐색. Git bisect·DB 인덱스·자동완성 전부 이 원리.
정렬된 1,000,000개 원소에서 이진 탐색은 최악 몇 번 비교로 찾는가?
이진 탐색은 정렬되지 않은 배열에도 쓸 수 있다.
Git에서 '언제부터 버그가 생겼는지'를 이진 탐색으로 찾는 명령은?
단건 조회라면 해시맵이 이진 탐색보다 평균적으로 빠르다.