
퀵 정렬 핸드북
핵심 요약: 퀵 정렬(Quick Sort)은 Tony Hoare가 1959년 모스크바대 방문 중에 고안하여 1961년 논문으로 발표한 분할 정복 기반의 고속 정렬 알고리즘이다1. 평균 시간 복잡도는 , 최악의 경우 이며, 인플레이스(in-place) 방식으로 메모리 효율성이 높다1.
1. 탄생 배경 및 목적
퀵 정렬은 기계 번역 작업에서 러시아어 단어를 알파벳 순으로 정렬해야 했던 Tony Hoare의 필요에 의해 탄생했다. 기존 삽입 정렬의 시간적 한계를 극복하고자, **분할(partition) 후 정복(conquer)**을 통해 서브문제를 재귀적으로 해결하는 아이디어를 도입했다12. Algol60의 재귀 지원이 퀵 정렬의 완전한 구현을 가능하게 했으며, 이는 이후 모든 프로그래밍 언어에 큰 영향을 주었다1.
2. 알고리즘 구조
2.1 전체 흐름
- 피벗 선택
- 배열에서 한 요소를 피벗(pivot)으로 선택
- 위치: 첫 번째, 마지막, 임의, 혹은 중앙값(median-of-three) 등
- 분할(partition)
- 피벗보다 작은 요소는 왼쪽, 큰 요소는 오른쪽으로 재배치
- 최종적으로 피벗은 정확한 정렬 위치 확보
- 재귀 호출
- 피벗 기준 좌우 서브배열에 대해 같은 과정을 반복
- 기저 조건: 서브배열 크기가 1 이하일 때 종료3
2.2 파티셔닝 구현 예시 (파이썬)
def partition(arr, low, high):
"""
Lomuto 분할 기법을 사용한 배열 분할 함수
피벗을 기준으로 작은 값은 왼쪽, 큰 값은 오른쪽으로 재배치
"""
# 1단계: 피벗 선택 - 배열의 마지막 요소를 피벗으로 설정
pivot = arr[high]
# 2단계: 포인터 초기화 - i는 피벗보다 작은 요소들의 경계를 추적
# i = low - 1로 초기화하여 아직 작은 요소가 없음을 표시
i = low - 1
# 3단계: 배열 순회하며 분할 수행 (피벗 제외하고 low부터 high-1까지)
for j in range(low, high):
# 현재 요소가 피벗보다 작은 경우
if arr[j] < pivot:
# 작은 요소 구간을 한 칸 확장
i += 1
# 작은 요소를 앞쪽(i 위치)으로 이동
# j 위치의 작은 요소와 i 위치의 큰 요소를 교환
arr[i], arr[j] = arr[j], arr[i]
# 4단계: 피벗을 최종 정렬 위치에 배치
# i+1은 피벗이 들어갈 올바른 위치 (작은 요소들 바로 다음)
# 피벗과 i+1 위치의 요소를 교환하여 피벗을 중앙에 배치
arr[i+1], arr[high] = arr[high], arr[i+1]
# 피벗의 최종 위치 인덱스 반환
return i+1
def quick_sort(arr, low, high):
"""
퀵 정렬 메인 함수 - 분할 정복 방식으로 재귀적 정렬 수행
Parameters:
arr: 정렬할 배열
low: 정렬 구간의 시작 인덱스
high: 정렬 구간의 끝 인덱스
"""
# 기저 조건: 서브배열에 2개 이상의 요소가 있을 때만 정렬 진행
# low >= high이면 요소가 1개 이하이므로 이미 정렬된 상태
if low < high:
# 1단계: 분할 - 피벗을 기준으로 배열을 둘로 나누고 피벗의 최종 위치 획득
pi = partition(arr, low, high)
# 2단계: 정복 - 피벗 기준으로 나뉜 두 서브배열을 재귀적으로 정렬
# 왼쪽 서브배열 정렬 (피벗보다 작은 요소들)
# 범위: low부터 pi-1까지 (피벗 제외)
quick_sort(arr, low, pi-1)
# 오른쪽 서브배열 정렬 (피벗보다 큰 요소들)
# 범위: pi+1부터 high까지 (피벗 제외)
quick_sort(arr, pi+1, high)
# 사용 예시
if __name__ == "__main__":
# 테스트 배열
test_array = [10, 7, 8, 9, 1, 5]
print("정렬 전:", test_array)
# 퀵 정렬 실행 (전체 배열을 대상으로)
quick_sort(test_array, 0, len(test_array) - 1)
print("정렬 후:", test_array)
"""
실행 과정 예시:
초기: [10, 7, 8, 9, 1, 5]
1회차 분할 후: [1, 5, 8, 9, 10, 7] (피벗=5, 위치=1)
왼쪽 [1] 정렬 완료
오른쪽 [8, 9, 10, 7] 계속 분할...
최종: [1, 5, 7, 8, 9, 10]
"""
위 코드는 인플레이스로 동작하며, 추가 메모리 없이 정렬을 수행한다3.
3. 성능 분석
구분 | 평균 시간 복잡도 | 최악 시간 복잡도 | 공간 복잡도 |
---|---|---|---|
퀵 정렬 | * |
* 꼬리 재귀 최적화 또는 작은 파티션부터 재귀 호출 시 보장14.
- 평균 성능: 비교 기반 정렬 중 상수 계수 효과로 병합 정렬·힙 정렬 대비 빠른 경향
- 최악 성능: 이미 정렬된 배열·역정렬된 배열에서 피벗이 계속 극단값인 경우 발생
4. 주요 최적화 기법
- 피벗 선택 개선
- 작은 서브배열 처리
- 서브배열 크기가 임계값(k, 예:10) 이하 시 삽입 정렬으로 전환1
- 또는 재귀 중지 후 최종에 한 번 삽입 정렬 수행
- 재귀 깊이 최소화
- 3방향 파티셔닝(돌연변이 많은 배열 최적화)
- 피벗과 같은 값은 중앙 그룹으로 묶어 재귀 호출 횟수 감소
5. 장·단점 및 활용 팁
5.1 장점
- 평균적 우수한 성능: 실제 데이터에서 매우 빠름
- 인플레이스 정렬: 추가 메모리 최소 사용
- 간결한 재귀 구조: 구현이 직관적
5.2 단점
- 최악 복잡도 : 극단적 분할 시 성능 저하
- 불안정 정렬: 동일 값 순서 보장 안 됨
5.3 실무 적용 시 유의사항
- 피벗 전략을 데이터 특성에 맞게 선택
- 임계값 튜닝: 작은 배열 처리용 알고리즘(삽입 정렬) 전환 크기 실험
- 꼬리 재귀 최적화로 재귀 깊이 제어
6. 결론
퀵 정렬은 분할 정복 아이디어를 기반으로 수십 년간 표준 정렬 알고리즘으로 자리 잡았다. 피벗 선택·작은 서브배열 처리·재귀 최적화 기법을 적절히 결합하면, 다양한 실무 환경에서 최고의 성능을 발휘할 수 있다.
1 Wikipedia: Quicksort (1959–1961, Tony Hoare) 3 GeeksforGeeks: Quick Sort Algorithm Steps 5 Youcademy: Optimizations for Quick Sort (Median-of-Three) 4 GeeksforGeeks: QuickSort Tail Call Optimization
Footnotes
-
https://en.wikipedia.org/wiki/Quicksort ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html ↩
-
https://www.geeksforgeeks.org/dsa/quick-sort-algorithm/ ↩ ↩2 ↩3
-
https://www.geeksforgeeks.org/dsa/quicksort-tail-call-optimization-reducing-worst-case-space-log-n/ ↩ ↩2 ↩3