[k-mooc/자료구조] 09. 우선 순위 큐(Priority Queue)와 힙(Heap)

2025. 8. 7. 17:08·CS/자료구조

 

 

우선 순위 큐(Priority Queue)

가장 우선 순위가 높은 원소가 가장 먼저 제거되는 큐

연산과 성능

연산 설명 시간 복잡도
추가(Push) 큐에 새로운 원소를 삽입하는 연산으로, 새로운 원소의 위치는 그 중요도에 따라서 결정된다. O(n)
제거(Pop)  큐에서 가장 우선 순위가 높은 원소(가장 앞에 있는 원소)를 삭제한다. O(n)
탑(Top) 큐에서 가장 우선 순위가 높은 원소를 조회한다. O(1)

 

탑의 성능은 좋지만…
추가와 제거의 성능을 향상시킬 수 있는 방법은?

 

 

힙(Heap)

이진 트리를 기반으로 구현된 우선 순위 큐 — 완전 이진 트리

  • 최대 힙(Max heap): 모든 노드의 값 > 자식 노드의 값 — 큰 숫자가 우선
  • 최소 힙(Min heap): 모든 노드의 값 < 자식 노드의 값 — 작은 숫자가 우선
힙은 (1)포인터나 (2)배열을 기반으로 구현할 수 있는데, 배열 구현으로 설명한다.
배열을 기반으로 힙을 구현하기 위해서는 정렬된 배열의 첫 번째 원소부터 번호를 1부터 부여한다.

 

배열로 구현한 힙에서 k번째 노드의 부모 노드나 자식 노드에 접근하는 방법:

k번째 노드의 부모 노드 k/2
k번째 노드의 왼쪽 자식 노드 2*k
k번째 노드의 오른쪽 자식 노드 2*k + 1

 

자료구조

int *heap;
int n;       //힙의 전체 크기 - 메모리 크기 할당 
int cnt;     //저장된 원소 개수 

cnt = 0;    
n = 1000;
heap = (int*)calloc(n, sizeof(int));

 

📍 heapify(k)

k번째 노드로부터 힙을 재구성하는 함수

 

1. 하향식 재구성(Topdown heapify): 리프 노드까지 힙을 재구성 ⇒ max heap

/* max-heapify: 하향식 재구성 */
void heapify_topdown(int idx) {
	//리프 노드에 도착했다면 종료(왼쪽 자식 노드 주소가 카운트(전체 노드 개수)보다 크다면 존재하지 않으므로 리프 노드 - 완전 이진 트리이므로 왼쪽부터 노드를 채우니까)
	if(2*idx > cnt) return;
	
	//왼쪽 자식 노드 주소가 카운트와 같다면 이 자식 노드가 트리의 마지막 노드 -> 오른쪽 자식 노드는 없음
	//그러니까 현재 노드와 왼쪽 자식 노드만 비교: 자식이 더 크다면 자리 교환
	if(2*idx == cnt) {
		if(heap[idx] < heap[2*idx]) swap(&heap[idx], &heap[2*idx]);
		return;
	}
	
	//오른쪽 자식 노드와 비교하고 오른쪽 자식 노드로 내려가서 다시 히피파이
	if(heap[2*idx] > heap[2*idx+1] && heap[2*idx] > heap[idx]) {
		swap(&heap[idx], &heap[2*idx]);
		heapify_topdown(2*idx);
	}

	//왼쪽 자식 노드와 비교하고 왼쪽 자식 노드로 내려가서 다시 히피파이
	else if(heap[2*idx+1] > heap[2*idx] && heap[2*idx+1] > heap[idx]) {
		swap(&heap[idx], &heap[2*idx+1]);
		heapify_topdown(2*idx+1);
	}
}

 

 

2. 상향식 재구성(Bottomup heapify): 루트 노드까지 힙을 재구성 ⇒ min heap

/* min-heapify: 상향식 재구성 */
void heapify_bottomup(int idx) {
	//상향식이므로 루트 노드에 도착하면 종료 
	if(idx == 1) return;
	
	//상향식이므로 자식 노드의 좌우 상관없이 자식노드는 부모노드와 비교하면 된다.
	if(heap[idx/2] < heap[idx]) {
		swap(&heap[idx/2], &heap[idx]);
		heapify_bottomup(idx/2);
	}
}

 

