이 내용은 윤성우의 열혈 자료구조를 학습한 내용입니다.
enqueue: 우선순위 큐에 데이터를 삽입하는 행위dequeue: 우선순위 큐에서 데이터를 꺼내는 행위큐와 달리 들어간 순서에 상관없이 우선순위가 높은 데이터가 먼저 나온다.
숫자 1이 가장 높은 우선순위를 뜻하며, 이보다 값이 커질수록 우선순위는 낮아진다고 가정한다.
우선순위 큐를 구현하는 방법은 다음과 같이 세 가지로 구분할 수 있다.

삽입의 위치를 찾기 위해 배열에 저장된 모든 데이터와 우선순위를 비교해야 할 수 있다.
⇒ 따라서, 우선순위 큐는 ‘힙’이라는 자료구조를 이용해서 구현하는 것이 일반적이다.
힙은 ‘완전 이진 트리’이다.
힙의 정의
힙의 종류


‘최소 힙’을 기준으로 설명한다.
숫자가 작을수록 우선순위가 높다고 가정한다.
저장 과정
우선순위 큐의 삭제는 ‘가장 높은 우선순위의 데이터 삭제’를 의미한다.
마지막 노드를 루트 노드의 자리로 옮긴 다음에, 자식 노드와의 비교를 통해서 제자리를 찾아가게 한다.

