순차 탐색(Sequential Search)와 이진 탐색(Binary Search)


문제


data = {1, 12, 8, 7, 2, 11, 5, 81, 29, 10}

이란 배열이 있습니다.

여기에서 숫자 7이 몇번째에 있는지 알아내고자 합니다.





순차탐색은 말그대로 순차적으로 비교해가면서 찾는 것입니다.

data[0]부터 값을 살펴봐서 7인지 아닌지 하나하나 확인하는 것이죠.

data[0], data[1], data[2] 는 계속 아니다가

data[3] == 7 이므로 여기서 탐색을 멈춥니다.




복잡도(Time Complexity)


만약 data 배열에서 1을 탐색하면 1번만에 찾아낼 것입니다.

12를 탐색하고자 한다면 2번만에 찾아낼 것이고,

8을 탐색하고자 한다면 3번만에 찾아낼 것이고,

…

10을 탐색하고자 한다면 10번만에 찾아낼 것입니다.

그럼 탐색하는게 걸리는 평균 연산 횟수는

\[\frac{1+2+3+\cdots+9+10}{10}\]

이 되겠죠.


그럼 data 배열의 크기가 n이라면 탐색에 걸리는 평균 연산횟수는

\[\frac{1+2+3+\cdots+(n-1)+n}{n} = \frac{n+1}{2}\]

즉, 복잡도는 O(n)이 됩니다.




예제 코드

public class SequentialSearch {
    public static void main(String[] ar){
        SequentialSearch ex = new SequentialSearch();
        int[] data = {1, 12, 8, 7, 2, 11, 5, 81, 29, 10};
        int key = 7;
        int dataLength = data.length;

        for(int i=0; i<dataLength; i++){
            if(data[i] == key){
                System.out.println(key + " is in the " + i + ex.ordianlNumber(i) + " position");
            }
        }

//        7 is in the 3rd position
    }

    public String ordianlNumber(int num){
        if(num == 1){
            return "st";
        }else if(num == 2){
            return "nd";
        }else if(num == 3){
            return "rd";
        }else{
            return "th";
        }
    }
}





이진 탐색을 위해서는 data값이 오름차순으로 정렬이 되어있어야합니다.

그 이유는 이진탐색의 탐색 방법에 있습니다.

위의 data 배열을 오름차순으로 정렬하면

data = {1, 2, 5, 7, 8, 10, 11, 12, 29, 81}

입니다. 여기서 7을 찾는다 하면

일단 이 배열의 가운데 값을 봅니다.

배열의 크기가 홀수면 딱 가운데가 존재하겠지만 여기선 짝수네요.

별 상관 없습니다. 배열 전체를 봤을 때

시작은 0번째, 맨 마지막은 9번째 이므로

\(\frac{0+9}{2} == 4.5\) 이므로 4번째 수인 8을 봅니다.

8은 7보다 크죠. 그럼 8을 포함한 위의 값은 탐색할 필요가 없어집니다.


{1, 2, 5, 7}에서만 검색하면 되죠.

즉, 이제 시작은 0번째, 맨 마지막은 3번째 가 되고,

가운데 값은 1번째 값인 2입니다.

2는 7보다 작으므로 {1, 2}는 이제 탐색할 필요가 없습니다.


즉, {5, 7}만 탐색하면 되죠.

시작은 2번째, 끝은 3번째이고

가운데 값은 (2+3)/2 == 2, 즉 2번째 값인 5입니다.

5는 7보다 작죠.

즉 5는 탐색할 필요가 없고 나머지 7만 탐색하면 됩니다.

시작은 3번째, 끝도 3번째. 즉 가운데 값은 3번째인 7입니다.

찾으려고 하는 숫자를 찾았습니다!




복잡도(Time Complexity)


탐색하는 배열의 크기가 n이고, k번의 연산을 통해

원하는 값을 찾았다고 가정을 하겠습니다.

한번 연산을 하면 탐색해야 할 n개의 숫자가 반으로 줄어듭니다. 즉,

