한양과학기술고등학교

에듀테크 프로젝트 대시보드

이진 탐색 (Binary Search)

이진 탐색 (Binary Search)

"업다운 게임"을 코드로 옮긴 것. 정렬된 데이터에서 가운데를 확인하고, 답이 왼쪽인지 오른쪽인지 판단해 탐색 범위를 절반씩 버린다. 100만 개에서 20번, 10억 개에서 30번이면 끝난다.

핵심 전제 — 배열이 정렬되어 있어야 한다. 그래야 "가운데보다 크다 → 왼쪽 절반엔 절대 없다"는 판단이 성립한다. 정렬 비용이 아깝지만, 한 번 정렬해 두고 여러 번 검색한다면 크게 남는 장사다.

1. 업다운 게임으로 이해하기

1~100 중 하나를 정해 두고 친구가 맞힌다. "50?" → "업(더 큼)" → "75?" → "다운(더 작음)" → "62?" … 이렇게 매번 남은 후보의 절반을 지운다. 최악이라도 7번(⌈log₂100⌉)이면 반드시 맞힌다.

컴퓨터가 1~1000 사이의 내 숫자를 이진 탐색으로 맞힌다. 「업/다운/정답」으로만 답해 주자.

컴퓨터의 추측:–
「새 게임」을 누르고 머릿속으로 1~1000 중 하나를 정하세요.

2. 배열에서 한 단계씩 실행해 보기

lo(왼쪽 끝)와 hi(오른쪽 끝)로 살아있는 범위를 표시한다. 매 단계 mid = (lo + hi) // 2 를 확인하고:

lo
–
mid
–
hi
–
비교 횟수
0
남은 후보 수
–
「한 단계」를 눌러 탐색을 시작하세요.

3. 왜 O(log n) 인가

n개에서 시작해 매 비교마다 후보가 n → n/2 → n/4 → … 로 줄어든다. 후보가 1개 이하가 될 때까지 걸리는 횟수 k는:

n / 2ᵏ ≤ 1 ⟹ k ≥ log₂ n ⟹ 최악 비교 횟수 = ⌈log₂ n⌉ + 1
배열 크기 n선형 탐색 (최악)이진 탐색 (최악)
1001007
1,0001,00010
1,000,0001,000,00020
1,000,000,00010억30

아래 그래프는 n이 커질 때 두 방식의 비교 횟수 차이를 보여준다. 선형은 직선으로 치솟고, 이진 탐색은 거의 눕는다.

4. 코드 — 반복문 버전

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                             # 배열에 없음
흔한 버그 4가지
  • 정렬 안 된 배열에 그냥 쓰기 — 전제가 깨져 엉뚱한 결과. 반드시 사전 정렬.
  • mid = (lo + hi) / 2 의 오버플로 — C/Java에서 큰 인덱스면 lo + hi가 int 범위를 넘는다. lo + (hi - lo) // 2 로 쓴다.
  • off-by-one — lo = mid (+1 빠뜨림) 로 쓰면 범위가 안 줄어 무한 루프.
  • 경계 조건 — while lo < hi vs <=, 마지막 한 칸을 검사하느냐 마느냐로 결과가 갈린다.

5. 변형 — "같은 값" 말고 "경계" 찾기

실무에서 더 자주 쓰는 건 정확히 일치하는 값이 아니라 삽입 위치 / 첫 번째 위치 / 마지막 위치다. Python bisect, C++ lower_bound가 이걸 한다.

연산의미예: [1,3,3,3,7], x=3
lower_boundx 이상이 처음 나오는 위치인덱스 1
upper_boundx 초과가 처음 나오는 위치인덱스 4
개수upper - lower3개

"정답에 대한 이진 탐색" (parametric search)

배열이 아니라 답 자체를 이진 탐색하기도 한다. "조건을 만족하는 최소값 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

6. 어디에 쓰이나

데이터베이스 인덱스 — B-Tree는 이진 탐색을 디스크 블록 단위로 다차원 확장한 것. 정렬된 키에서 log 번 만에 행을 찾는다.

git bisect — 버그가 처음 생긴 커밋을 이진 탐색으로 찾는다. 커밋 1000개도 10번의 "좋음/나쁨" 판정이면 끝.

표준 라이브러리 — bisect(Python), Arrays.binarySearch(Java), std::lower_bound(C++), sort.Search(Go).

검색·자동완성 — 정렬된 사전에서 접두사 범위를 lower/upper_bound로 잘라낸다. (→ 트라이와 비교)

한 줄 요약 — 정렬해 두면, 매 비교마다 후보의 절반을 버릴 수 있다. 관련: 이진 탐색 트리, Big-O 표기법, 정렬 알고리즘.