| 삽입 | 삭제 | |
|---|---|---|
| 배열 | ||
| 연결 리스트 | ||
| 힙 |
힙을 기반으로 하면 트리의 높이에 해당하는 수만큼만 비교연산을 진행하면 된다.
‘힙’은 배열 기반으로 구현해야 한다.
부모 노드의 인덱스 값 × 2부모 노드의 인덱스 값 × 2 + 1자식 노드의 인덱스 값 ÷ 2#ifndef __SIMPLE_HEAP_H__
#define __SIMPLE_HEAP_H__
#define TRUE 1
#define FALSE 0
#define HEAP_LEN 100
typedef char HData;
typedef int Priority;
typedef struct _heapElem
{
Priority pr; // 값이 작을수록 높은 우선순위
HData data;
} HeapElem;
typedef struct _heap
{
int numOfData;
HeapElem heapArr[HEAP_LEN];
} Heap;
void HeapInit(Heap * ph);
int HIsEmpty(Heap * ph);
void HInsert(Heap * ph, HData data, Priority pr);
HData HDelete(Heap * ph);
#endiftypedef struct _heapElem
{
Priority pr; // 값이 작을수록 높은 우선순위
HData data;
} HeapElem;✅ 숙지할 내용
void HeapInit(Heap * ph)
{
ph->numOfData = 0;
}int HIsEmpty(Heap * ph)
{
if(ph->numOfData == 0)
return TRUE;
else
return FALSE;
}int GetParentIDX(int idx)
{
return idx/2;
}int GetLChildIDX(int idx)
{
return idx*2;
}int GetRChildIDX(int idx)
{
return GetLChildIDX(idx)+1;
}int GetHiPriChildIDX(Heap * ph, int idx)
{
if(GetLChildIDX(idx) > ph->numOfData) // 자식노드가 존재하지 않는다면
return 0;
else if(GetLChildIDX(idx) == ph->numOfData) // 자식 노드가 왼쪽 자식 노드 하나만 존재한다면
return GetLChildIDX(idx);
else // 둘 다 존재한다면
{
// 오른쪽 자식 노드의 우선순위가 높다면
if(ph->heapArr[GetLChildIDX(idx)].pr
> ph->heapArr[GetRChildIDX(idx)].pr)
return GetRChildIDX(idx);
// 왼쪽 자식 노드의 우선순위가 높다면
else
return GetLChildIDX(idx);
}
}numOfData 는 마지막 노드의 고유번호이므로, 자식 노드의 값이 이보다 크면 존재하지 않는 자식 노드이다.힙의 마지막 노드를 루트 노드의 위치에 올린 다음에, 자식 노드와의 비교과정을 거치면서 아래로 내린다.
자신의 위치를 찾을 때까지 내린다.
HData HDelete(Heap * ph)
{
HData retData = (ph->heapArr[1]).data; // 반환을 위해서 삭제할 데이터 저장
HeapElem lastElem = ph->heapArr[ph->numOfData]; // 힙의 마지막 노드 저장
// parentIdx에는 마지막 노드가 저장될 위치정보가 담긴다.
int parentIdx = 1; // 루트 노드가 위치해야 할 인덱스 값 저장
int childIdx;
// 루트 노드의 우선순위가 높은 자식 노드를 시작으로 반복문 시작
while(childIdx = GetHiPriChildIDX(ph, parentIdx))
{
if(lastElem.pr <= ph->heapArr[childIdx].pr)
break; // 마지막 노드의 우선순위가 높으면 반복문 탈출
// 마지막 노드보다 우선순위 높으니, 비교대상 노드의 위치를 한 레벨 올림
ph->heapArr[parentIdx] = ph->heapArr[childIdx];
parentIdx = childIdx; // 마지막 노드가 저장될 위치정보를 한 레벨 내림
} // 반복문 탈출하면 parentIdx에는 마지막 노드의 위치정보가 저장됨
ph->heapArr[parentIdx] = lastElem; // 마지막 노드 최종 저장
ph->numOfData -= 1;
return retData;
}parentIdx 에 저장된 인덱스 값을 갱신해가며 찾아간다.새로운 데이터는 우선순위가 제일 낮다는 가정하에서 ‘마지막 위치’에 저장한다. 그리고 우선순위의 비교를 통해서 자신의 위치를 찾을 때까지 위로 올린다.
void HInsert(Heap * ph, HData data, Priority pr)
{
int idx = ph->numOfData+1; // 새 노드가 저장될 인덱스 값을 idx에 저장
HeapElem nelem = {pr, data}; // 새 노드의 생성 및 초기화
// 새 노드가 저장될 위치가 루트 노드의 위치가 아니라면 while문 반복
while(idx != 1)
{
// 새 노드와 부모 노드의 우선순위 비교
if(pr < (ph->heapArr[GetParentIDX(idx)].pr)) // 새 노드의 우선순위가 높다면
{
// 부모 노드를 한 레벨 내림, 실제로 내림
ph->heapArr[idx] = ph->heapArr[GetParentIDX(idx)];
// 새 노드를 한 레벨 올림,실제로 올리지는 않고 인덱스 값만 갱신
idx = GetParentIDX(idx);
}
else // 새 노드의 우선순위가 높지 않다면
break;
}
ph->heapArr[idx] = nelem; // 새 노드를 배열에 저장
ph->numOfData += 1;
}idx 를 통해서 계속 갱신한다.이전에 구성한 구조체와 함수들에서는 데이터를 입력하기 전 우선순위를 결정하고 그 값을 전달했다.
이는 대부분 데이터를 기준으로 결정되는 우선순위를 직접 결정하는 불편한 과정이다.
프로그래머가 힙의 우선순위 판단 기준을 설정할 수 있도록 변경한다.
typedef struct _heap
{
PriorityComp * comp; // typedef int PriorityComp(HData d1, HData d2);
int numOfData;
HData heapArr[HEAP_LEN]; // typedef char HData;
} Heap;HeapElem 을 삭제했다.PriorityComp * comptypedef int (*PriorityComp)(HData d1, HData d2);첫 번째 인자의 우선순위 ≥ 두 번째 인자의 우선순위: 0보다 큰 값이 반환첫 번째 인자의 우선순위 ≤ 두 번째 인자의 우선순위: 0보다 작은 값 반환첫 번째 인자의 우선순위 = 두 번째 인자의 우선순위: 0 반환void HeapInit(Heap * ph, PriorityComp pc)
{
ph->numOfData = 0;
ph->comp = pc; // 우선순위 비교에 사용되는 함수의 등록
}int GetHiPriChildIDX(Heap * ph, int idx);int GetHiPriChildIDX(Heap * ph, int idx)
{
if(GetLChildIDX(idx) > ph->numOfData)
return 0;
else if(GetLChildIDX(idx) == ph->numOfData)
return GetLChildIDX(idx);
else
{
// if(ph->heapArr[GetLChildIDX(idx)].pr
// > ph->heapArr[GetRChildIDX(idx)].pr)
if(ph->comp(ph->heapArr[GetLChildIDX(idx)],
ph->heapArr[GetRChildIDX(idx)]) < 0) // 두 번째 인자 우선순위가 더 크다.
return GetRChildIDX(idx);
else
return GetLChildIDX(idx);
}
}void HInsert(Heap * ph, HData data);void HInsert(Heap * ph, HData data)
{
int idx = ph->numOfData+1;
while(idx != 1)
{
// if(pr < (ph->heapArr[GetParentIDX(idx)].pr))
if(ph->comp(data, ph->heapArr[GetParentIDX(idx)]) > 0) // 첫 번째 우선순위가 더 높음
{
ph->heapArr[idx] = ph->heapArr[GetParentIDX(idx)];
idx = GetParentIDX(idx);
}
else
{
break;
}
}
ph->heapArr[idx] = data;
ph->numOfData += 1;
}HData HDelete(Heap * ph);HData HDelete(Heap * ph)
{
HData retData = ph->heapArr[1];
HData lastElem = ph->heapArr[ph->numOfData];
int parentIdx = 1;
int childIdx;
while(childIdx = GetHiPriChildIDX(ph, parentIdx))
{
// if(lastElem.pr <= ph->heapArr[childIdx].pr)
if(ph->comp(lastElem, ph->heapArr[childIdx]) >= 0) // 첫 번째 우선수위가 높거나 같음
break;
ph->heapArr[parentIdx] = ph->heapArr[childIdx];
parentIdx = childIdx;
}
ph->heapArr[parentIdx] = lastElem;
ph->numOfData -= 1;
return retData;
}#include <stdio.h>
#include "UsefulHeap.h"
int DataPriorityComp(char ch1, char ch2) // 우선순위 비교함수
{
return ch2-ch1;
}
int main(void)
{
Heap heap;
HeapInit(&heap, DataPriorityComp); // 우선순위 비교함수 등록
HInsert(&heap, 'A');
HInsert(&heap, 'B');
HInsert(&heap, 'C');
printf("%c \n", HDelete(&heap));
HInsert(&heap, 'A');
HInsert(&heap, 'B');
HInsert(&heap, 'C');
printf("%c \n", HDelete(&heap)); // A
while(!HIsEmpty(&heap))
printf("%c \n", HDelete(&heap)); // A B B C C
return 0;
}
void PQueueInit(PQueue * ppq, PriorityComp pc);int PQIsEmpty(PQueue * ppq);void PEnqueue(PQueue * ppq, PQData data);PQData PDequeue(PQueue * ppq);#ifndef __PRIORITY_QUEUE_H__
#define __PRIORITY_QUEUE_H__
#include "UsefulHeap.h"
typedef Heap PQueue;
typedef HData PQData;
void PQueueInit(PQueue * ppq, PriorityComp pc);
int PQIsEmpty(PQueue * ppq);
void PEnqueue(PQueue * ppq, PQData data);
PQData PDequeue(PQueue * ppq);
#endif#include "PriorityQueue.h"
#include "UsefulHeap.h"
void PQueueInit(PQueue * ppq, PriorityComp pc)
{
HeapInit(ppq, pc);
}
int PQIsEmpty(PQueue * ppq)
{
return HIsEmpty(ppq);
}
void PEnqueue(PQueue * ppq, PQData data)
{
HInsert(ppq, data);
}
PQData PDequeue(PQueue * ppq)
{
return HDelete(ppq);
}#include <stdio.h>
#include "PriorityQueue.h"
int DataPriorityComp(char ch1, char ch2)
{
return ch2-ch1;
}
int main(void)
{
PQueue pq;
PQueueInit(&pq, DataPriorityComp);
PEnqueue(&pq, 'A');
PEnqueue(&pq, 'B');
PEnqueue(&pq, 'C');
printf("%c \n", PDequeue(&pq));
PEnqueue(&pq, 'A');
PEnqueue(&pq, 'B');
PEnqueue(&pq, 'C');
printf("%c \n", PDequeue(&pq)); // A
while(!PQIsEmpty(&pq))
printf("%c \n", PDequeue(&pq)); // A B B C C
return 0;
}참고: 윤성우의 열혈 자료구조