Post

[CS 면접 질문] 알고리즘

병합 정렬 (Merge Sort)

병합 정렬은 정렬하고자 하는 배열을 절반씩 분할하여 이를 다시 합하면서 정렬하는 방법이다. 더이상 나누어지지 않을 때 까지 분할하다가 원소가 하나인 배열일 때 자기 자신, 즉 원소 하나를 반환한다. 이 때 반환한 값끼리 combine될 때 비교가 이뤄지며, 비교 결과를 기반으로 정렬되어 임시 배열에 저장된다. 실제 정렬은 나눈 것을 병합하는 과정에서 이뤄지는 것이다.

시간복잡도공간복잡도
O(nlogn)O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void mergeSort(int[] arr) {
    int[] helper = new int[arr.length];
    mergeSort(arr, helper, 0, arr.length-1);
}

void mergeSort(int[] arr, int[] helper, int low, int high) {
    if (low < high) {
        int middle = (low+high)/2;
        mergeSort(arr, helper, low, middle);      // 왼쪽 절반 정렬
        mergesort(arr, helper, middle+1, high);   // 오른쪽 절반 정렬
        merge(arr, helper, low, middle, high);    // 병합
    }
}

void merge(int[] arr, int[] helper, int low, int middle, int high) {
    /* 절반짜리 두 배열을 helper 배열에 복사 */
    for (int i=low; i<=high; i++) {
        helper[i] = arr[i];
    }

    int helperLeft = low;
    int helperRight = middle+1;
    int current = low;

    /* helper 배열 순회. 왼쪽 절반과 오른쪽 절반을 비교해 작은 원소를 원래 배열에 복사 */
    while (helperLeft <= middle && helperRight <= high) {
        if (helper[helperLeft] <= helper[helperRight]) {
            arr[current] = helper[helperLeft];
            helperLeft++;
        } else {
            arr[current] = helper[helperRight];
            helperRight++;
        }
        current++;
    }

    /* 왼쪽 절반에 남은 원소를 원래 배열에 복사 */
    for (int i=helperLeft; i<=middle-helperLeft; i++) {
        arr[current++] = helper[i];
    }
}


퀵 정렬 (Quick Sort)

퀵 정렬은 무작위로 선정된 한 원소인 피봇(pivot)을 사용해 배열을 분할하는데, 피봇보다 작은 원소들은 앞에, 큰 원소들을 뒤로 보낸다. 배열과 그 부분 배열을 반복적으로 분할해 나가면 결국 배열은 정렬된 상태에 도달한다. 하지만 피봇이 중간값(median)에 가깝다는 보장이 없기 때문에, 최악의 경우(피봇이 배열의 최솟값 또는 최댓값일 때) 수행 시간이 O(n^2)이 될 수 있다.

시간복잡도공간복잡도
O(nlogn)O(logn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void quickSort(int[] arr, int left, int right) {
    int index = partition(arr, left, right);
    if (left < index-1) {   // 왼쪽 절반 정렬
        quickSort(arr, left, index-1);
    }
    if (index < right) {    // 오른쪽 절반 정렬
        quickSort(arr, index, right);
    }
}

int partition(int[] arr, int left, int right) {
    int pivot = arr[(left+right)/2];    // 분할 기준 원소
    while (left <= right) {
        // 왼쪽에서 오른쪽으로 옮겨야 하는 원소 탐색
        while (arr[left] < pivot) left++;

        // 오른쪽에서 왼쪽으로 옮겨야 하는 원소 탐색
        while (pivot < arr[right]) right--;

        // 원소를 swap하고 left와 right 이동
        if (left <= right) {
            swap(arr, left, right);
            left++;
            right--;
        }
    }
    return left;
}


정렬 알고리즘 시간, 공간 복잡도 비교

알고리즘공간복잡도시간복잡도 (avg)시간복잡도 (worst)
버블 정렬 (Bubble Sort)O(1)O(n^2)O(n^2)
선택 정렬 (Selection Sort)O(1)O(n^2)O(n^2)
삽입 정렬 (Insertion Sort)O(1)O(n^2)O(n^2)
병합 정렬 (Merge Sort)O(n)O(nlogn)O(nlogn)
퀵 정렬 (Quick Sort)O(1)O(nlogn)O(n^2)
힙 정렬 (Heap Sort)O(1)O(nlogn)O(nlogn)
계수 정렬 (Counting Sort)O(n)O(n)O(n)
기수 정렬 (Radix Sort)O(n)O(n)O(n)


정렬된 배열 안에서 특정 원소를 찾을 때 탐색 범위를 절반씩 줄여나가는 방식의 알고리즘으로, 선형탐색에 비해 빠른 속도를 보장한다. O(logN)의 시간복잡도를 갖는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int binarySearch(int[] arr, int key) { 
    int start = 0;
    int end = arr.length - 1;
    int mid = 0;

    while (start <= end) {
        mid = (start + end) / 2;
        if (arr[mid] == key) {
            return mid;
        } else if (arr[mid] < key) {
            start = mid + 1;
        } else if (arr[mid] > key) {
            end = mid - 1;
        }
    }
    return -1;
}


동적 계획법 (Dynamic Programming)

동적 계획법이란 주어진 문제를 풀기 위해 문제를 여러 개의 하위 문제(sub-problem)로 나누어 푸는 방법이다. 작은 문제들 속에서 ‘계속 반복되는 연산’을 활용하여 빠르게 풀 수 있는 것이 핵심이다.

동적 계획법의 조건

  1. 작은(하위) 문제에서 반복이 일어난다.
  2. 같은 문제는 항상 정답이 같다.

이 두 가지 조건이 충족한다면 동적 계획법을 이용하여 문제를 풀 수 있다.
같은 문제가 항상 정답이 같고, 반복적으로 일어난다는 점을 활용해 메모이제이션(Memoization)1으로 큰 문제를 해결해나가는 것이다.

구현 방식

  • Top-down : 큰 문제를 풀다가 풀리지 않은 작은 문제가 있다면 그때 해결하는 방법 (재귀 방식)
  • Bottom-up : 작은 문제부터 차근차근 구하는 방법




참고 자료

Interview_Question_for_Beginner
tech-interview-for-developer




Notes

  1. 메모이제이션(Memoization): 한 번 계산한 문제는 다시 계산하지 않도록 저장해두고 활용하는 방식 ↩︎

This post is licensed under CC BY 4.0 by the author.