알고리즘

이진탐색

na_o 2021. 8. 23. 03:50
728x90

" 주어진 범위 안에서 특정 숫자 찾기 "

방법1) 숫자를 일일이 다 비교하면서 찾기(순차탐색)

방법2) 이진탐색으로 찾기

 

 

[이진탐색]

이진탐색Up&Down 게임과 같음!

Up&Down 게임 : 특정 숫자를 맞추는 게임. 정답인 숫자가 더 높다면 "UP", 낮다면 "Down" 을 부르는 게임

위의 게임을 할 때 숫자의 범위를 최대한 줄이는 게 가장 효율적임

만약 1~100 사이의 숫자를 찾아야 한다면, 50을 먼저 외쳐 범위를 1~49 또는 51~100으로 줄이고 게임을 하는게 좋음

이처럼 범위를 절반씩 줄여가면서 찾는게 가장 빨리 찾을 수 있음

이 방법이 바로 "이진탐색"

 

" 그럼, 방법 1이랑 방법 2 둘 중 뭐가 더 효율적일까??! " 

 


[1~16까지 오름차순으로 정렬되어있는 배열 중에서 14 찾기]

 

방법1) 숫자를 일일이 다 비교하면서 찾기(순차탐색)

finding_target = 14
finding_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]


def is_existing_target_number_sequential(target, array):
    find_count = 0
    for number in array:
        find_count += 1
        if target == number:
            print("시도한 횟수:", find_count)  # 14!
            return True

    return False


result = is_existing_target_number_sequential(finding_target, finding_numbers)
print(result)  # True


# 시도한 횟수: 14
# True

[시간복잡도]

배열을 차례로 따라가면서 찾다보니, 검색횟수가 총 14번이 걸림

만약 찾는 숫자가 16이었다면 16번 검색해야 했을테니 최악의 경우는 O(N) 걸린다고 할 수 있음

 


 

방법2) 이진탐색으로 찾기

array 를 따라가면서 target 이 존재한다면 True 를 반환하고, 끝까지 없다면 False 를 반환

시도했을 때 target 보다 작다면 더 큰 값들을 뒤져야 하니까 최솟값에 시돗값 + 1 을 대입

시도했을 때 target 보다 크다면 더 작은 값들을 뒤져야 하니까 최댓값에 시돗값 - 1 을 대입

 

이를 최솟값 ≤ 최댓값 까지 반복!

왜 < 가 아니라 ≤ 냐면 최솟값 == 최댓값인 시점까지 확인을 해야 하기 때문!

finding_target = 14
finding_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]


def is_existing_target_number_binary(target, array):
    current_min = 0
    current_max = len(array) - 1
    count = 0
    while current_min <= current_max:
        current_guess = (current_min + current_max) // 2
        count += 1
        if array[current_guess] == target:
            print("시도한 횟수: ", count)
            return True
        elif array[current_guess] < target:
            current_min = current_guess + 1
        else:
            current_max = current_guess - 1
    print("시도한 횟수: ", count)
    return False


result = is_existing_target_number_binary(finding_target, finding_numbers)
print(result)


# 시도한 횟수: 3
# True

[시간복잡도]

총 숫자가 1~N 까지이면

1번 탐색) 절반이 줄어듦:  N/2 개 남음

2번 탐색) 절반이 줄어듦:  N/4 = N/(2^2) 개 남음

3번 탐색) 절반이 줄어듦:  N/8 = N/(2^3) 개 남음

....

k번 탐색) 절반이 줄어듦:  N/(2^k) 개 남음

이 때, 이분탐색을 열심히 시도해서 딱 1개만 남았다고 가정!

이걸 수식으로 표현하면, N/(2^k) = 1

    ->   k = log_2{N} 횟수를 시도하면 정답을 찾을 수 있음

결론) 시간 복잡도가 O(logN) 만큼 걸린다. 라고 말할 수 있음

 

왜 log_2N 이 아니라 logN 인가요?

상수는 무시해도 되기 때문에 표기하기 쉽도록 logN 으로 부르기로 약속했음

 

 

연산량이 반으로 나눠진다 싶으면 O(logN) 이겠구나 하면 됨

 

 

 

[내가 만든 코드]

finding_target = 14
finding_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]


def is_existing_target_number_binary(target, array):
    min_num = array[0]
    max_num = array[-1]
    try_num = 0
    count = 0
    if target > len(array):
        return "None!!"
    while target != try_num:
        try_num = (min_num + max_num) // 2
        count += 1
        if try_num < target:
            min_num = try_num + 1
        elif try_num > target:
            max_num = try_num - 1
    print("시도한 횟수: ", count)
    return try_num


result = is_existing_target_number_binary(finding_target, finding_numbers)
print(result)


# 시도한 횟수: 3
# 14

정리

 

'알고리즘' 카테고리의 다른 글

재귀함수 - 2  (0) 2021.08.24
재귀함수 - 1  (0) 2021.08.23
링크드 리스트 문제  (0) 2021.08.22
링크드 리스트 구현 - 2  (0) 2021.08.22
링크드 리스트 구현 - 1  (0) 2021.08.21