ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 정렬 알고리즘 6부 퀵 정렬
    24년 11월 이전/레거시-자료구조 2019. 9. 14. 17:21
    반응형

    Contents

    1. 시작하며...
    2. 퀵 정렬의 이해와 구현
    3. 퀵 정렬의 성능 분석
    4. 마치며...

    시작하며...

    구르미의 "Computer Science 정복하기 - 자료구조"의 열 여덟 번째 장입니다. 이 장의 대략적인 내용은 다음과 같습니다.

     

    • 퀵 정렬의 이해와 구현
    • 퀵 정렬의 성능 분석

     

    이 장의 소스코드는 다음을 참고해주세요.

     

    url: https://github.com/gurumee92/datastructure
    branch: ch18
    code directory: src/ch18

     

    자 시작합시다!

    퀵 정렬의 이해와 구현

    우리는 지난 세 장(13 ~ 15장)에 걸쳐서 버블 정렬, 선택 정렬, 삽입 정렬을 배웠습니다. 이들의 시간 복잡도는 O(N^2)입니다. 이제부터는 조금 복잡하지만, 조금 더 성능이 좋은 정렬 알고리즘에 대해서 배우도록 하겠습니다. 이번 장에서는 조금 더 높은 성능을 지닌 알고리즘 중 하나인 퀵 정렬에 대해 공부하도록 하겠습니다.

     

    다음 배열을 퀵 정렬을 통해서 오름차순으로 정렬한다고 가정합니다.

    배열에서 각 위치가 표현하는 것은 다음과 같습니다.

     

     

    • left 정렬 대상의 가장 왼쪽 지점을 가리킵니다.
    • right 정렬 대상의 가장 오른쪽 지점을 가리킵니다.
    • pivot 현재 정렬의 중심점입니다.
    • low 피벗을 제외한 가장 왼쪽에 위치한 지점을 가리킵니다.
    • high 피벗을 제외한 가장 오른쪽에 위치한 지점을 가리킵니다.

     

    이제 low가 가리키는 원소가 pivot 보다 작은 수가 나올 때까지 low를 1씩 증가시킵니다. 즉 pivot보다 큰 수인 7을 가리키는 지점에서 멈춥니다.

     

     

    이제 반대로 pivot보다 큰 수가 나올 때까지 high를 1씩 감소시킵니다. 즉 pivot 보다 작은 수인 4를 가리키는 지점에서 멈춥니다.

     

     

    low <= high 라면 두 위치를 바꿉니다.

     

     

    또한, low <= high를 만족할 때까지 위의 과정을 반복합니다. 이제 low가 가리키는 원소가 pivot 보다 작은 수가 나올 때까지 low를 1씩 증가시킵니다. 즉 pivot보다 큰 수인 9을 가리키는 지점에서 멈춥니다.

     

     

    이제 반대로 pivot보다 큰 수가 나올 때까지 high를 1씩 감소시킵니다. 즉 pivot 보다 작은 수인 2를 가리키는 지점에서 멈춥니다.

     

     

    low <= high 라면 두 위치를 바꿉니다.

    low <= high를 만족하기 때문에 또 low, high를 움직입니다. low가 가리키는 원소가 pivot 보다 작은 수가 나올 때까지 low를 1씩 증가시킵니다. 즉 pivot보다 큰 수인 9을 가리키는 지점에서 멈춥니다.

    이제 반대로 pivot보다 큰 수가 나올 때까지 high를 1씩 감소시킵니다. 즉 pivot 보다 작은 수인 2를 가리키는 지점에서 멈춥니다.

    바로 이 순간, high < low 인 순간, 위 반복을 멈추고 high 위치의 원소와 pivot과 교환합니다.

    이제 high 위치에서 왼쪽 오른쪽으로 재귀적으로 퀵 정렬을 수행하면 됩니다.

    한 번 다음에 일어나는 과정들을 직접 손으로 그려 보세요! 이를 토대로 만든 코드는 다음과 같습니다.

     

    src/ch18/main.c

    void Swap(int arr[], int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    
    int Partition(int arr[], int left, int right) {
        int pivot = arr[left];
        int low = left + 1;
        int high = right;
    
        while (low <= high) {
            while (low <= right && pivot > arr[low]){
                low += 1;
            }
    
            while(high >= left + 1 && pivot < arr[high]) {
                high -= 1;
            }
    
            if (low <= high) {
                Swap(arr, low, high);
            }
        }
    
        Swap(arr, left, high);
        return high;
    }
    
    void QuickSort(int arr[], int start, int end) {
    
        if (start > end) {
            return;
        }
    
        int pivot = Partition(arr, start, end);
        QuickSort(arr, start, pivot-1);
        QuickSort(arr, pivot + 1, end);
    }

    코드를 통해 알 수 있는 것은 퀵 정렬 역시, 분할 정복의 일종이라는 것입니다. 퀵 정렬은 Partion 함수에서 결국 high 위치에서 쪼갤 때, 정렬하면서 쪼개기 때문입니다.

    퀵 정렬의 성능 분석

    이제 퀵 정렬의 성능을 알아봅시다. 퀵 정렬도 병합 정렬과 마찬가지로, log(N)의 평균 시간 복잡도를 갖는 쪼개기 연산이 들어갑니다. 각 쪼개기 연산 시 N번의 비교 연산이 들어갑니다.

     

    즉 시간 복잡도는 O(N * log(N)) 입니다.

     

    퀵 정렬의 경우 평균 시간 복잡도 O(N * log(N))입니다만, 최악의 경우는 O(N^2)입니다. 다만, 데이터가 커질 수록, 최악의 경우의 수가 나오기가 매우 힘들기 때문에, 평균 시간 복잡도를 통해 성능을 분석하는 것입니다. 또한 병합 정렬과 달리 별도의 공간은 필요하지 않습니다.

    마치며...

    이번 시간에는 정렬 알고리즘 중 퀵 정렬에 대해서 살펴보았습니다. 다음 장에서는 기수 정렬에 대해서 살펴보도록 하겠습니다.

    728x90
Designed by Tistory.