Heap & Priority Queue

Heap 구조체

  • 우선순위 큐를 위해 만들어진 자료구조
  • 여러 값들 중 최솟값이나 최댓값을 빠르게 탐색하기 위한 자료구조
  • 반정렬 상태를 유지
    • 작은 값은 하위, 큰 값은 상위에 있는 정도
  • 이진 트리와는 달리, 중복된 값을 허용
  • 이진 트리에서 특정한 조건을 갖춘 구조체
    1. 트리의 높이 h가 h-1 까지 완전 이진 트리
    2. 모든 leaf 노드의 깊이는 h 이거나 h-1
    3. 깊이 h의 모든 leaf 노드들의 경로는 h-1의 모든 leaf 노드보다 왼편에 있다
      • 깊은 쪽의 leaf 노드가 항상 왼편에 온다

Heap 종류

post_thumbnail

  • 최대 힙
    • 부모 노드의 키 값이 자식 노드의 키 값보다 크거나 같은 완전 이진트리
    • 부모노드 키 >= 자식노드 키
  • 최소 힙
    • 자식 노드의 키 값이 부모 노드의 키 값보다 크거나 같은 완전 이진트리
    • 자식노드 키 >= 부모노드 키

Heap 구현

post_thumbnail

  • 기본적으로 자료구조는 배열(Array)
  • 첫 번째 인덱스(index 0)는 사용하지 않음
  • 아래 표는 부모 노드와 자식 노드의 관계
인덱스 계산
왼쪽 자식의 인덱스 = 부모 인덱스 * 2
오른쪽 자식의 인덱스 = 부모 인덱스 * 2 + 1
부모의 인덱스 = 자식 인덱스 / 2
// 힙 안에 저장된 요소의 개수
#define MAX_ELEMENT 200

// 원소
typedef struct{
  int key;
} element;

// 타입 선언
typedef struct{
  element heap[MAX_ELEMENT];
  int heap_size;
} HeapType;

// 힙의 생성
HeapType heap1;

Heap 삽입

  • 힙에 새로운 요소가 들어오면, 일단 새로운 노드를 힙의 마지막 노드에 이어서 삽입한다.
  • 새로운 노드를 부모 노드들과 교환해서 힙의 성질을 만족시킨다.

post_thumbnail

/* 현재 요소의 개수가 heap_size인 힙 h에 item을 삽입한다. */
// 최대 힙(max heap) 삽입 함수
void insert_max_heap(HeapType *h, element item){

  int i; // i번째 인덱스에 새 노드 삽입
  i = ++(h->heap_size); // 힙 크기를 하나 증가

  /* 트리를 거슬러 올라가면서 부모 노드와 비교하는 과정 */
  // i가 루트 노트(index: 1)이 아니고, 삽입할 item의 값이 i의 부모 노드(index: i/2)보다 크면
  while((i != 1) && (item.key > h->heap[i/2].key)){
    // i번째 노드와 부모 노드를 교환환다.
    h->heap[i] = h->heap[i/2];
    // 한 레벨 위로 올라단다.
    i /= 2;
  }
  h->heap[i] = item; // 새로운 노드를 삽입
}

Heap 삭제

  • 최대 힙에서 삭제는 최댓값, 즉 루트 노드의 삭제
  • 삭제된 루트 노드에 배열 상 마지막 노드를 가져온다
  • 힙을 교환하며 재구성 한다

post_thumbnail

// 최대 힙(max heap) 삭제 함수
element delete_max_heap(HeapType *h){
  int parent, child;
  element item, temp;

  item = h->heap[1]; // 루트 노드 값을 반환하기 위해 item에 할당
  temp = h->heap[(h->heap_size)--]; // 마지막 노드를 temp에 할당하고 힙 크기를 하나 감소
  parent = 1; // 첫번째 부모
  child = 2; // 첫번째 자식

  while(child <= h->heap_size){

    // 현재 노드의 자식 노드 중 더 큰 자식 노드를 찾는다. (루트 노드의 왼쪽 자식 노드(index: 2)부터 비교 시작)
    if( (child < h->heap_size) && ((h->heap[child].key) < h->heap[child+1].key) ){

      // 오른쪽 child가 더 크면 옆으로 이동
      child++;
    }

    // 더 큰 자식 노드보다 마지막 노드가 크면, while문 중지
    if( temp.key >= h->heap[child].key ){
      break;
    }

    // 더 큰 자식 노드보다 마지막 노드가 작으면, 부모 노드와 더 큰 자식 노드를 교환
    h->heap[parent] = h->heap[child];

    // 한 단계 아래로 이동
    parent = child;

    // 업데이트 된 부모의 왼쪽 인덱스
    child *= 2; 
  }

  // 마지막 노드를 재구성한 위치(넣기로 한 위치)에 삽입
  h->heap[parent] = temp;

  // 최댓값(루트 노드 값)을 반환
  return item;
}

Priority Queue 우선순위 큐

  • 일반적인 큐는 First In First Out
  • 우선순위 큐는 삽입된 순서와 무관하게 우선순위가 높은 데이터나 먼저 Out
  • Heap 자료 구조를 통해 구현 가능
  • Priority Queue는 배열 안의 원소을 모두 정렬하는 것이 아니라, 최대/최소값이 head에 오도록하는 컨테이너다

왜 Priority Queue는 Array나 LinkedList로 구현하지 않는가?

  1. 배열로 구현
    • 우선 순위가 가장 높은 맨 앞의 인덱스를 Out까지는 가능
    • 그러나 삽입하는 과정 적절한 삽입 위치를 찾고, 또 삽입하기 위해 인덱스를 모두 한 칸씩 미뤄야 함
  2. 링크리스트로 구현
    • 역시 최우선 순위의 반환은 쉬움
    • 그러나 배열과 마찬가지로 삽입 위치를 찾는데 낭비
    • 삭제는 O(1)
    • 삽입은 O(n)

출처

Categories: ,

Updated: