정렬
퀵 정렬
기준 하나로 좌우를 가르기
카드 일곱 장이 있습니다. 맨 앞 카드를 기준으로 삼고 나머지를 한 장씩 보며 왼쪽과 오른쪽 묶음으로 가릅니다.
맨 앞 카드 6을 기준으로 잡습니다. 기준보다 작은 카드가 왼쪽 묶음에 몇 장 모일지 먼저 맞혀 보세요
6
3
8
2
9
1
5
기준
왼쪽 0장 · 오른쪽 0장
기준 카드를 두고 왼쪽 0장, 오른쪽 0장으로 갈렸습니다.
- 기준 카드 — 왼쪽 묶음의 장수가 곧 제자리 칸 번호다. 다시 움직이지 않는다
- 두 묶음 — 안쪽 순서는 뒤죽박죽이어도 된다. 묶음마다 같은 일을 되풀이한다
기준을 잡고 좌우로 가르기를 되풀이하는 방법 = 퀵 정렬. 이때 기준 카드를 피벗(pivot)이라고 부릅니다.
버블 정렬 · 1000개499,500번
퀵 정렬(평균) · 1000개9,966번
기준이 가운데쯤에 놓여야 빠르다
- 반씩 갈리면 — 층이 log N 개다. 층마다 카드를 한 번씩 보니 N log N
- 한쪽으로 쏠리면 — 한 번에 기준 카드 하나만 정해진다. 층이 N 개가 되어 비교가 N(N−1)/2, 곧 N²
- 정렬된 카드 — 위의 「이미 정렬된 카드로 해 보기」에서 왼쪽이 0장이었다. 일곱 장이면 비교 21번이다(손으로 해 본 카드는 13번). 10만 장이면 4,999,950,000번으로 8분 걸린다
코드는 가르고 양쪽에 되풀이하는 것이 전부다
① 에서 한 순서 그대로 기준을 맨 앞 칸에 두고 앞에서부터 훑습니다. 다음을 눌러 첫 분할을 한 줄씩 보세요. 안쪽 분할은 기준을 놓은 줄 하나로 묶어 보여 줍니다.
60
31
82
23
94
15
56
lohi
다음을 눌러 한 줄씩 넘겨 보세요
lo = 0 hi = 6
quick_sort.py
- 1def quick_sort(arr, lo, hi):
- 2 if lo >= hi:
- 3 return
- 4 pivot = arr[lo]
- 5 i = lo
- 6 for j in range(lo + 1, hi + 1):
- 7 if arr[j] < pivot:
- 8 i += 1
- 9 arr[i], arr[j] = arr[j], arr[i]
- 10 arr[lo], arr[i] = arr[i], arr[lo]
- 11 quick_sort(arr, lo, i - 1)
- 12 quick_sort(arr, i + 1, hi)
0 / 25
lo·hi— 지금 가르는 구간. 처음엔 0번 칸부터 6번 칸까지다i— 작은 카드 묶음의 끝 칸 번호j— 지금 기준과 견주는 칸.lo + 1부터hi까지 간다
이미 정렬된 카드 1 2 3 4 5 6 7 을 맨 앞 카드를 기준으로 첫 분할하면 왼쪽 묶음에 몇 장이 모일까요?
퀵 정렬에서 틀리는 자리
- 정렬된 큰 입력 — 5000장만 돼도
RecursionError: maximum recursion depth exceeded가 난다. 층이 N 개가 되는데 파이썬의 기본 재귀 한도는 1000이다 - 같은 값이 많으면 — 5000장이 모두 같아도 같은 에러가 난다. 같은 카드는
<가 거짓이라 전부 오른쪽으로 가서 쏠린다 - 기준을 가운데나 무작위로 — 가운데 카드를 맨 앞으로 옮겨 기준으로 삼으면 정렬된 5000장도 끝까지 간다
- 같은 값의 순서가 뒤집힌다 —
2a 1b 1c를 정렬하면 1c 1b 2a 가 된다. 안정 정렬이 아니다 - 리스트 컴프리헨션 버전 — 짧게 쓸 수 있지만 가를 때마다 새 리스트를 만들어 메모리를 더 쓴다
코딩테스트에서 이 말이 보이면
평균적으로 빠른 정렬을 직접 짜야 한다k 번째로 작은 수기준보다 작다/크다로 가르기
실전에서 정렬이 필요하면 sorted() 한 줄이면 됩니다.
다음 장에서는 여러 기준으로 정렬하는 Python sort 와 key 를 봅니다.