에듀테크 프로젝트 대시보드
이진 탐색 (Binary Search)
"업다운 게임"을 코드로 옮긴 것. 정렬된 데이터에서 가운데를 확인하고, 답이 왼쪽인지 오른쪽인지 판단해 탐색 범위를 절반씩 버린다. 100만 개에서 20번, 10억 개에서 30번이면 끝난다.
1~100 중 하나를 정해 두고 친구가 맞힌다. "50?" → "업(더 큼)" → "75?" → "다운(더 작음)" → "62?" … 이렇게 매번 남은 후보의 절반을 지운다. 최악이라도 7번(⌈log₂100⌉)이면 반드시 맞힌다.
컴퓨터가 1~1000 사이의 내 숫자를 이진 탐색으로 맞힌다. 「업/다운/정답」으로만 답해 주자.
lo(왼쪽 끝)와 hi(오른쪽 끝)로 살아있는 범위를 표시한다. 매 단계 mid = (lo + hi) // 2 를 확인하고:
arr[mid] == target → 찾음, 끝arr[mid] < target → 정답은 오른쪽 → lo = mid + 1 (왼쪽 절반 폐기)arr[mid] > target → 정답은 왼쪽 → hi = mid - 1 (오른쪽 절반 폐기)lo > hi 가 되면 → 배열에 없음n개에서 시작해 매 비교마다 후보가 n → n/2 → n/4 → … 로 줄어든다. 후보가 1개 이하가 될 때까지 걸리는 횟수 k는:
| 배열 크기 n | 선형 탐색 (최악) | 이진 탐색 (최악) |
|---|---|---|
| 100 | 100 | 7 |
| 1,000 | 1,000 | 10 |
| 1,000,000 | 1,000,000 | 20 |
| 1,000,000,000 | 10억 | 30 |
아래 그래프는 n이 커질 때 두 방식의 비교 횟수 차이를 보여준다. 선형은 직선으로 치솟고, 이진 탐색은 거의 눕는다.
def binary_search(arr, target): lo, hi = 0, len(arr) - 1 # 양 끝(닫힌 구간 [lo, hi]) while lo <= hi: mid = (lo + hi) // 2 # 가운데 인덱스 if arr[mid] == target: return mid # 찾음 elif arr[mid] < target: lo = mid + 1 # 오른쪽 절반만 남김 else: hi = mid - 1 # 왼쪽 절반만 남김 return -1 # 배열에 없음
mid = (lo + hi) / 2 의 오버플로 — C/Java에서 큰 인덱스면 lo + hi가 int 범위를 넘는다. lo + (hi - lo) // 2 로 쓴다.lo = mid (+1 빠뜨림) 로 쓰면 범위가 안 줄어 무한 루프.while lo < hi vs <=, 마지막 한 칸을 검사하느냐 마느냐로 결과가 갈린다.실무에서 더 자주 쓰는 건 정확히 일치하는 값이 아니라 삽입 위치 / 첫 번째 위치 / 마지막 위치다. Python bisect, C++ lower_bound가 이걸 한다.
| 연산 | 의미 | 예: [1,3,3,3,7], x=3 |
|---|---|---|
lower_bound | x 이상이 처음 나오는 위치 | 인덱스 1 |
upper_bound | x 초과가 처음 나오는 위치 | 인덱스 4 |
| 개수 | upper - lower | 3개 |
배열이 아니라 답 자체를 이진 탐색하기도 한다. "조건을 만족하는 최소값 k"를 찾을 때, k가 커질수록 조건이 거짓→참으로 한 번만 바뀐다면(단조성), 그 경계를 log 번의 판정으로 찾는다.
# 예: 나무를 잘라 M미터 이상 얻는 절단기 높이의 최댓값 lo, hi = 0, max(trees) while lo < hi: mid = (lo + hi + 1) // 2 if total_cut(trees, mid) >= M: # 높이 mid로 충분한가? lo = mid # 더 높여 보자 else: hi = mid - 1 return lo
데이터베이스 인덱스 — B-Tree는 이진 탐색을 디스크 블록 단위로 다차원 확장한 것. 정렬된 키에서 log 번 만에 행을 찾는다.
git bisect — 버그가 처음 생긴 커밋을 이진 탐색으로 찾는다. 커밋 1000개도 10번의 "좋음/나쁨" 판정이면 끝.
표준 라이브러리 — bisect(Python), Arrays.binarySearch(Java), std::lower_bound(C++), sort.Search(Go).
검색·자동완성 — 정렬된 사전에서 접두사 범위를 lower/upper_bound로 잘라낸다. (→ 트라이와 비교)