ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 정렬 알고리즘 (4): 퀵 정렬 (Quick Sort)
    알고리즘 2025. 2. 13. 16:00

    1. 퀵 정렬이란?

    퀵 정렬(Quick Sort)은 "분할 정복(Divide and Conquer)" 기법을 활용하여 리스트를 정렬하는 효율적인 알고리즘이다. 평균적으로 O(n log n)의 성능을 가지며, 대부분의 경우 매우 빠르게 동작하기 때문에 실무에서 널리 사용된다.

    2. 퀵 정렬의 동작 원리

    퀵 정렬은 피벗(Pivot) 요소를 선택하고, 피벗보다 작은 요소는 왼쪽으로, 큰 요소는 오른쪽으로 이동시키는 분할 과정을 반복하여 정렬을 수행한다.

    2.1 정렬 과정

    1. 배열에서 피벗(Pivot)을 선택한다.
    2. 피벗을 기준으로 작은 값과 큰 값을 좌우로 분할(Partitioning)한다.
    3. 분할된 두 개의 하위 배열을 재귀적으로 정렬한다.
    4. 모든 하위 배열이 정렬되면 최종적으로 정렬이 완료된다.

    2.2 Partition 함수 설명

    Partition 함수는 배열을 피벗을 기준으로 나누는 핵심 기능을 담당한다. 이 과정에서 피벗보다 작은 값은 왼쪽, 큰 값은 오른쪽으로 이동하여 피벗이 올바른 위치에 놓이도록 한다.

    Partition 함수는 다음과 같은 방식으로 동작한다:

    1. 피벗을 배열의 마지막 요소로 선택한다.
    2. i 변수를 low - 1로 설정하여, 피벗보다 작은 요소들이 위치할 곳을 추적한다.
    3. j 변수를 low부터 high - 1까지 반복하면서 피벗보다 작은 요소를 찾으면 i를 증가시키고 해당 요소와 교환한다.
    4. 반복이 끝난 후, 피벗을 i+1 위치와 교환하여 피벗이 최종적으로 올바른 위치에 오도록 한다.
    5. 피벗의 최종 위치를 반환하여 재귀적으로 퀵 정렬을 수행할 수 있도록 한다.

    2.3 예제 코드 (Java)

    public class QuickSort {
        // 퀵 정렬 메서드
        public static void quickSort(int[] arr, int low, int high) {
            if (low < high) {
                int pivotIndex = partition(arr, low, high);
                quickSort(arr, low, pivotIndex - 1); // 왼쪽 부분 정렬
                quickSort(arr, pivotIndex + 1, high); // 오른쪽 부분 정렬
            }
        }
    
        // 피벗을 기준으로 배열을 분할하는 메서드
        private static int partition(int[] arr, int low, int high) {
            int pivot = arr[high]; // 마지막 요소를 피벗으로 선택
            int i = low - 1;
            
            for (int j = low; j < high; j++) {
                if (arr[j] < pivot) { // 피벗보다 작은 값이면
                    i++;
                    swap(arr, i, j);
                }
            }
            swap(arr, i + 1, high); // 피벗을 최종 위치로 이동
            return i + 1;
        }
    
        // 두 요소를 교환하는 메서드
        private static void swap(int[] arr, int i, int j) {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    
        // 퀵 정렬 실행 및 결과 출력
        public static void main(String[] args) {
            int[] data = {64, 34, 25, 12, 22, 11, 90};
            System.out.println("정렬 전: ");
            for (int num : data) {
                System.out.print(num + " ");
            }
            
            quickSort(data, 0, data.length - 1);
            
            System.out.println("\n정렬 후: ");
            for (int num : data) {
                System.out.print(num + " ");
            }
        }
    }

    3. 퀵 정렬의 성능 분석

    퀵 정렬의 시간 및 공간 복잡도를 분석하면 다음과 같다.

    3.1 시간 복잡도

    • 최선의 경우 (Best Case): O(n log n) → 균형 잡힌 분할이 이루어진 경우
    • 평균의 경우 (Average Case): O(n log n)
    • 최악의 경우 (Worst Case): O(n²) → 이미 정렬된 배열에서 피벗을 가장 작은 또는 큰 값으로 선택할 경우

    퀵 정렬의 평균 성능은 O(n log n)이지만, 피벗 선택 방식에 따라 성능이 좌우된다.

    3.2 공간 복잡도

    • 공간 복잡도: O(log n) → 재귀 호출로 인해 스택 메모리를 사용

    4. 퀵 정렬의 장점과 단점

    4.1 장점

    • 평균적으로 매우 빠른 성능 (O(n log n))
    • 비교적 적은 메모리 사용 (In-place 정렬)
    • 다양한 피벗 선택 전략을 활용하여 최적화 가능

    4.2 단점

    • 최악의 경우 O(n²)로 성능이 저하될 수 있음
    • 재귀 호출로 인해 스택 오버플로우 가능성 있음
    • 안정 정렬(Stable Sort)이 아님 (같은 값의 순서가 바뀔 수 있음)

    5. 퀵 정렬의 활용 및 개선 방법

    5.1 활용 사례

    • 데이터가 큰 경우 빠른 정렬이 필요할 때
    • 정렬이 자주 발생하는 대규모 시스템 (데이터베이스, 검색 엔진 등)

    5.2 개선 방법

    • 랜덤 피벗 선택(Randomized Quick Sort): 피벗을 랜덤하게 선택하여 최악의 경우를 방지
    • 중앙값 피벗 선택(Median-of-Three): 첫 번째, 중간, 마지막 요소 중 중간값을 피벗으로 선택하여 성능 개선
    • 혼합 정렬(Hybrid Sort): 작은 배열에서는 삽입 정렬을 사용하여 성능 향상

    6. 결론

    퀵 정렬은 평균적으로 O(n log n)의 성능을 가지며, 대부분의 상황에서 효율적인 정렬 알고리즘이다. 하지만 최악의 경우 O(n²)으로 성능이 저하될 수 있어 피벗 선택 전략을 적절히 활용하는 것이 중요하다. 대규모 데이터 정렬이 필요한 경우 퀵 정렬을 최적화하여 활용하면 높은 성능을 얻을 수 있다.

Designed by Tistory.