김현우
  • 퀵 정렬 (Quick Sort, 퀵 소트)
    2025년 02월 02일 20시 27분 41초에 업로드 된 글입니다.
    작성자: kugorang

    들어가며

    퀵 정렬(Quick Sort) 알고리즘의 동작 과정을 시각적으로 표현

    최근 모 기업의 필기 테스트에서 다뤄졌던 문제를 계기로 퀵 정렬에 대해 다시 공부하게 되었다. 퀵 정렬은 DB 인덱스처럼 여러 분야에서 중요한 역할을 하는 알고리즘으로, 효율성과 간결한 구조 덕분에 널리 사용된다.

    본 글에서는 퀵 정렬의 기본 개념과 동작 원리, 그리고 특히 최악의 경우 발생 원인과 이를 개선하기 위한 최적화 전략을 단계적으로 살펴본다.

    또한, 게임 개발, 3D 웹 애플리케이션, 모바일 게임 등 실시간 응용 프로그램에서 데이터 정렬의 효율성은 전체 시스템 성능에 큰 영향을 미치므로 이 알고리즘의 특성과 한계를 정확히 이해하는 것이 중요하다 생각한다.


    퀵 정렬이란?

    퀵 정렬은 분할 정복(Divide and Conquer) 알고리즘의 대표적인 예로, 배열이나 리스트와 같은 데이터 집합을 빠르게 정렬하는 효율적인 알고리즘이다.

    기본 개념

    퀵 정렬의 핵심 아이디어는 다음과 같다.

    1. 피벗 선택
      배열에서 하나의 요소를 피벗(Pivot)으로 선택한다. 보통 배열의 첫 번째, 마지막 또는 랜덤하게 선택하며, 때로는 미디언-오브-쓰리(Median-of-Three) 기법을 사용해 보다 적절한 피벗을 고른다.
    2. 분할(Partitioning)
      선택한 피벗을 기준으로 배열의 요소들을 두 그룹으로 나눈다.
      • 피벗보다 작은 요소는 왼쪽에,
      • 피벗보다 큰 요소는 오른쪽에 배치되고
      • 피벗은 최종 정렬 위치로 이동한다.
    3. 재귀적 정렬(Recursive Sorting)
      피벗을 기준으로 나뉜 두 부분 배열에 대해 같은 과정을 재귀적으로 수행하여 전체 배열을 정렬한다.

    퀵 정렬의 특징

    • 효율성: 평균적으로 O(n log n)의 시간복잡도를 보이며, 대부분의 경우 매우 빠른 성능을 나타낸다.
    • 제자리 정렬(In-place Sorting): 추가 메모리 없이 원본 배열 내에서 요소의 위치를 교환하며 정렬한다.
    • 불안정 정렬(Unstable Sorting): 동일한 값을 가진 요소들의 상대적 순서를 보장하지 않는다.
    • 분할 정복 전략: 문제를 작은 문제로 분할하여 해결함으로써 복잡한 문제도 쉽게 접근할 수 있다.

    실무 적용 사례

    • 게임 개발: Unity나 Unreal Engine 환경에서 렌더링 순서 조정, AI 우선순위 결정 등 다양한 상황에서 활용된다.
    • 3D 웹 기술: React, Three.js 등으로 대규모 데이터를 실시간 처리할 때 유용하다.

    퀵 정렬 알고리즘의 동작 원리

    퀵 정렬은 피벗을 기준으로 배열을 분할한 후, 각 부분 배열에 대해 재귀적으로 정렬을 수행한다.

    1. 분할(Partition) 과정

    • 피벗 선택: 일반적으로 배열의 마지막 요소를 피벗으로 선택한다. (미디언-오브-쓰리 같은 기법도 사용 가능하다)
    • 배열 분할: 피벗보다 작은 값은 왼쪽, 큰 값은 오른쪽으로 재배열하여 피벗을 최종 정렬 위치로 이동시킨다.

    2. 재귀적 정복(Recursive Sorting)

    • 재귀 호출: 분할된 두 부분 배열에 대해 같은 과정을 재귀적으로 수행한다.
    • 기저 조건: 배열의 길이가 1 이하이면 정렬이 완료되었으므로 재귀를 종료한다.

    3. 결합(Combine) 과정

    퀵 정렬은 in-place 정렬 알고리즘이므로, 별도의 합병(merge) 과정 없이 분할과 정복만으로 전체 배열을 정렬한다.

    C++ 예제 코드

    #include <iostream>
    #include <vector>
    #include <algorithm> // std::swap 사용
    
    using namespace std;
    
    // Partition 함수: 배열을 피벗 기준으로 분할하고 피벗의 최종 인덱스를 반환한다.  
    int partition(vector& 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], arr[j]);
            }
        }
        swap(arr[i + 1], arr[high]); // 피벗을 올바른 위치로 이동시킨다.
        return i + 1;
    }
    
    // 퀵 정렬 함수: 배열을 재귀적으로 정렬한다.  
    void quickSort(vector& arr, int low, int high)
    {
        if (low < high)
        {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }  
    }
    
    int main()
    {
        vector arr = {9, 3, 7, 6, 2, 8, 5};
        quickSort(arr, 0, arr.size() - 1);
    
        for (int num : arr)
            cout << num << " ";
        cout << endl;
    
        return 0;
    }

    728x90

    In-place 정렬이란?

    In-place 정렬은 추가 메모리 없이 원본 배열 내에서 직접 요소의 위치를 변경하여 정렬하는 방식이다.

    • 메모리 효율성: 추가 배열 없이 원본 데이터를 재배열하기 때문에 메모리 사용량이 적어 임베디드 시스템이나 모바일 환경에 유리하다.
    • 빠른 데이터 접근 및 캐시 활용: 연속된 메모리 공간을 사용하므로 CPU 캐시 활용도가 높다.
    • 퀵 정렬에서의 역할: 분할 단계에서 요소를 스왑하여 배열 내에서 직접 정렬하며, 별도의 병합 과정 없이 전체 배열이 정렬된다.

    퀵 정렬의 시간복잡도

    퀵 정렬은 피벗 선택과 분할의 균형 여부에 따라 시간복잡도가 달라진다.

    1. 평균 및 최선의 경우: O(n log n)

    • 설명: 무작위 데이터 또는 균형 잡힌 분할이 이루어지는 경우, 각 분할 단계에서 n번의 연산과 재귀 깊이 log n이 곱해져 평균적으로 O(n log n)의 시간복잡도를 갖는다.

    2. 최악의 경우: O(n²)

    • 설명 및 근거: 배열이 이미 정렬되어 있거나 역순일 때, 피벗이 항상 가장 작은 또는 가장 큰 값이 되면 한쪽에 n-1개, 그다음에는 n-2개… 식으로 분할되어 전체 비교 연산이 (n-1) + (n-2) + … + 1 ≈ n(n-1)/2가 되어 O(n²)가 된다.
    • 예방 방법: 랜덤 피벗 선택이나 미디언-오브-쓰리 기법을 사용해 불균형 분할을 줄임으로써 최악의 경우를 방지할 수 있다.

    3. 공간복잡도

    • 재귀 호출 스택: 평균 O(log n)이며, 불균형 분할 시 최악에는 O(n)까지 증가할 수 있다.

    퀵 정렬의 최악의 경우

    퀵 정렬의 최악의 경우는 주로 피벗 선택에 의한 불균형 분할에서 발생한다.

    발생 조건

    • 불균형 분할: 매 분할 시 피벗이 배열에서 가장 작은 또는 큰 요소가 되어 한쪽에 모든 원소가 몰리게 되는 경우이다.
    • 특정 데이터 패턴: 이미 정렬되어 있거나 역순인 배열에서 첫 번째 혹은 마지막 요소를 피벗으로 선택할 때 발생한다.

    예시

    예를 들어, [1, 2, 3, 4, 5]와 같이 오름차순으로 정렬된 배열에서 첫 번째 요소를 피벗으로 선택하면

    • 첫 분할: 피벗 1, 오른쪽에 [2, 3, 4, 5]
    • 두 번째 분할: 피벗 2, 오른쪽에 [3, 4, 5]
      와 같이 불균형 분할이 반복되어 전체 연산 횟수가 급증한다.

    최악의 경우 개선 방법 및 최적화 전략

    최악의 경우를 예방하기 위해 여러 최적화 전략을 도입한다.

    1. 랜덤 피벗 선택

    • 개념: 배열 내 임의의 위치에서 피벗을 선택해 데이터 순서에 따른 편향을 줄인다.
    • 효과: 평균적으로 균형 잡힌 분할이 이루어져 최악의 경우(O(n²)) 발생 확률을 낮춘다.

    2. 미디언-오브-쓰리

    • 개념: 배열의 첫 번째, 중간, 마지막 요소 중 중간값을 피벗으로 선택한다.
    • 효과: 이미 정렬된 배열 등 특정 패턴에서도 균형 잡힌 분할을 유도해 재귀 깊이를 줄인다.

    3. 하이브리드 정렬

    • 개념: 부분 배열의 크기가 작아지면 재귀 호출 대신 삽입 정렬(Insertion Sort) 등 간단한 정렬 알고리즘으로 전환한다.
    • 효과: 작은 배열에서의 재귀 호출 오버헤드를 줄여 전체 성능을 개선한다.

    4. 꼬리 재귀 최적화

    • 개념 및 원리: 함수의 마지막에 자기 자신을 호출하는 꼬리 재귀는 컴파일러가 현재 스택 프레임을 재사용해 반복문으로 변환할 수 있다.
    • 효과: 스택 메모리 사용을 줄여 재귀 깊이가 깊어지는 상황에서도 안정적인 실행을 보장한다.

    5. Introsort

    • 개념 및 원리: 초기에는 퀵 정렬을 사용하다가 재귀 깊이가 일정 임계치(일반적으로 log(n) 상수배)를 초과하면 힙 정렬(Heap Sort)로 전환하는 하이브리드 정렬 알고리즘이다.
    • 효과: 퀵 정렬의 빠른 평균 성능과 힙 정렬의 최악의 경우 O(n log n) 성능 보장을 결합해 모든 데이터 패턴에서 안정적인 성능을 제공한다.
    • 실제 적용: C++의 std::sort는 Introsort를 기반으로 구현되어 있다.

    나가며

    퀵 정렬은 평균적으로 매우 빠르고 효율적인 정렬 알고리즘(O(n log n))이지만, 피벗 선택에 따라 최악의 경우 O(n²)로 성능이 저하될 수 있다.

    랜덤 피벗 선택, 미디언-오브-쓰리, 하이브리드 정렬, 꼬리 재귀 최적화, 그리고 Introsort와 같은 최적화 기법을 통해 이러한 최악의 경우를 예방하고 안정적인 성능을 유지할 수 있다.

    이 글을 통해 퀵 정렬의 기본 원리, 시간복잡도, 최악의 경우 발생 원인 및 개선 전략을 학습해 두면, 알고리즘 설계와 최적화에 대한 깊은 이해를 갖게 되고, 기술 면접이나 실무 개발에서 큰 도움이 될 것이다.


    참고 자료

    • Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms (3rd ed.)
      알고리즘 전반에 걸친 이론과 정렬 알고리즘 분석을 다룬다.
    • Musser, D. R. (1997). Introsort — a hybrid sorting algorithm. Software: Practice and Experience, 27(8), 983-993.
      Introsort의 개념과 구현 방법을 설명한다.
    • Wikipedia - Quick Sort
      퀵 정렬의 기본 원리 및 피벗 선택 기법을 자세히 설명한다.
    • GeeksforGeeks - Quick Sort Algorithm
      다양한 피벗 선택 및 최적화 전략에 관한 예제와 설명을 제공한다.
    • C++ Reference - std::sort
      C++ 표준 라이브러리의 정렬 함수 구현 설명을 확인할 수 있다.
    • Unity Performance Optimization 및 Unreal Engine Optimization Guidelines
      게임 개발 환경에서 성능 최적화 사례와 권장 사항을 제공한다.
    728x90
    댓글