새소식

algorithm/이론

이진 탐색 알고리즘

  • -

이진 탐색

이진 탐색(Binary Search)은 이분 탐색이라고도 하며, 리스트 내에서 데이터를 매우 빠르게 탐색하는 알고리즘이다.

이진 탐색 조건

이진 탐색은 배열 내부의 데이터가 정렬되어 있어야만 사용할 수 있는 알고리즘이다.
데이터가 무작위일 때는 사용할 수 없지만 이미 정렬되어 있다면 매우 빠르게 데이터를 찾을 수 있다.

이진 탐색 특징

이진 탐색은 탐색 범위를 절반씩 좁혀가며 데이터를 탐색한다.
이진 탐색은 위치를 나타내는 변수 3개를 사용하는데, 탐색하고자 하는 범위의 시작점, 끝점, 중간점이다.
찾으려는 데이터와 중간점 위치에 있는 데이터를 반복적으로 비교해서 원하는 데이터를 찾는 것이 이진 탐색 과정이다.

이진 탐색 동작 과정

이진 탐색의 동작 과정은 다음과 같다.

  1. 데이터 리스트(arr)에서 시작 인덱스(s)와 끝 인덱스(e)를 설정한다.
  2. se를 통해 중간 인덱스(m)을 설정한다.
    • 중간점이 실수라면 소수점 이하를 버린다.
  3. 찾으려는 데이터(k)와 중간 인덱스의 값(arr[m])을 비교하여 조건에 따라 아래 내용을 수행한다.
    • k < arr[m]이라면 em - 1로 설정하고 2번 과정으로 돌아가 재수행한다.
    • k > arr[m]이라면 sm + 1로 설정하고 2번 과정으로 돌아가 재수행한다.
    • k == arr[m]이라면 탐색을 종료한다.

이진 탐색 알고리즘 구현

이진 탐색을 구현하는 방법은 2가지로, 재귀 함수를 통해 구현하는 방법과 단순 반복문을 통해 구현하는 방법이 있다.

재귀 함수 구현

아래는 자바 코드로 재귀 함수를 통해 이진 탐색을 구현한 것이다.

// 재귀 호출 이진 탐색
private static int recursiveBinarySearch(int[] arr, int target, int startIdx, int endIdx) {
    if (startIdx > endIdx) {
        return -1;
    }

    recursiveCount++;
    int midIdx = (startIdx + endIdx) / 2;

    if (arr[midIdx] == target) {
        return midIdx;
    } else if (arr[midIdx] > target) {
        return recursiveBinarySearch(arr, target, startIdx, midIdx - 1);
    } else {
        return recursiveBinarySearch(arr, target, midIdx + 1, endIdx);
    }
}

반복문 구현

아래는 자바 코드로 반복문을 통해 이진 탐색을 구현한 것이다.

// 반복문 이진 탐색
private static int loopBinarySearch(int[] arr, int target) {
    int startIdx = 0;
    int endIdx = arr.length - 1;

    while (startIdx <= endIdx) {
        loopCount++;
        int midIdx = (startIdx + endIdx) / 2;

        if (target == arr[midIdx]) {
            return midIdx;
        } else if (target < arr[midIdx]) {
            endIdx = midIdx - 1;
        } else {
            startIdx = midIdx + 1;
        }
    }

    return -1;
}

이진 탐색 시간 복잡도

이진 탐색은 한 번 확인할 때마다 확인하는 원소의 개수가 절반씩 줄어든다는 점에서 시간 복잡도가 O(logN)이다.

재귀 호출, 반복문 실행 시간 측정

이진 탐색 알고리즘을 재귀 호출 방식으로 구현한 로직과 반복문으로 구현한 로직의 실행 시간을 측정해보았다.
측정을 위해 배열의 데이터의 개수는 10,000,000개로 설정하였고, 배열 내부 데이터는 1부터 10,000,000까지 차례로 삽입하였다.

target 값을 지정하고, 배열에서 해당 target이 위치한 인덱스를 출력하도록 이진 탐색 메서드를 작성했다. 값이 존재하지 않는다면 -1을 반환한다.

코드는 다음과 같다.

public class BinarySearch {

    private static int recursiveCount = 0;
    private static int loopCount = 0;

    public static void main(String[] args) {
        int size = 10000000;
        int[] arr = new int[size];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i + 1;
        }
        int target = 3;

        // 재귀 호출 이진 탐색
        long start = System.nanoTime();
        int recursiveBinarySearchIdx = recursiveBinarySearch(arr, target, 0, arr.length - 1);
        long end = System.nanoTime();
        System.out.println("===== 재귀 호출 이진 탐색 결과 =====");
        System.out.println("has data : " + (recursiveBinarySearchIdx != -1));
        System.out.println("target index : " + recursiveBinarySearchIdx);
        System.out.println("recursive count : " + recursiveCount);
        System.out.println("run time (nano-seconds) : " + (end - start) + "ns");

        System.out.println();

        // 반복문 이진 탐색
        start = System.nanoTime();
        int loopBinarySearchIdx = loopBinarySearch(arr, target);
        end = System.nanoTime();
        System.out.println("===== 반복문 이진 탐색 결과 =====");
        System.out.println("has data : " + (loopBinarySearchIdx != -1));
        System.out.println("target index : " + loopBinarySearchIdx);
        System.out.println("loop count : " + loopCount);
        System.out.println("run time (nano-seconds) : " + (end - start) + "ns");
    }

    // 반복문 이진 탐색
    private static int loopBinarySearch(int[] arr, int target) {
        int startIdx = 0;
        int endIdx = arr.length - 1;

        while (startIdx <= endIdx) {
            loopCount++;
            int midIdx = (startIdx + endIdx) / 2;

            if (target == arr[midIdx]) {
                return midIdx;
            } else if (target < arr[midIdx]) {
                endIdx = midIdx - 1;
            } else {
                startIdx = midIdx + 1;
            }
        }

        return -1;
    }

    // 재귀 호출 이진 탐색
    private static int recursiveBinarySearch(int[] arr, int target, int startIdx, int endIdx) {
        if (startIdx > endIdx) {
            return -1;
        }

        recursiveCount++;
        int midIdx = (startIdx + endIdx) / 2;

        if (arr[midIdx] == target) {
            return midIdx;
        } else if (arr[midIdx] > target) {
            return recursiveBinarySearch(arr, target, startIdx, midIdx - 1);
        } else {
            return recursiveBinarySearch(arr, target, midIdx + 1, endIdx);
        }
    }
}

측정한 결과는 다음과 같다.

===== 재귀 호출 이진 탐색 결과 =====
has data : true
target index : 2
recursive count : 23
run time (nano-seconds) : 16667ns

===== 반복문 이진 탐색 결과 =====
has data : true
target index : 2
loop count : 23
run time (nano-seconds) : 7875ns

데이터가 1,000만개 일 때, 위와 같은 결과를 보인다.
재귀 호출 방식으로 구현하는 것 보다 반복문으로 구현하는 것이 더 효율적인 것을 확인할 수 있다.

'algorithm > 이론' 카테고리의 다른 글

[Algorithm] 비트마스킹(Bitmasking) (Java)  (0) 2022.12.20
정렬 알고리즘  (0) 2022.12.01
깊이 우선 탐색(DFS)와 너비 우선 탐색(BFS)  (0) 2022.11.29
최단 경로 알고리즘  (0) 2022.11.26
이진 탐색 알고리즘

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.