n개의 노드가 있을 때 heapify(k)의 시간 복잡도:

완전 이진 탐색 트리의 depth는 O(logn)이다. 그런데 최악의 경우 검색은 리프 노드 또는 루트 노드에서 끝나게 되므로 시간 복잡도는 O(logn)이 된다.

 

 

 

힙의 연산: 추가와 제거

추가(Push)

최대 힙에 새로운 원소를 추가한다고 가정한다. 이 때, 최대 힙은 (1)완전 이진 트리이면서 (2)항상 부모 노드의 값이 자식 노드의 값보다 커야 한다.

“힙에 새로운 원소를 추가한다”라는 말의 의미는 “새로운 노드를 생성한다”는 연산인데, (1)완전 이진 트리 조건 때문에 새롭게 생성된 노드는 항상 맨 마지막 위치(리프 노드)에만 삽입될 수 있다. 그러나 이 위치에 노드가 저장되었을 때 조건 (2)를 만족시킨다는 보장이 없기 때문에 `heapify()` 를 이용해 새로운 원소가 삽입된 힙을 재구성해야한다.

구현

void insert(int ndata) {
	//cnt 증가 + 힙 마지막 위치에 새로운 원소를 저장 O(1)
	cnt++;
	heap[cnt] = ndata;
	
	//힙 재구성 o(logn)
	heapify_bottomup(cnt);
}

힙의 추가 연산 시간 복잡도는 O(logn)이 된다.

 

 

제거(Pop)

최대 힙에서 원소를 제거한다고 가정한다. 이 때, 최대 힙은 (1)완전 이진 트리이면서 (2)항상 부모 노드의 값이 자식 노드의 값보다 커야 한다.

“힙에서 원소를 제거한다”라는 말의 의미는 “루트 노드에 저장된 값을 제거한다”는 연산인데, 조건 (1) 때문에 “제거”할 수 있는 노드는 마지막 노드(리프 노드) 뿐이다. 그렇기 때문에 힙의 마지막 노드에 저장된 값을 루트 노드로 옮기고 마지막 노드를 제거한 뒤에, 루트 노드로부터 하향식 힙 재구성을 수행해야 한다.

구현

int remove() {
	//마지막 노드의 값을 루트 노드에 옮기고, 카운트를 감소 O(1)
	int temp = heap[1];
	heap[1] = heap[cnt];
	cnt--;
	
	//루트 노드로부터 힙을 하향식 재구성 O(logn)
	heapify_topdown(1);
	return temp;
}

힙의 제거 연산 시간 복잡도는 O(logn)이 된다.

 

'CS > 자료구조' 카테고리의 다른 글

[k-mooc/자료구조] 11. 그래프  (2) 2025.08.11
[k-mooc/자료구조] 10. 탐색  (6) 2025.08.09
[k-mooc/자료구조] 08. 이진 탐색 트리(Binary Search Tree)  (1) 2025.08.06
[k-mooc/자료구조] 01-07 복습  (4) 2025.08.05
[k-mooc/자료구조] 07. 트리(Tree)  (3) 2025.07.30
'CS/자료구조' 카테고리의 다른 글
  • [k-mooc/자료구조] 11. 그래프
  • [k-mooc/자료구조] 10. 탐색
  • [k-mooc/자료구조] 08. 이진 탐색 트리(Binary Search Tree)
  • [k-mooc/자료구조] 01-07 복습
마동탁
마동탁
보고또보고
  • 마동탁
    원데이투데이
    마동탁
  • 전체
    오늘
    어제
    • 분류 전체보기 (34)
      • 개발 (2)
        • Elasticsearch (2)
      • CS (21)
        • 자료구조 (15)
        • 네트워크 (6)
      • 코딩테스트 (11)
        • 문제풀이 (9)
        • 알고리즘 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
마동탁
[k-mooc/자료구조] 09. 우선 순위 큐(Priority Queue)와 힙(Heap)
상단으로

티스토리툴바