분할 정복(divide and conquer)방식을 사용하여, pivot이라는 하나의 요소를 중심으로 pivot보다 작은 요소들과 pivot보다 큰 요소들의 두 리스트로 분할하는 것을 반복하여 전체 리스트를 정렬한다.
"""
pivot을 중심으로 partition하고 두 개의 부분 리스트들에 대하여 quickSort 실행
Args:
num_list: 정렬하고자 하는 전체 리스트
left: 현재 리스트의 가장 왼쪽 index
right: 현재 리스트의 가장 오른쪽 index
"""
def quickSort(num_list, left, right):
if left >= right:
return
pivot = partition(num_list, left, right)
quickSort(num_list, left, pivot - 1)
quickSort(num_list, pivot + 1, right)
"""
pivot의 위치를 확정하고, pivot보다 작은 것들은 왼쪽에, 큰 것들은 오른쪽에 위치하도록 하는 함수
Args:
num_list: 정렬하고자 하는 전체 리스트
left: 현재 리스트의 가장 왼쪽 index
right: 현재 리스트의 가장 오른쪽 index
Return:
pivot의 index
"""
def partition(num_list, left, right):
p = num_list[left]
left_index, right_index = left, right
left_index += 1
while True:
while num_list[right_index] > p and right_index >= left:
right_index -= 1
while num_list[left_index] < p and left_index <= right:
left_index += 1
if left_index < right_index:
# swap num_list[left_index] and num_list[right_index]
num_list[left_index], num_list[right_index] = num_list[right_index], num_list[left_index]
else:
# swap num_list[right_index] and pivot(=num_list[left]), return pivot's final index
num_list[right_index], num_list[left] = num_list[left], num_list[right_index]
return right_index
# 정렬하고자 하는 임의의 리스트
num_list = [4, 6, 1, 7, 5, 3, 2]
quickSort(num_list, 0, len(num_list) - 1)
print(num_list) ### 출력: [1, 2, 3, 4, 5, 6, 7]
만약, num_list
의 left가 right보다 크거나 같다면(정렬하고자 하는 num_list
의 길이가 1이하라면), 더 이상 정렬할 필요가 없기 때문에 그냥 return합니다.
그렇지 않다면, partition
함수를 통해 pivot을 선정하고, pivot을 중심으로 나뉜 두 개의 리스트를 quickSort
를 통해 정렬합니다.
1) num_list[left, pivot-1]
2) num_list[pivot+1, right]
이 때 pivot의 위치는 partition
을 통해 이미 정해졌기 때문에, 위 두 개의 리스트에 포함되지 않습니다.
가장 왼쪽의 요소를 pivot으로 임의로 지정합니다. -> num_list[left]
🔔이 때, 가장 왼쪽의 요소를 pivot으로 지정했다고 pivot의 위치가 가장 왼쪽인 것은 아닙니다!! pivot의 위치는 partition
함수의 마지막 부분에서 확정됩니다.
left_index
와 right_index
두 개의 변수를 통해 pivot보다 작은 것들은 pivot의 왼쪽으로, pivot보다 큰 것들은 pivot의 오른쪽으로 이동시킵니다.
pivot 다음인 left_index + 1
부터 시작하여 pivot보다 큰 값이 나올 때까지 오른쪽으로 이동합니다.
right_index
부터 시작하여 pivot보다 작은 값이 나올 때까지 왼쪽으로 이동합니다.
left_index
와 right_index
의 이동이 끝난 시점에서,
1) left_index
< right_index
라면 left_index
에는 pivot보다 큰 값이, right_index
에는 pivot보다 작은 값이 있는 것이므로 두 요소의 위치를 바꿔주면 됩니다. 그리고 아직 리스트의 모든 요소들을 보지 않았기 때문에 계속해서 이동합니다.
2) left_index
>= right_index
라면, 이미 모든 리스트의 모든 요소가 pivot보다 작은 것과 큰 것으로 잘 구분되어 있는 것이기 때문에 pivot의 위치를 두 부분 리스트를 구분하는 위치로 바꿔줍니다.
👇 처음 num_list
에 partition
함수를 적용하는 예시
# 파이썬의 장점을 살린 퀵 정렬
def quick_sort(array):
# 리스트가 하나 이하의 원소를 가지면 종료
if len(array) <= 1: return array
pivot, tail = array[0], array[1:]
leftSide = [x for x in tail if x <= pivot]
rightSide = [x for x in tail if x > pivot]
return quick_sort(leftSide) + [pivot] + quick_sort(rightSide)
사실, pivot을 선택하고 pivot보다 작은 요소들은 pivot의 왼쪽에, 큰 요소들은 오른쪽에 위치하도록 함으로써 pivot의 위치를 확정시키는 방식으로 정렬을 하기 때문에, itertools
의 filter
함수나, 위와 같은 방식으로 작은 요소들과 큰 요소들을 거르는 방식으로 정렬을 할 수도 있습니다.
한 눈에 보기에 굉장히 직관적이고 이해가 쉽지만, 시간이 더 오래 걸린다는 단점이 존재합니다.