이진 탐색 알고리즘과 파이썬을 활용한 다양한 응용

이진 탐색의 기본 원리

이진 탐색(Binary Search)은 정렬된 배열에서 특정 값을 효율적으로 찾는 대표적인 알고리즘입니다. 탐색 과정은 배열의 중앙 요소부터 시작하며, 이 값이 목표값과 일치하면 탐색을 종료합니다. 만약 목표값이 중앙값보다 크다면 오른쪽 반쪽에서, 작다면 왼쪽 반쪽에서 계속 탐색을 진행합니다. 이 방식은 매번 탐색 범위를 절반으로 줄이기 때문에 매우 빠릅니다.

시간 및 공간 복잡도

배열의 길이를 n이라고 할 때, 각 단계마다 남은 후보 수가 절반으로 줄어들므로 최대 log₂(n) 번의 비교만 수행하면 됩니다. 따라서 시간 복잡도는 O(log n)이며, 공간 복잡도는 두 개의 인덱스 변수만 사용하므로 O(1)입니다.

제약 조건

  • 데이터가 반드시 정렬되어 있어야 합니다. 비정렬 데이터는 사전에 정렬해야 하는데, 이 과정의 비용(O(n log n))이 오히려 순차 탐색보다 클 수 있습니다.
  • 배열 기반 자료구조에서만 효율적으로 작동하며, 연결 리스트에서는 인덱스 접근이 느려 적용하기 어렵습니다.
  • 작은 데이터셋에서는 간단한 선형 탐색이 더 빠를 수 있습니다. 이진 탐색은 계산 오버헤드가 존재하기 때문입니다.

기본 구현: 삽입 위치 찾기

정렬된 배열에서 주어진 값의 위치를 찾거나, 존재하지 않을 경우 삽입될 위치를 반환하는 문제입니다. LeetCode 35번 문제와 동일한 요구사항을 충족합니다.

def find_insert_position(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return left

2차원 행렬 탐색

행과 열이 모두 정렬된 2D 매트릭스에서 특정 값을 찾는 문제입니다. 각 행의 첫 번째 원소가 이전 행의 마지막 원소보다 크다는 조건 덕분에 전체 구조가 일종의 일차원 정렬 배열처럼 다뤄질 수 있습니다.

def search_2d_matrix(matrix, target):
    if not matrix or not matrix[0]:
        return False

    rows, cols = len(matrix), len(matrix[0])
    low, high = 0, rows * cols - 1

    while low <= high:
        mid = low + (high - low) // 2
        row_idx = mid // cols
        col_idx = mid % cols
        current = matrix[row_idx][col_idx]

        if current == target:
            return True
        elif current < target:
            low = mid + 1
        else:
            high = mid - 1

    return False

범위 탐색: 첫 번째와 마지막 위치 찾기

중복된 값이 있을 때, 목표값의 시작 인덱스와 끝 인덱스를 모두 찾아야 하는 상황입니다. 단순히 한 번 찾은 후 양쪽으로 확장하는 방법은 최악의 경우 O(n)이 될 수 있지만, 여기서는 각 경계를 개별적으로 이진 탐색으로 찾는 방식을 제안합니다.

def find_first_last_position(nums, target):
    def find_bound(is_first):
        left, right = 0, len(nums) - 1
        bound = -1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] == target:
                bound = mid
                if is_first:
                    right = mid - 1
                else:
                    left = mid + 1
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return bound

    first = find_bound(True)
    if first == -1:
        return [-1, -1]
    last = find_bound(False)
    return [first, last]

회전된 배열에서 탐색

정렬된 배열이 어느 지점에서 회전된 상태일 때, 여전히 O(log n) 시간 내에 탐색을 수행해야 합니다. 핵심 아이디어는 매번 분할했을 때 두 부분 중 하나는 반드시 정렬되어 있다는 점을 이용하는 것입니다.

def search_in_rotated_array(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if nums[mid] == target:
            return mid

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

회전 배열의 최솟값 찾기

회전된 정렬 배열에서 가장 작은 원소를 찾는 문제입니다. 마찬가지로 이진 탐색을 활용하며, 중간값과 오른쪽 끝값을 비교하여 최솟값이 포함된 반쪽을 결정합니다.

def find_minimum_in_rotated(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = left + (right - left) // 2
        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    return nums[left]

피크 원소 찾기

양 옆보다 큰 값을 갖는 "피크"를 찾는 문제입니다. 가상의 무한대(-∞)가 양 끝에 있다고 가정하므로, 배열 내에는 반드시 하나 이상의 피크가 존재합니다. 기울기를 판단하여 탐색 방향을 결정합니다.

def find_peak_element(nums):
    if len(nums) == 1:
        return 0

    left, right = 0, len(nums) - 1
    while left <= right:
        mid = left + (right - left) // 2
        
        left_val = float('-inf') if mid == 0 else nums[mid - 1]
        right_val = float('-inf') if mid == len(nums) - 1 else nums[mid + 1]

        if nums[mid] > left_val and nums[mid] > right_val:
            return mid
        elif nums[mid] < right_val:
            left = mid + 1
        else:
            right = mid - 1
    return -1

태그: 이진 탐색 파이썬 알고리즘 정렬 배열 O(log n)

5월 28일 07:24에 게시됨