이진 탐색(Binary Search)은 정렬된 데이터에서 원하는 값을 매우 빠르게 찾아내는 알고리즘입니다. 일상생활에서 전화번호부나 사전에서 단어를 찾을 때 중간쯤을 펼쳐보는 것과 같은 원리라고 생각하면 이해하기 쉽습니다.
이진 탐색의 가장 중요한 전제 조건은 데이터가 반드시 오름차순이나 내림차순으로 정렬되어 있어야 한다는 것입니다.
작동 방식은 다음과 같습니다:
숫자 [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]가 들어있는 리스트에서 "17"을 찾는다고 가정해 봅시다.
| 단계 | 범위 | 중간값 | 비교 결과 | 다음 조치 |
| 1단계 | 0~9 | 9(인덱스 4) | 17 > 9 | 오른쪽 절반(인덱스 5~9) 선택 |
| 2단계 | 5~9 | 15(인덱스 7) | 17 > 15 | 오른쪽 절만(인덱스 8~9) 선택 |
| 3단계 | 8~9 | 17(인덱스 8) | 17 == 17 | 탐색 성공 |
위 표를 살펴보면 단 3번의 비교만으로 10개의 숫자 중 원하는 값을 찾아냈습니다. 처음부터 하나씩 찾는 순차 탐색이었다면 9번을 비교해야 했을 것입니다.
이진 탐색의 효율성은 데이터가 많아질수록 빛을 발합니다. 한 번 비교할 때마다 후보군이 절반씩 사라지기 때문에 시간 복잡도는 O(log n)입니다.
def binary_search(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid # 찾은 경우 인덱스 반환
elif arr[mid] > target:
high = mid - 1 # 왼쪽 절반 탐색
else:
low = mid + 1 # 오른쪽 절반 탐색
return -1 # 찾지 못한 경우
target 이상의 값(target보다 같거나 큰 값)이 처음 나타나는 위치를 찾는 데 아주 유용합니다.
def search_lower_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = (left + right) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid
return left
초기 상태: left = 0, right = 5 (배열 길이), arr = [1, 3, 3, 5, 8], target = 3
Step 1
Step 2
Step 3
종료 (Final)
lower bound 찾기 알고리즘이 일반적인 이진 탐색과 다른 결정적인 부분은 중복된 값이 있을 때의 처리 방식입니다.
Upper Bound(상한선) 알고리즘입니다. Lower Bound가 target보다 같거나 큰 수가 처음 나타나는 위치를 찾는다면, Upper Bound는 target보다 큰 값(초과)이 처음으로 나타나는 위치를 찾습니다.
def search_upper_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = (left + right) // 2
if arr[mid] <= target:
left = mid + 1
else:
right = mid
return left
초기 상태: left = 0, right = 5, arr = [1, 3, 3, 5, 8], target = 3
Step 1
Step 2
Step 3
종료 (Final)
두 알고리즘의 차이는 딱 한 군데, if문의 비교 연산자에 있습니다.
| 구분 | lower bound | upper bound |
| 핵심 조건 | arr[mid] < target | arr[mid] <= target |
| 목표 | target 이상의 첫 번째 위치 | target 초과의 첫 번째 위치 |
| 예시 결과 | 3이 시작되는 인덱스 1 | 3이 끝난 다음 인덱스 3 |
이 두 알고리즘을 조합하면 정렬된 배열에서 특정 숫자가 몇 개 포함되어 있는지 아주 쉽게 알 수 있습니다.
개수 구하기 공식: upper_bound - lower_bound
우리는 left_limit 이상 right_limit 이하의 숫자를 세고 싶습니다. 이때 이진 탐색의 두 가지 변형을 이렇게 사용합니다.
def count_range(arr, left_limit, right_limit):
# 1. left_limit 이상인 값이 처음 나오는 위치 (Lower Bound)
low_idx = search_lower_bound(arr, left_limit)
# 2. right_limit보다 큰 값이 처음 나오는 위치 (Upper Bound)
high_idx = search_upper_bound(arr, right_limit)
# 두 인덱스의 차이가 곧 해당 범위 숫자의 개수입니다.
return high_idx - low_idx
# 테스트
arr = [1, 2, 4, 4, 4, 6, 7, 8, 8, 10]
# 4 이상 8 이하의 숫자 개수 찾기
result = count_range(arr, 4, 8)
print(f"4 이상 8 이하 숫자의 개수: {result}개")
arr = [1, 2, 4, 4, 4, 6, 7, 8, 8, 10] 에서 4 이상 8 이하를 찾는다면:
만약 배열의 크기가 1억 개이고, 우리가 찾아야 할 범위의 숫자가 5천만 개라고 가정해 봅시다.
데이터가 많아질수록 이 차이는 비교할 수 없을 만큼 커집니다.
| Fast and Slow pointer pattern in Linked list (0) | 2025.12.21 |
|---|---|
| Linked List(연결 리스트) (0) | 2025.12.20 |
| 위상 정렬(Topological Sort) (0) | 2025.12.15 |
| Dynamic Programming (0) | 2023.08.27 |
| 탐욕 알고리즘(Greedy Algorithm) (0) | 2023.05.22 |
댓글 영역