정렬
병합 정렬
반으로 쪼갰다 붙이기
이미 정렬된 두 묶음을 하나의 줄로 합쳐 보세요. 카드는 한 장씩 새 줄로 내려갑니다.
카드 여섯 장이 두 묶음으로 서 있습니다. 두 묶음 모두 이미 작은 수부터 정렬돼 있습니다. 하나의 줄로 합치려면 카드를 몇 번 내려놓아야 할지 먼저 맞혀 보세요
왼쪽 묶음
오른쪽 묶음
새 줄 — 왼쪽이 가장 작게
카드 여섯 장을 0번 내려놓아 합쳤습니다. 맨 앞 두 장만 견주면 됐습니다. 견준 횟수는 0번입니다.
2 5 9 + 1 4 7 → 1 2 4 5 7 9맨 앞 두 장만 견주며 한 장씩 내려놓은 결과입니다.
이미 정렬된 두 묶음을 하나로 합치는 방법 = 병합(합치기)
정렬이 안 된 카드 여덟 장도 이걸로 정렬할 수 있습니다. 반으로 쪼개고 또 쪼개면 한 장씩이 됩니다. 한 장은 이미 정렬된 묶음이라 거꾸로 합쳐 올라가면 끝납니다.
반으로 쪼갠 뒤 합치며 올라가는 방법 = 병합 정렬
쪼개기는 한 장에서 멈춘다
- 두 묶음이 각각 정렬돼 있다 — 그래서 맨 앞 두 장만 견주면 됐다. ① 에서 뒤쪽 카드를 누를 수 없던 이유다
- 한 장짜리 묶음은 이미 정렬돼 있다 — 쪼개기를 여기서 멈춘다. 재귀가 멈추는 자리와 같다
- 층마다 — 합치며 카드를 한 번씩 내려놓는다. 반으로 쪼갠 횟수가 층 수다. 8장은 3층, 1000장은 10층이다
반으로 몇 번 쪼개면 1이 되는지가 log N 입니다. 층 수 × 카드 수가 곧 N log N 입니다.
코드는 쪼개는 함수와 합치는 함수 둘이다
다음을 눌러 한 줄씩 넘기며 위 줄에서 묶음이 합쳐지고 아래 줄이 채워지는 걸 보세요. 재귀로 부르는 줄(5·6번)과 i += 1 같은 짧은 줄은 건너뜁니다. 마지막 합치기가 ① 에서 한 일입니다.
원래 줄 — 켜진 칸이 지금 합치는 구간
합쳐서 만드는 줄
다음을 눌러 한 줄씩 넘겨 보세요
n = 6 mid = 3
- 1def merge_sort(arr):
- 2 if len(arr) <= 1:
- 3 return arr
- 4 mid = len(arr) // 2
- 5 left = merge_sort(arr[:mid])
- 6 right = merge_sort(arr[mid:])
- 7 return merge(left, right)
- 8
- 9def merge(left, right):
- 10 result = []
- 11 i = j = 0
- 12 while i < len(left) and j < len(right):
- 13 if left[i] <= right[j]:
- 14 result.append(left[i])
- 15 i += 1
- 16 else:
- 17 result.append(right[j])
- 18 j += 1
- 19 result += left[i:] + right[j:]
- 20 return result
merge_sort(arr)— 반으로 나눠 각각 정렬해 돌려받고merge로 합친다. 자기를 두 번 부르는 재귀다left[i] <= right[j]— 두 묶음의 맨 앞 두 장 중 작은 쪽을 고른다. ① 에서 한 일이다result— 합쳐서 만드는 줄. 위 그림의 아래 줄이다
이미 작은 수부터 서 있는 카드 8장을 병합 정렬하면 카드를 모두 몇 번 내려놓을까요?
병합 정렬에서 틀리는 자리
<=대신<— 같은 값에서 오른쪽 카드가 먼저 나와 원래 순서가 뒤집힌다.2a 1c 2b를 정렬하면<=는1c 2a 2b,<는1c 2b 2a순서가 된다result += left[i:] + right[j:]를 빼먹으면 — 한쪽 묶음이 먼저 비었을 때 남은 카드가 사라진다. 여섯 장5 2 9 1 7 4를 정렬하면 1장만 남는다arr[:mid]는 복사다 — 카드를 새 리스트로 옮기니 메모리를 카드 수만큼 더 쓴다. 쪼개는 깊이는 log N 이라 100만 장도 20층이다. RecursionError 는 걱정하지 않아도 된다
문제에서 이 말이 보이면
실전에서 정렬이 필요하면 sorted() 한 줄이면 됩니다. 파이썬의 sorted() 는 병합 정렬과 삽입 정렬을 섞은 방법을 C 로 구현해 둬서 직접 만든 이 코드보다 훨씬 빠릅니다. Python 3.14 에서 10만 장을 재 보니 이 코드는 0.13초, sorted() 는 0.008초였습니다(컴퓨터마다 다르니 비율만 보세요).
다음 장에서는 기준 카드 하나로 좌우를 가르는 퀵 정렬을 봅니다.