\[(\frac{1}{2})^1 n\]

두번 연산을 하면 탐색해야 할 n개의 숫자가 1/4이 되겠죠. 즉,

\[(\frac{1}{2})^2 n\]

k번 연산 후, 원하는 값을 찾았으므로,

k번 연산 후 탐색해야 할 숫자의 개수는 한개입니다. 이를 식으로 나타내면

\[(\frac{1}{2})^k n = 1\]

양변을 n으로 나눠주고, 양변의 분모 분자를 뒤집어주면

\[2^k = n\]

입니다. 양변에 \(log_2\)를 씌워주면,

\[k = log_2 n\]

입니다. k가 연산 횟수이기 때문에, 이진탐색의 복잡도는

\(O(log_2 n)\) 이 됩니다.




예제 코드


import java.util.Random;
import java.util.Arrays;

public class BinarySearch {
    public static void main(String[] ar){
        BinarySearch ex = new BinarySearch();
        Random rand = new Random();
        int arrayLength = 10;

        int[] intArray = new int[arrayLength];
        for(int i=0; i<arrayLength; i++){
            intArray[i] = rand.nextInt(20) + 1;
        }

        Arrays.sort(intArray);
        System.out.print("array = ");
        ex.printArray(intArray);
        int key = rand.nextInt(20) + 1;
        System.out.println("key = " + key);
        ex.doBinarySearch(intArray, key);

    }

    public void doBinarySearch(int[] inputArray, int key){
        int inputArraySize = inputArray.length;
        int upperBound = inputArraySize-1;
        int lowerBound = 0;
        int median;

        while(true){
            if(upperBound < lowerBound){
                System.out.println(key + " is not in the inputArray");
                break;
            }

            median = (upperBound+lowerBound)/2;

            if(inputArray[median] == key){
                System.out.println(key + " is in the " + median + " position");
                break;
            }else if(inputArray[median] > key){
                upperBound = median-1;
            }else{
                lowerBound = median+1;
            }
        }


    }

    public void printArray(int[] array){
        for(int item: array){
            System.out.print(item + " ");
        }
        System.out.println();
    }
}

1 ~ 20 사이의 숫자를 랜덤으로 받아

크기 10인 배열을 만들고, 오름차순으로 정렬한 후에

doBianrySearch()에서 key값을 찾습니다.


이진 탐색은 Recursive하게도 많이 구현합니다.

import java.util.Random;
import java.util.Arrays;

public class BinarySearchRecursive {
    public static void main(String[] ar){
        BinarySearchRecursive ex = new BinarySearchRecursive();
        Random rand = new Random();
        int arrayLength = 10;

        int[] intArray = new int[arrayLength];
        for(int i=0; i<arrayLength; i++){
            intArray[i] = rand.nextInt(20) + 1;
        }

        Arrays.sort(intArray);
        System.out.print("array = ");
        ex.printArray(intArray);
        int key = rand.nextInt(20) + 1;
        System.out.println("key = " + key);
        int result = ex.doBinarySearchRecursive(intArray, key, 0, arrayLength-1);

        if(result == -1){
            System.out.println(key + " is not in the array");
        }else{
            System.out.println(key + " is in the " + result + " position");
        }

    }

    public int doBinarySearchRecursive(int[] inputArray, int key, int start, int end){
        int upperBound = end;
        int lowerBound = start;
        int median;

        while(true){
            if(upperBound < lowerBound){
                return -1;
            }
            median = (upperBound+lowerBound)/2;

            if(inputArray[median] == key){
                return median;
            }else if(inputArray[median] > key){
                return doBinarySearchRecursive(inputArray, key, lowerBound, median-1);
            }else{
                return doBinarySearchRecursive(inputArray, key, median+1, upperBound);
            }
        }


    }

    public void printArray(int[] array){
        for(int item: array){
            System.out.print(item + " ");
        }
        System.out.println();
    }
}

이진탐색을 recursive하게 구현할 때는 받는 매개변수 값으로

탐색의 시작위치와 마지막 위치도 받아야합니다.