이진 탐색 (혹은 검색, binary search) 는 이미 정렬된 배열에서 타겟을 찾는 검색 알고리즘으로 시간 복잡도가 이 소요되는 대표적인 로그 시간 알고리즘입니다.
술자리에서 0부터 100까지의 임의의 숫자를 생각해 놓고 업다운을 통해 숫자를 맞추는 게임이 있습니다. 이때, 하나씩 높여가는 것이 아니라 50 -> 25,75 -> 12,37,87 등 숫자 범위의 절반을 줄여가면서 탐색 범위를 점점 좁히게 되죠. 합병정렬처럼 매번 절반으로 쪼개니 의 시간 복잡도가 소요됩니다.
절반으로 탐색 범위를 좁혀 계산 횟수가 만큼 줄게 만드는 것은 시간 복잡도 측면에서 굉장히 강력한 성능을 띠게 됩니다. 100까지의 숫자는 7번, 1억까지의 숫자도 27번의 비교로 타겟을 찾아낼 수가 있죠.
그렇다면 왜 이진 탐색은 이미 정렬된 배열에 대해서 수행할까요? 이진 탐색은 일반적으로 특정한 타겟을 찾는 알고리즘이기 때문에 배열의 절반 인덱스의 값이 크냐 작냐에 따라 왼쪽 절반, 오른쪽 절반 중 무엇을 선택할지 재귀적으로 결정하는 알고리즘이기 때문입니다. 이진 탐색은 재귀 혹은 반복 방법으로 구현할 수 있습니다.
파이썬 코드
재귀 방법
def binary_search_with_recursive(array, target):
## 재귀적으로 절반을 나눌 divide 함수
def divide(left, right):
if left <= right:
mid = left + (right - left) // 2
## array[mid] 값이 target보다 작다면 오른쪽 절반에 대해 재귀
if array[mid] < target:
return divide(left=mid+1, right=right)
## array[mid] 값이 target보다 크다면 왼쪽 절반에 대해 재귀
elif array[mid] > target:
return divide(left=left, right=mid-1)
## array[mid] 값이 target과 같다면 그대로 return
else:
return mid
## 재귀함수 종료 조건
## left가 right보다 클 때까지 target이 발견되지 않음
else:
return -1
return divide(left=0, right=len(array)-1)
합병정렬처럼 절반으로 계속 줄여나가니 재귀로 구현할 수 있습니다. 이 때, 값을 맞출 때까지 재귀 함수를 호출하고 값을 맞춘다면 해당 인덱스를 return, 값을 못 찾았을 경우 -1을 return합니다.
반복 방법
def binary_search_with_iterative(array, target):
## 재귀가 아니므로 미리 left, right 값 양 끝으로 설정
left, right = 0, len(array) - 1
## left가 right보다 커질 때까지
while left <= right:
## 절반 인덱스
mid = left + (right - left) // 2
## array[mid]가 target보다 작다면 오른쪽 절반에 대해
if array[mid] < target:
left = mid + 1
## array[mid]가 target보다 크다면 왼쪽 절반에 대해
elif array[mid] > target:
right = mid - 1
## array[mid]가 target과 같다면 그대로 return
else:
return mid
## while문을 통과했다면 target에 해당하는 인덱스를 못 찾은 것이므로 -1 return
return -1
고려 사항
재귀, 반복 방법 모두 루프 안 mid 인덱스를 구하는 과정에서 mid=(left+right)//2가 아니라 mid=left+(right-left)//2 를 사용했습니다.
mid=(left+right)//2 도 사용해도 잘 동작합니다. 하지만 left+right 값이 정수 표현형의 최대값 (32bit)를 넘어갔을 때 overflow가 발생합니다. 이를 방지하기 위해 mid 인덱스를 구할 때는 다음과 같이 사용합니다.
mid = left + (right - left) // 2
홍머스 정리
- 이진 검색은 정렬된 배열에 대해 타겟을 찾는 알고리즘
- 재귀나 반복으로 구현 가능
- 의 시간복잡도
- 로그는 축복
참조
- <파이썬 알고리즘 인터뷰>, p515-524
'Theory > Algorithms' 카테고리의 다른 글
자료구조 - 스택 (Stack), 큐 (Queue) (0) | 2021.02.24 |
---|---|
다이나믹 프로그래밍 (Dynamic Programming) (0) | 2021.02.23 |
그래프 (2) - 너비우선탐색 (BFS, Breadth First Search) (0) | 2021.02.22 |
그래프 (1) - 깊이우선탐색 (DFS, Depth First Search) (0) | 2021.02.22 |
안정 정렬 vs 불안정 정렬 (0) | 2021.02.22 |