이진 탐색

728x90
반응형
 

이진 탐색 알고리즘

· 순차 탐색 : 리스트 안에 있는 특정한 데이터를 찾기 위해 앞에서부터 데이터를 하나씩 확인하는 방법

· 이진 탐색 : 정렬되어 있는 리스트에서 탐색 범위를 절반씩 좁혀가며 데이터를 탐색하는 방법

· 시작점, 끝점, 중간점을 이용하여 탐색 범위를 설정한다.

이미 정렬된 10개의 데이터 중에서 값이 4인 원소를 찾아보자.

먼저 시작점은 제일 왼쪽에 있는 0번째 인덱스, 끝점은 가장 오른쪽에 있는 9번째 인덱스 그리고 중간점은 가운데 4번째 인덱스로 설정한다. 만약 중간값이 2개가 있으면 소수점을 제거한 값이 된다. ex) 9 / 2 = 4.5 이므로 소수점을 날리면 4

처음에는 중간점의 값인 '8'과 찾고자 하는 값 '4'를 비교하여 어떤 값이 더 큰지를 비교한다. 만약 찾고자하는 값보다 중간점의 값이 더 크다면 중간점부터 오른쪽에 있는 값은 전부 확인할 필요가 없어진다. 그러므로 끝점을 중간점의 왼쪽으로 옮긴다.

끝점을 옮긴 뒤, 다시 중간점을 설정한다. 그 뒤, 다시 중간점의 값과 찾고자 하는 값을 비교한다. 중간점의 값이 더 작으므로 중간점을 포함해서 왼쪽에 있는 값들은 확인할 필요가 없어지므로 시작점을 중간점의 왼쪽으로 이동시킨다.

시작점을 옮기면 위와 같이 시작점이 2, 끝점이 3이되어 위와 같은 탐색 범위를 가지게 된다. 이때 중간점 '4'가 우리가 찾고자 하는 값이 되기 때문에 탐색을 마친다.

이진 탐색의 시간 복잡도

· 단계마다 탐색 범위를 2로 나누는 것과 동일하므로 연산 횟수는

$\log _2\combi{N}$log2N

에 비례한다.

· 예를 들어 초기 데이터 개수가 32개일 때, 이상적으로 1단계를 거치면 16개의 데이터만 남고, 2단계를 거치면 8개, 3단계를 거치면 4개의 데이터만 남는다.

· 다시 말해 이진 탐색은 탐색 범위를 절반씩 줄이며, 시간 복잡도는 O(logN)을 보장한다.

이진 탐색 소스코드 : 재귀적 구현

def binary_search(array, target, start, end):
    if start > end:
        return None
    print()
    print("array : ", array[start:end+1])
    mid = (start + end) // 2
    print("mid : ", mid+1)
    print("array[mid] : ", array[mid])
    #목표값을 찾은 경우 해당 인덱스값 반환
    if array[mid] == target:
        return mid

    elif array[mid] > target:
        return binary_search(array, target, start, mid-1)
    else:
        return binary_search(array, target, mid+1, end)

n, target = list(map(int, input().split()))
array = list(map(int, input().split()))

result = binary_search(array, target, 0, n-1)

if result == None:
    print("원소가 존재하지 않습니다.")
else:
    print(result+1)

이진 탐색 소스코드 : 반복문 구현

def binary_search(array, target, start, end):
    while start <= end:
        mid = (start + end) // 2

        print("array : ", array[start:end+1])
        print("mid : ", mid+1)
        print("array[mid] : ", array[mid])
        if array[mid] == target:
            return mid
        elif array[mid] > target:
            end = mid - 1
        else:
            start = mid + 1
    return None

n, target = list(map(int, input().split()))
array = list(map(int, input().split()))

result = binary_search(array, target, 0, n-1)
if result == None:
    print("원소가 존재하지 않습니다.")
else:
    print(result+1)

파이썬 이진 탐색 라이브러리

· binary_left(a, x) : 정렬된 순서를 유지하면서 배열 a에 x를 삽입할 가장 왼쪽 인덱스를 반환

· binary_right(a, x) : 정렬된 순서를 유지하면서 배열 a에 x를 삽입할 가장 오른쪽 인덱스를 반환

예를 들어, 위와 같이 정렬된 리스트 a = [1, 2, 4, 4, 8]이 있다고 해보자.

여기서 '4'라는 어떤 값을 삽입하려고 할 때, bisect_left(a, 4)를 하면 '4'가 들어갈 수 있는 가장 왼쪽의 인덱스를 반환하므로 2를 출력한다. 반면에 bisect_right(a, 4)를 하면 '4'가 들어갈 수 있는 가장 오른쪽의 인덱스를 반환하므로 4를 반환하게 된다.

 

from bisect import bisect_left, bisect_right

a = [1, 2, 4, 4, 8]
x = 4

print(bisect_left(a, x))
print(bisect_right(a, x))

값이 특정 범위에 속하는 데이터 개수 구하기

bisect_right로 구한 인덱스 값과 bisect_left로 구한 인덱스의 값을 빼주게 되면 해당 값의 범위에 포함되어있는 데이터의 갯수를 구할 수 있다.

from bisect import bisect_left, bisect_right

def count_by_range(a, left_value, right_value):
    right_index = bisect_right(a, right_value)
    left_index = bisect_left(a, left_value)
    return right_index - left_index

a = list(map(int, input().split()))
print("List : ", a)

#값이 4인 데이터 개수 출력
print(count_by_range(a, 4, 4))
#값이 [-1, 3]인 데이터 개수 출력
print(count_by_range(a, -1, 3))

파라메트릭 서치 (Parametric Search)

· 파라메트릭 서치란 최적화 문제를 결정 문제('예' 혹은 '아니오')로 바꾸어 해결하는 기법이다.

ex) 특정한 조건을 만족하는 가장 알맞은 값을 빠르게 찾는 최적화 문제

· 일반적으로 코딩 테스트에서 파라메트릭 서치 문제는 이진 탐색을 이용하여 해결할 수 있다.

728x90
반응형