포스트

이분 탐색(이진 탐색, binary search)

이분 탐색, 이진 탐색, Binary Search

이분 탐색(이진 탐색)은 결정 문제(Decision Problem)의 답이 이분적(T or F)일 때 사용할 수 있는 탐색 기법이다. 보통 1개의 parameter에 대해 탐색할 때사용한다.

이분 탐색을 사용하면 정렬된 배열에서 특정 값을 효율적으로 찾을 수 있다.

길이가 $N$인 배열에 대한 완전 탐색은 $O(N)$ 의 시간 복잡도를 가지지만, 이분 탐색을 이용하여 해당 배열을 탐색하는 경우는 $O(\log N)$이다.

img

아이디어

  1. 정렬된 배열의 가장 왼쪽 인덱스(left)와 가장 오른쪽 인덱스(right)를 가지고, 중간의 값(mid)을 계산한다.
  2. 계산된 mid를 우리가 찾고자하는 target 값과 비교한다.
    1. 중간 값이 타겟 값과 동일하다면, 해당 중간 값을 반환
    2. 중간 값 < 타겟 값이라면, left를 중간 값 다음 인덱스로 업데이트 후 1~2를 반복
    3. 중간 값 > 타겟 값이라면, right을 중간 값 이전 인덱스로 업데이트 후 1~2를 반복

반복하여 타겟값을 찾거나, left와 right가 교차할 때(left>right)까지 반복한다.

분할 정복과의 차이점

분할 정복의 경우 전체 문제에 대해 분할하여 가장 작은 형태로 만드는 것에 반해,

이분 탐색은 정답이 포함되지 않은 부분은 추가로 탐색하지 않는다.

구현

1. 재귀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def binary_search(arr, target, left, right):
    if left > right:
        return -1  # 탐색 실패

    mid = (left + right) // 2

    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search(arr, target, mid + 1, right)
    else:
        return binary_search(arr, target, left, mid - 1)

# 예시 사용
arr = [1, 3, 5, 7, 9, 11]
target = 7

result = binary_search(arr, target, 0, len(arr) - 1)

if result != -1:
    print("타겟이 배열의 인덱스", result, "에 위치합니다.")
else:
    print("타겟을 찾을 수 없습니다.")

2. 반복문

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def binary_search(arr, target):
    left = 0
    right = len(arr) - 1

    while left <= right:
        mid = (left + right) // 2

        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return -1  # 탐색 실패

# 예시 사용
arr = [1, 3, 5, 7, 9, 11]
target = 7

result = binary_search(arr, target)

if result != -1:
    print("타겟이 배열의 인덱스", result, "에 위치합니다.")
else:
    print("타겟을 찾을 수 없습니다.")

유의 사항

이분 탐색은 off-by-one error가 발생하기 쉬우므로,

반복문의 구간 설정 또는 left, right 변수를 업데이트 할 때 유의해야한다.

반복문에서 루프가 한번 더 실행되거나 덜 실행되는 경우의 오류

일반적으로 인덱스를 잘못 처리하거나, 반복문의 범위를 잘못 설정하여 발생한다. 예를 들어, 배열의 인덱스를 0부터 시작하지 않고 1부터 시작하는 경우, 또는 반복문의 범위를 정확히 설정하지 않고 한 번 더 실행하거나 빠트리는 경우에 발생할 수 있다.

구현을 하다 보면 left<= right , left< right , left+ 1 < right 중 어느 걸 선택해야 할 지, 정답이 left인지 right 인지 (left+ right ) / 2인지 모르겠고, 심지어는 while문이 끝나지 않아서 시간초과를 받기도 한다.

  1. 구간 [left, right]가 Check(left) != Check(right)가 되도록 구간을 설정

  2. while (left+ 1 < right)동안 mid = (left+ right) / 2에서

    Check(mid) = Check(left)라면 left = mid, 아니라면 right = mid

  3. 구한 경계에서 답이 left인지 right인지 생각해보고 출력

구현에서 헷갈린다면 관련하여 설명한 글을 참고하자.

참고 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.