자료구조
힙(우선순위 큐)
가장 급한 것부터 꺼내기
응급실 접수대입니다. 환자 카드의 숫자가 작을수록 급합니다. 가장 급한 환자가 맨 위 자리에 앉아야 합니다.
숫자 1 인 새 환자가 맨 아래 빈자리에 앉았습니다.
새 환자는 몇 번 자리를 바꿔 올라갈까요? 먼저 골라 보세요.
올라간 횟수 0
내 예상 번 · 실제 번 → 달랐어요
6번 자리 → 2번 자리 → 0번 자리새 환자가 올라간 길입니다. 부모보다 급할 때만 한 칸씩 올라갑니다.
- 맨 위 환자 — 언제나 가장 급한 환자(가장 작은 숫자)입니다
- 위 칸과 아래 칸 — 위 칸은 바로 아래 두 칸보다 늘 같거나 급합니다
- 올라간 횟수 — 많아야 나무 높이만큼입니다. 환자가 100,000명이어도 16번입니다
맨 위가 늘 가장 작은 나무 = 힙. 파이썬에서는 heapq 모듈이 이 일을 해 줍니다.
아래 한 줄도 같은 나무입니다. 칸 번호로 읽으면 부모 = (자식 번호 − 1) // 2 입니다.
올 때마다 전부 다시 줄 세우기1,660,964번
힙에 앉히고 올리기 (많아야)16번
왜 맨 위만 믿을 수 있을까
- 규칙은 하나 — 위 칸은 바로 아래 두 칸보다 늘 같거나 급하다
- 줄 세운 게 아니다 — 새 환자를 올린 뒤 배열은
[1, 5, 2, 9, 6, 8, 3]입니다. 앞에서부터 읽으면 작은 순서가 아닙니다. 믿을 수 있는 건 맨 앞 칸뿐입니다 - 부모가 더 급하면 멈춘다 — 새 환자가 4였다면 부모 3이 더 급해서 첫 비교에서 바로 멈춥니다
1을 부르면맨 끝 환자 3이 빈 맨 위에 앉고, 아래 두 자식 중 더 급한 쪽과 바꾸며 내려갑니다. 결과는 [2, 5, 3, 9, 6, 8] 입니다.
코드는 부모와 견주며 올라가는 반복문이다
다음을 눌러 한 줄씩 넘기세요. 새 환자가 앉은 자리에서 부모와 견주고, 급하면 바꿔 올라갑니다.
같은 환자들을 한 줄로 (칸 번호 = 자리 번호)
20
51
32
93
64
85
16
다음을 눌러 한 줄씩 넘겨 보세요
x = 1
push.py
- 1def push(heap, x):
- 2 heap.append(x)
- 3 i = len(heap) - 1
- 4 while i > 0:
- 5 p = (i - 1) // 2
- 6 if heap[p] <= heap[i]:
- 7 break
- 8 heap[p], heap[i] = heap[i], heap[p]
- 9 i = p
- 10 return heap
0 / 14
p = (i - 1) // 2— 부모 자리 번호if heap[p] <= heap[i]: break— 부모가 같거나 더 급하면 멈춘다heap[p], heap[i] = heap[i], heap[p]— 부모와 자리를 바꾼다i = p— 올라간 자리에서 다시 견준다
파이썬에서는 이 반복문을 직접 쓰지 않습니다. heapq.heappush(heap, x) 한 줄이 같은 일을 합니다.
새 환자가 4 였다면 맨 아래 빈자리에서 몇 번 올라갈까요?
heapq 에서 헷갈리는 자리
- 가장 작은 것이 먼저다 —
heapq는 최소 힙이다. 큰 것부터 꺼내려면-x로 넣고 꺼낸 뒤 다시-를 붙인다 - 리스트는 정렬이 아니다 — 5, 3, 8, 1을 차례로 넣으면
[1, 3, 8, 5]다. 맨 앞heap[0]만 가장 작다 - 급한 정도와 이름을 같이 —
heappush(h, (2, "복통"))처럼 튜플로 넣는다. 앞 숫자가 같으면 뒤 값끼리 견주는데, 견줄 수 없는 값이면TypeError가 난다 - 한 번에 만들 때 — 이미 있는 리스트는
heapify(arr)한 번으로 N 에 비례해 만든다. 하나씩 넣으면 N log N 이다
문제에서 이 말이 보이면
매번 가장 작은(큰) 것K번째로 큰 수다익스트라 · 작업 스케줄
다음 장에서는 옆 칸끼리 바꿔 가며 줄을 세우는 버블 정렬을 봅니다.