이진 탐색
마지막 업데이트
이진 탐색은 **정렬된** 배열에서 탐색 범위를 반복적으로 절반씩 줄여 목표 값을 찾습니다. 가운데 요소를 목표 값과 비교해서 일치하면 탐색이 끝나고, 그렇지 않으면 목표 값이 있을 수 없는 절반을 버리고 범위를 나머지 절반으로 좁힙니다. 비교할 때마다 남은 요소의 절반이 제거되므로 O(log n) 시간에 동작합니다 — 정렬된 값 100만 개를 탐색해도 비교는 최대 약 20번이면 충분합니다.
위 애니메이션은 lo, mid, hi 포인터를 보여 주고, 비교가 끝날 때마다 제거된 절반을 흐리게 표시합니다. 단 하나의 절대 전제 조건은 배열이 이미 정렬되어 있어야 한다는 것입니다 — 정렬되지 않은 데이터에는 선형 탐색을 쓰거나 먼저 정렬해야 합니다(병합 정렬 참고). 이 절반으로 나누는 아이디어는 이진 탐색 트리의 기반이기도 합니다.
시간 및 공간 복잡도
| 경우 | 복잡도 | 비고 |
|---|---|---|
| 최선의 경우 | O(1) | 첫 번째 비교에서 가운데 요소가 목표 값인 경우. |
| 평균적인 경우 | O(log n) | 비교할 때마다 남은 범위가 절반으로 줄어든다. |
| 최악의 경우 | O(log n) | 일치 또는 실패가 확정되기 전에 범위가 요소 하나까지 줄어든다. |
| 공간 | O(1) | 반복 버전은 lo, hi, mid 인덱스만 유지한다. |
단계별 과정
| 단계 | 무슨 일이 일어나는가 |
|---|---|
| 1 | lo를 정렬된 배열의 첫 번째 인덱스로, hi를 마지막 인덱스로 설정한다. |
| 2 | 가운데 인덱스를 계산한다: mid = (lo + hi) // 2. |
| 3 | a[mid]가 목표 값과 같으면 mid를 반환한다 — 발견. |
| 4 | a[mid]가 목표 값보다 **작으면**, 목표 값은 오른쪽 절반에만 있을 수 있다: lo = mid + 1로 설정한다. |
| 5 | a[mid]가 목표 값보다 **크면**, 왼쪽 절반을 탐색한다: hi = mid - 1로 설정한다. |
| 6 | lo <= hi인 동안 2단계부터 반복한다. 범위가 비면 목표 값은 배열에 없다. |
풀이 예제
[1, 2, 3, 5, 7, 8, 9]에서 5를 탐색하는 경우:
| 패스 | 범위 (lo..hi) | mid | a[mid] | 동작 |
|---|---|---|---|---|
| 1 | [1, 2, 3, 5, 7, 8, 9] (0..6) | 3 | 5 | a[3] = 5 — 인덱스 3에서 목표 값 발견. |
실패하는 탐색, 단계별로
같은 배열에서 4를 탐색하면 범위가 어떻게 비는지 볼 수 있습니다:
| 패스 | 범위 (lo..hi) | mid | a[mid] | 동작 |
|---|---|---|---|---|
| 1 | 0..6 | 3 | 5 | 5 > 4 — 왼쪽 절반 탐색, hi = 2. |
| 2 | 0..2 | 1 | 2 | 2 < 4 — 오른쪽 절반 탐색, lo = 2. |
| 3 | 2..2 | 2 | 3 | 3 < 4 — lo가 3이 되어 범위가 빈다: 찾지 못함. |
이진 탐색을 사용해야 할 때
| 사용해야 할 때 | 피해야 할 때 |
|---|---|
| 데이터가 이미 정렬되어 있을 때(또는 여러 번 탐색할 때) | 데이터가 정렬되어 있지 않고 한 번만 탐색할 때 — 먼저 정렬하면 O(n log n)이 든다 |
| 컬렉션이 빠른 임의 접근을 지원할 때(배열) | 순차 접근만 가능할 때(연결 리스트) |
데이터셋이 클 때 — O(log n)은 대규모에서 빛난다 | 데이터셋이 아주 작을 때 — 단순 순회도 그만큼 빠르고 더 간단하다 |
Binary Search 코드
Python, JavaScript, Java, C++, C, Pseudocode로 작성된 깔끔하고 실행 가능한 Binary Search 구현입니다. 언어를 선택해 코드를 복사하거나 Coddy 플레이그라운드에서 바로 열어보세요.
Python로 구현한 Binary Search 코드
1def binary_search(a, target):2 lo, hi = 0, len(a) - 13 while lo <= hi:4 mid = (lo + hi) // 25 if a[mid] == target:6 return mid7 if a[mid] < target:8 lo = mid + 1 # search the right half9 else:10 hi = mid - 1 # search the left half11 return -112
13
14nums = [1, 2, 3, 5, 7, 8, 9] # must be sorted15print("Index of 5:", binary_search(nums, 5))16print("Index of 4:", binary_search(nums, 4))JavaScript로 구현한 Binary Search 코드
1function binarySearch(a, target) {2 let lo = 0;3 let hi = a.length - 1;4 while (lo <= hi) {5 const mid = Math.floor((lo + hi) / 2);6 if (a[mid] === target) return mid;7 if (a[mid] < target) {8 lo = mid + 1; // search the right half9 } else {10 hi = mid - 1; // search the left half11 }12 }13 return -1;14}15
16const nums = [1, 2, 3, 5, 7, 8, 9]; // must be sorted17console.log("Index of 5:", binarySearch(nums, 5));18console.log("Index of 4:", binarySearch(nums, 4));Java로 구현한 Binary Search 코드
1public class Main {2 static int binarySearch(int[] a, int target) {3 int lo = 0;4 int hi = a.length - 1;5 while (lo <= hi) {6 int mid = (lo + hi) / 2;7 if (a[mid] == target) return mid;8 if (a[mid] < target) {9 lo = mid + 1; // search the right half10 } else {11 hi = mid - 1; // search the left half12 }13 }14 return -1;15 }16
17 public static void main(String[] args) {18 int[] nums = {1, 2, 3, 5, 7, 8, 9}; // must be sorted19 System.out.println("Index of 5: " + binarySearch(nums, 5));20 System.out.println("Index of 4: " + binarySearch(nums, 4));21 }22}C++로 구현한 Binary Search 코드
1#include <iostream>2#include <vector>3
4int binarySearch(const std::vector<int>& a, int target) {5 int lo = 0;6 int hi = static_cast<int>(a.size()) - 1;7 while (lo <= hi) {8 int mid = lo + (hi - lo) / 2;9 if (a[mid] == target) return mid;10 if (a[mid] < target) {11 lo = mid + 1; // search the right half12 } else {13 hi = mid - 1; // search the left half14 }15 }16 return -1;17}18
19int main() {20 std::vector<int> nums = {1, 2, 3, 5, 7, 8, 9}; // must be sorted21 std::cout << "Index of 5: " << binarySearch(nums, 5) << "\n";22 std::cout << "Index of 4: " << binarySearch(nums, 4) << "\n";23 return 0;24}C로 구현한 Binary Search 코드
1#include <stdio.h>2
3int binary_search(const int a[], int n, int target) {4 int lo = 0;5 int hi = n - 1;6 while (lo <= hi) {7 int mid = lo + (hi - lo) / 2;8 if (a[mid] == target) return mid;9 if (a[mid] < target) {10 lo = mid + 1; /* search the right half */11 } else {12 hi = mid - 1; /* search the left half */13 }14 }15 return -1;16}17
18int main(void) {19 int nums[] = {1, 2, 3, 5, 7, 8, 9}; /* must be sorted */20 int n = sizeof(nums) / sizeof(nums[0]);21 printf("Index of 5: %d\n", binary_search(nums, n, 5));22 printf("Index of 4: %d\n", binary_search(nums, n, 4));23 return 0;24}Pseudocode로 구현한 Binary Search 코드
1DECLARE nums : ARRAY[1:7] OF INTEGER2DECLARE n : INTEGER3n ← 74// The array must be sorted for binary search5nums[1] ← 16nums[2] ← 27nums[3] ← 38nums[4] ← 59nums[5] ← 710nums[6] ← 811nums[7] ← 912
13FUNCTION binarySearch(target : INTEGER) RETURNS INTEGER14 DECLARE lo : INTEGER15 DECLARE hi : INTEGER16 DECLARE mid : INTEGER17 lo ← 118 hi ← n19 WHILE lo <= hi DO20 mid ← (lo + hi) DIV 221 IF nums[mid] = target THEN22 RETURN mid23 ENDIF24 IF nums[mid] < target THEN25 // Target is larger — search the right half26 lo ← mid + 127 ELSE28 // Target is smaller — search the left half29 hi ← mid - 130 ENDIF31 ENDWHILE32 RETURN -133ENDFUNCTION34
35OUTPUT "Index of 5 is ", binarySearch(5)36OUTPUT "Index of 4 is ", binarySearch(4)이진 탐색 FAQ
이진 탐색의 시간 복잡도는 어떻게 되나요?
O(log n)입니다. 비교할 때마다 남은 탐색 범위가 절반으로 줄어들기 때문입니다. 첫 번째 가운데 요소가 목표 값인 최선의 경우는 O(1)입니다. 반복 버전은 O(1)의 추가 공간을 사용합니다.이진 탐색은 왜 정렬된 배열이 필요한가요?
이진 탐색과 선형 탐색의 차이는 무엇인가요?
O(n)) 어떤 배열에서도 동작합니다. 이진 탐색은 정렬된 배열의 탐색 범위를 절반씩 줄이지만(O(log n)) 정렬된 입력이 필요합니다. 요소가 몇 개뿐이라면 차이는 미미하지만, 규모가 커지면 이진 탐색이 압도적으로 유리합니다.이진 탐색에는 비교가 몇 번 필요한가요?
log2(n) + 1번입니다. 비교 10번으로 1,000개, 20번으로 1,000,000개를 처리할 수 있습니다. 이 로그적 증가야말로 정렬된 데이터에서 기본 탐색 방법이 되는 이유입니다.이진 탐색의 고전적인 오버플로 버그는 무엇인가요?
(lo + hi) / 2로 계산하면 lo + hi가 타입의 최댓값을 넘을 때 고정 크기 정수가 오버플로될 수 있습니다. 안전한 형태는 mid = lo + (hi - lo) / 2입니다. Python에서는 문제가 되지 않지만(임의 정밀도 정수), Java/C/C++에서는 실제로 일어난 유명한 버그입니다.