탐색 - 3. 이진 탐색(Binary Search)
2025. 2. 24. 14:56ㆍ소프트웨어/알고리즘
📌 3. 이진 탐색(Binary Search)
3.1 이진 탐색(Binary Search) 개념 및 필요 조건
이진 탐색(Binary Search)은 정렬된 배열에서 특정 값을 찾는 효율적인 알고리즘입니다.
탐색 과정에서 검색 범위를 절반씩 줄여가기 때문에 O(log n)의 시간 복잡도를 가집니다.
🔹 필요 조건
- 반드시 정렬된 배열이어야 함
→ 정렬되지 않은 배열에서는 이진 탐색을 사용할 수 없음! - 배열을 중간값 기준으로 나누어 탐색하는 방식
→ 따라서 정렬된 데이터에서만 효율적으로 작동
3.2 이진 탐색의 동작 원리 (중간값 기준 비교)
이진 탐색은 중간값을 기준으로 범위를 절반씩 줄이며 검색하는 방식입니다.
🔹 탐색 과정
- 배열의 중앙값을 찾음
- 찾는 값과 중앙값을 비교
- 중앙값이 찾는 값보다 크면 → 왼쪽 부분 탐색
- 중앙값이 찾는 값보다 작으면 → 오른쪽 부분 탐색
- 일치하면 탐색 종료
- 위 과정을 반복하여 값을 찾거나, 없으면 탐색 종료
🔹 예제
배열:
[ 10, 20, 30, 40, 50, 60, 70 ]
찾고자 하는 값: 40
- 1단계: 중앙값 → 30 (왼쪽은 버림, 오른쪽 탐색)
- 2단계: 중앙값 → 50 (오른쪽은 버림, 왼쪽 탐색)
- 3단계: 중앙값 → 40 (일치! 탐색 종료)
💡 탐색 범위가 절반씩 줄어들기 때문에 O(log n)의 성능을 가짐
💡 데이터가 많을수록 선형 탐색보다 훨씬 빠름
3.3 재귀(Recursive) vs 반복(Iterative) 방식의 구현 비교
이진 탐색은 반복문(Iterative) 또는 재귀(Recursive) 방식으로 구현할 수 있습니다.
구현 방식 | 장점 | 단점 |
반복문 (Iterative) | 메모리 효율적 (재귀 호출 X), 코드 실행 속도 빠름 | 코드가 조금 더 길어질 수 있음 |
재귀 (Recursive) | 코드가 간결하고 직관적 | 깊은 재귀 호출 시 스택 오버플로우 가능 |
3.4 시간 복잡도 분석 (O(log n))
경우 | 비교 횟수 | 시간 복잡도 |
최선 (Best Case) | 단 한 번 비교 (중앙값이 찾는 값) | O(1) |
최악 (Worst Case) | 계속 반으로 나누며 비교 | O(log n) |
평균 (Average Case) | O(log n) 번 비교 후 값 찾음 | O(log n) |
🔍 실습: 이진 탐색 구현
1️⃣ 반복문 방식 (Iterative)
#include <stdio.h>
// 반복문을 이용한 이진 탐색
int binarySearchIterative(int arr[], int size, int target) {
int left = 0, right = size - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 중앙값 인덱스 계산
if (arr[mid] == target) {
return mid; // 찾았으면 인덱스 반환
}
if (arr[mid] < target) {
left = mid + 1; // 오른쪽 탐색
} else {
right = mid - 1; // 왼쪽 탐색
}
}
return -1; // 찾지 못한 경우
}
int main() {
int numbers[] = {10, 20, 30, 40, 50, 60, 70};
int size = sizeof(numbers) / sizeof(numbers[0]);
int target = 40;
int result = binarySearchIterative(numbers, size, target);
if (result != -1) {
printf("값 %d은(는) 인덱스 %d에 있습니다.\n", target, result);
} else {
printf("값 %d을(를) 찾을 수 없습니다.\n", target);
}
return 0;
}
📌 실행 결과
값 40은(는) 인덱스 3에 있습니다.
2️⃣ 재귀 함수 방식 (Recursive)
#include <stdio.h>
// 재귀를 이용한 이진 탐색
int binarySearchRecursive(int arr[], int left, int right, int target) {
if (left > right) {
return -1; // 찾지 못한 경우
}
int mid = left + (right - left) / 2; // 중앙값 인덱스 계산
if (arr[mid] == target) {
return mid; // 찾았으면 인덱스 반환
}
if (arr[mid] < target) {
return binarySearchRecursive(arr, mid + 1, right, target); // 오른쪽 탐색
}
return binarySearchRecursive(arr, left, mid - 1, target); // 왼쪽 탐색
}
int main() {
int numbers[] = {10, 20, 30, 40, 50, 60, 70};
int size = sizeof(numbers) / sizeof(numbers[0]);
int target = 40;
int result = binarySearchRecursive(numbers, 0, size - 1, target);
if (result != -1) {
printf("값 %d은(는) 인덱스 %d에 있습니다.\n", target, result);
} else {
printf("값 %d을(를) 찾을 수 없습니다.\n", target);
}
return 0;
}
🔍 실습: qsort()를 사용하여 정렬 후 이진 탐색 적용
정렬되지 않은 배열을 qsort()로 정렬한 후 이진 탐색을 적용하는 코드입니다.
#include <stdio.h>
#include <stdlib.h>
// 비교 함수 (qsort용)
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
// 이진 탐색 함수
int binarySearch(int arr[], int size, int target) {
int left = 0, right = size - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
}
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
int main() {
int numbers[] = {50, 10, 30, 70, 20, 60, 40}; // 정렬되지 않은 배열
int size = sizeof(numbers) / sizeof(numbers[0]);
int target = 40;
// 배열 정렬
qsort(numbers, size, sizeof(int), compare);
// 이진 탐색 실행
int result = binarySearch(numbers, size, target);
if (result != -1) {
printf("값 %d은(는) 인덱스 %d에 있습니다.\n", target, result);
} else {
printf("값 %d을(를) 찾을 수 없습니다.\n", target);
}
return 0;
}
📌 마무리 및 정리
- 이진 탐색(Binary Search)은 정렬된 배열에서만 사용 가능
- 시간 복잡도: O(log n) (탐색 범위 절반씩 감소)
- 반복문 방식은 메모리 효율적, 재귀 방식은 코드가 직관적
- qsort()를 이용하면 정렬 후 이진 탐색 적용 가능
'소프트웨어 > 알고리즘' 카테고리의 다른 글
탐색 - 5. 해싱(Hashing) 기법 (0) | 2025.02.24 |
---|---|
탐색 - 4. 점프 탐색(Jump Search) & 보간 탐색(Interpolation Search) (0) | 2025.02.24 |
탐색 - 2. 선형 탐색(Linear Search) (0) | 2025.02.24 |
탐색 - 1. 탐색(Search) 알고리즘 개요 (0) | 2025.02.24 |
그래프 이론 학습 리소스 (0) | 2025.02.21 |