[Algorithm] – “퀵 정렬(Quick Sort)”

[Algorithm] – “퀵 정렬(Quick Sort)”

5월 7, 2024

퀵 정렬(Quick Sort)의 개념

퀵 정렬은 분할 정복(Divide and Conquer) 방식을 통해 데이터를 정렬하는 방식입니다. 피벗(pivot) 이라고 불리는 특정 원소를 기준으로 주어진 배열을 두 부분배열로 분할하고, 각 부분배열에 대해서 퀵 정렬을 순환적으로 적용해 데이터를 정렬하게 됩니다.

아래와 같은 흐름을 이용해 데이터를 정렬합니다.

  1. 피벗 선택: 배열에서 하나의 요소를 선택합니다. 이 요소를 피벗(pivot)이라고 합니다. 피벗의 선택 방법은 다양하며, 이는 퀵 정렬의 성능에 큰 영향을 미칠 수 있습니다. 가장 간단한 방법은 배열의 첫 번째 요소나 마지막 요소를 피벗으로 선택하는 것입니다.
  2. 분할(Partitioning): 피벗을 기준으로 배열을 두 부분으로 나눕니다. 첫 번째 부분에는 피벗보다 작은 모든 요소들이 오고, 두 번째 부분에는 피벗보다 큰 모든 요소들이 옵니다. 이 과정에서 피벗은 최종 정렬 위치로 이동하게 됩니다.
  3. 정복(Conquer): 분할된 두 부분에 대해 재귀적으로 퀵 정렬을 수행합니다.

퀵 정렬의 Python 구현

Python 으로 퀵 정렬을 아래와 같이 구현할 수 있습니다.

def quick_sort(arr: list, n: int) -> None:
    if n <= 1:
        return

    pivot = _partition(arr, n - 1)
    quick_sort(arr, pivot)
    quick_sort(arr[pivot + 1 :], n - pivot - 1)


def _partition(arr: list, n: int) -> int:
    pivot = arr[n]
    i = -1

    for j in range(n):
        if arr[j] < pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i + 1], arr[n] = arr[n], arr[i + 1]

    return i + 1
    

위의 코드는 아래와 같이 동작합니다.

  1. quick_sort 에서, 분할 함수 _partition() 을 호출해, 피벗을 기준으로 배열을 두 부분배열로 분할합니다.
  2. 왼쪽 부분배열에 대해서 퀵 정렬을 수행하고, 오른쪽 부분배열에 대해서 퀵 정렬을 재귀적으로 호출해 정렬을 완료합니다.

아래와 같은 배열에서, 먼저 _partition 이 호출됩니다. 피벗은 70이 되고, 70을 기준으로 작은 값은 왼쪽으로, 큰 값은 오른쪽으로 이동하게 됩니다.

 -   +   -   +   -   -    ^ (pivot)
[10, 80, 30, 90, 40, 50, 70]

# 정렬 이후        ^ (pivot)
[10, 30, 40, 50, 70, 90, 80]

피벗을 기준으로 이제 왼쪽 배열과 오른쪽 배열이 나누어졌는데, 위의 과정처럼 왼쪽 배열에 대해서 퀵 정렬을 수행하고, 이어서 오른쪽 배열에 대해서 퀵 정렬을 수행하여 정렬을 완료합니다.

              ^  |       ^  # 50을 기준으로 quick_sort, 80을 기준으로 quick_sort
[10, 30, 40, 50, 70, 90, 80]


# 왼쪽 부분 정렬
         ^   |  |       ^  
[10, 30, 40, 50, 70, 90, 80]

...
# 왼쪽 부분 정렬 완료
 |   |   |   |   |       ^  
[10, 30, 40, 50, 70, 90, 80]

# 오른쪽 부분 정렬 완료
 |   |   |   |   |   |   | 
[10, 30, 40, 50, 70, 90, 80]

퀵 정렬의 성능 분석과 특징

  • 최악의 경우, O(n^2) 의 시간 복잡도를 가집니다.
    • 예컨대 최악의 경우는 배열이 이미 정렬된 상태이거나, 역순으로 정렬되어 있고 피벗을 배열의 가장 첫 번째 원소로 지정하는 경우가 있을 수 있습니다. 이러한 경우, 피벗만 제저리를 잡고 나머지 모든 원소가 하나의 부분배열이 됩니다.
    • 마찬가지로, 피벗이 항상 최댓값이나 최솟값이 되는 경우 최악의 경우가 됩니다.
  • 최선의 경우, O(nlogn) 의 시간 복잡도를 가집니다.
  • 평균적인 경우, O(nlogn) 의 시간 복잡도를 가집니다.
  • 최악의 경우 다른 정렬 알고리즘보다 빠르지 않지만, 피벗 설정의 임의성만 보장된다면 평균 성능(O(nlogn)) 을 보일 가능성이 매우 높은 정렬 알고리즘입니다.

Leave A Comment

Avada Programmer

Hello! We are a group of skilled developers and programmers.

Hello! We are a group of skilled developers and programmers.

We have experience in working with different platforms, systems, and devices to create products that are compatible and accessible.