step1 : pivot의 위치를 선정(그림에서는 맨 왼쪽)
step2 : low는 pivot보다 큰 숫자가 나올 때까지 오른쪽으로 이동하고, high는 pivot보다 작은 숫자가 나올 때까지 왼쪽으로 이동한다.
step3 : low와 high pointer가 멈추면 서로 교환해준다. low와 high pointer를 다시 이동시켜 준다.
step4 : low와 high가 엇갈릴 때까지 같은 조건으로 low와 high pointer를 이동한다.
step5 : pointer의 이동을 멈추고 pivot과 high를 교환한다.
step6 : pivot을 기준으로 왼쪽에는 작은 숫자들, 오른쪽에는 큰 숫자들이 위치하며 나눠진다. 현재의 pivot에 해당하는 숫자는 자리가 고정된다.
step7 : 이후에는 왼쪽 숫자들과 오른쪽 숫자들의 pivot을 선정하여 재귀적으로 step1~6를 반복한다.
# 💡퀵 정렬
# 2. 분할해서 pivot 기준으로 pivot보다 작은 것은 왼쪽으로, pivot보다 큰 것은 오른쪽으로 넣기
def partition(start, end):
# 2-1. pivot 지정 - 왼쪽 끝으로 지정
pivot = quick_nums[start]
low = start + 1
high = end
# 2-2. low high 엇갈릴 때까지 반복
while True: # low와 high가 엇갈려 있지 않고,
while low <= high and quick_nums[low] <= pivot: # 각 세부 반복문을 돌면서 low는 pivot보다 큰 수가 나올 때까지 오른쪽으로 이동
low += 1
while low <= high and quick_nums[high] >= pivot: # high는 pivot보다 작은 수가 나올 때까지 왼쪽으로 이동
high -= 1
# 📌 low == high 이유: 엇갈리는 순간을 만들기 위해,
# 📌 quick_nums[low] == pivot 허용한 이유: pivot과 동일한 것들이, low, high로 의미없이 서로 교환되는 것을 막기 위해
# 2-3. 세부 while 문이 멈추고, 엇갈려 있다면 바깥 while을 빠져 나오고
if low > high:
break
# 엇갈려 있지 않으면 low와 high를 서로 교환해준다. -> 엇갈린 상황에서 low high를 교환해주면 정렬을 한 것들이 망가진다.(10 22 -> 22 10)
else:
quick_nums[low], quick_nums[high] = quick_nums[high], quick_nums[low]
# 2-4. 엇갈린 상태에서 pivot 값과 high 값을 교환(엇갈려 있으므로 high가 low보다 작다 -> high를 pivot과 바꾼다.)
quick_nums[start], quick_nums[high] = quick_nums[high], quick_nums[start]
# 2-5. 새로운 pivot의 위치를 반환 -> 이 숫자의 위치는 영구적으로 고정
return high
# 1. start와 end 사이를 정렬하기
def quick_sort(start, end):
# 1-1. start와 end가 엇갈리지 않을 때 sort 가능
# start와 end가 같아도 되지만 어차피 partition 후에 pivot = start = end이 되므로 그 아래 코드에서 걸러진다.)
if start < end:
pivot = partition(start, end)
quick_sort(start, pivot-1) # pivot보다 작은 것들을 sort,
quick_sort(pivot+1, end) # pivot보다 큰 것들을 sort
quick_nums = [0, 55, 22, 33, 2, 1, 10, 26, 42]
quick_sort(0, len(quick_nums)-1)
print(quick_nums) # [0, 1, 2, 10, 22, 26, 33, 42, 55]
각 순환 호출 전 단계에서의 비교 연산(평균): n
순환 호출 : logn
- T(n) = n + 2T(n/2)
- n : 재귀 전에 pivot 선정해서 왼쪽 오른쪽 나누기
- 2T(n/2) : 왼쪽, 오른쪽 두 부분 리스트에서 quick sort 호출하기
- T(n) = O(nlogn)
이미 정렬된 형태에서 최악의 시간 복잡도를 갖는다.
정렬을 진행하면, pivot이 한 쪽 끝으로 쏠리게 되서 두 부분 리스트를 나눌 수가 없다.
순환 호출 단계 * 호출 전 단계에서의 비교 연산 : O(n^2)
결국 퀵 정렬의 최악의 시간복잡도는 버블 정렬과 비슷한 원리다.
분할 정복 알고리즘의 한 종류
pivot을 선정하는 방법에 따라 속도가 달라진다.
이미 정렬되어 있거나, 역순으로 정렬되어 있는 배열에서는 O(n^2)로 매우 느리다.
배열 안에서 교환이 일어나므로 merge sort와는 달리 추가 메모리를 필요로 하지 않는다.
시간 복잡도가 O(nlogn)을 가지는 다른 정렬 알고리즘(merge sort, heat sort)와 비교했을 때, 가장 빠르다.