- 완전 이진 트리(빈틈 없이 왼쪽부터 채워짐)를 배열로 표현
- MaxHeap: 부모 ≥ 자식 (루트가 항상 최댓값)
- MinHeap: 부모 ≤ 자식 (루트가 항상 최솟값)
- 중복 값 허용 (Set과 다름) — 부모 = 자식이어도 규칙 위반 아님
- 정렬된 상태가 아님 — 루트(최댓값/최솟값)만 O(1)로 보장
완전 이진 트리는 빈틈이 없어 배열에 순서대로 담으면 인덱스만으로 부모·자식을 계산할 수 있음 (포인터 불필요)
| 관계 | 인덱스 |
|---|---|
| 부모 | (i - 1) / 2 |
| 왼쪽 자식 | 2i + 1 |
| 오른쪽 자식 | 2i + 2 |
| 연산 | 시간 복잡도 |
|---|---|
| peek (루트 확인) | O(1) |
| insert (삽입) | O(log n) |
| poll (루트 제거) | O(log n) |
| resize (배열 확장) | O(n) (가끔 발생, 분할상환 O(1)) |
- insert / poll이 O(log n)인 이유: 트리 높이만큼만 이동하기 때문
-
siftUp (상향 조정) — insert 시
- 새 값을 맨 끝(leaf)에 넣고, 부모보다 크면(MaxHeap) 위로 교환하며 올라감
- 부모가 더 크면 즉시 멈춤 (위쪽은 원래 올바른 힙이므로 볼 필요 없음)
-
siftDown (하향 조정) — poll 시
- 마지막 원소를 루트로 옮기고, 두 자식 중 더 큰 쪽과 비교하며 내려감
- 자기 자신이 가장 크면 멈춤
- 시작: 원소 0~1개는 비교 대상이 없어 자동으로 올바른 힙
- insert: 올바른 힙에 leaf 하나만 추가 → 깨질 수 있는 곳은 한 경로뿐 → siftUp으로 복구
- poll: 올바른 힙에서 루트만 제거 → siftDown으로 복구
- 매 연산 후 규칙이 유지되므로 결과는 항상 올바른 힙
그래서 전체 n개를 검사할 필요 없이 한 줄기 경로(log n)만 고치면 충분함
- 배열은 크기가 고정 → "늘린다"는 건 더 큰 배열을 새로 만들어 값을 복사하는 것
- 꽉 차면 2배로 확장 (1씩 늘리면 매번 복사라 느림 → 2배면 평균 O(1))
- 힙이 아닌 배열을 처음부터 힙으로 만드는 과정
- 배열을 그대로 복사한 뒤, 마지막 부모 노드부터 거꾸로 siftDown (
parent(size-1)→ 0) - 뒤(아래)에서부터 하는 이유: 아래 서브트리가 먼저 힙이 되어야 위쪽 siftDown이 성립하기 때문
- 리프(전체의 약 절반)는 내려갈 데가 없어 건너뜀 → siftDown 대상은 약 n/2개
시간복잡도: O(n) (O(n log n) 아님)
- 아래층 노드는 많지만 이동 거리가 짧고, 위층 노드는 이동 거리가 길지만 개수가 적음 → 상쇄되어 O(n)
- 반면 하나씩 insert 반복은 O(n log n) (많은 리프가 먼 거리를 siftUp하기 때문)
| 방식 | 방향 | 멀리 이동하는 노드 | 시간복잡도 |
|---|---|---|---|
| insert 반복 | siftUp (위로) | 리프(많음) | O(n log n) |
| build heap | siftDown (아래로) | 루트 근처(적음) | O(n) |
- 구조(배열 표현, 인덱스 계산, siftUp/siftDown)는 완전히 동일
- 비교 부호만 뒤집으면 됨: MaxHeap
compareTo(...) > 0↔ MinHeapcompareTo(...) < 0 - 라이브러리는 부호를 고치는 대신 Comparator만 교체 (
Comparator.reverseOrder())
- peek: 꺼내지 않고 최댓값만 O(1)로 확인 — "꺼낼지 말지 먼저 판단"할 때 유용
- 큐(FIFO)와 달리 우선순위가 높은 데이터가 먼저 나가는 자료구조 → 힙으로 구현하는 게 일반적
- 힙 기반이면 삽입·삭제 모두 O(log n) (배열·연결 리스트는 한쪽이 O(n))
- 우리가 만든 MaxHeap이 곧 우선순위 큐 구현:
insert(추가) /poll(최댓값 삭제·반환) /peek(최댓값 확인) - "값이 큰 것이 우선"인 규칙이며, 우선순위 기준을 바꾸려면 Comparator 적용
- 중복 값은 정상 저장되고, poll 시 개수만큼 그대로 나옴
- 단, 같은 값끼리 "먼저 넣은 게 먼저 나온다"는 보장(안정성)은 없음
- 정렬 조건: MaxHeap을 poll로 모두 꺼내면 내림차순, MinHeap이면 오름차순인가
- resize 조건: 초기 capacity를 넘겨 넣어도 (예: 기본 10에 14개 삽입) 값 손실 없이 동작하는가