우선순위 큐
- 우선순위를 가진 항목들을 저장하는 큐
우선순위큐 ADT
- 객체
- n개의 element형의 우선순위를 가진 요소들의 모임
- n개의 element형의 우선순위를 가진 요소들의 모임
- 연산
create()
::= 우선순위 큐를 생성init(q)
::= 우선순위 큐 q를 초기화is_empty(q)
::= 우선순 큐 q가 비어있는지를 검사is_full(q)
::= 우선순위 큐 q가 가득 찼는가를 검사insert(q, x)
::= 우선순위 큐 q에 요소 x를 추가delete(q)
: = 우선순위 큐 q로부터 가장 우선순위가 높은 요소를 삭제 및 반환find(q)
::= 우선순위가 가장 높은 요소를 반환
- 우선순위 큐는 2가지로 구분
- 최솟값 우선수위 큐
- 작을수록 우선순위가 높음
- 작을수록 우선순위가 높음
- 최댓값 우선순위 큐
- 높을수록 우선순위가 높음
- 최솟값 우선수위 큐
우선순위 큐 구현방법
- 배열로 구현
- 순서 없는 배열
- 삽입 : O(1)
- 삭제 : O(n)
- 정렬된 배열
- 삽입 : O(n)
- 삭제 : O(1)
- 순서 없는 배열
- 연결리스트를 이용한 우선순위 큐
- 순서 없는 연결리스트
- 삽입 : O(1)
- 삭제 : O(n)
- 정렬된 연결 리스트
- 삽입 : O(n)
- 삭제 : O(1)
- 순서 없는 연결리스트
- 힙(heap)을 이용한 우선순위 큐
- 삽입 = 삭제 = O(logn)
힙(heap)
- 노드들이 저장하고 있는 키들이 다음 조건을 만족하는 완전이진트리
- 최대 힙(max heap) → key(부모노드) ≥ key(자식노드)
- 최소 힙(min heap) → key(부모노드) ≤ key(자식노드)
- 최대 힙(max heap) → key(부모노드) ≥ key(자식노드)
힙(heap)의 높이
- n개의 노드를 가지고 있는 힙의 높이 = O(log n)
힙(heap)의 구현 방법
- 배열로 구현
- 완전 이진트리이므로 밀집된 배열로 구성 → 편리함
- 각 노드에 번호를 매김 → 배열로 인덱스로 사용
- 부모노드와 자식노드의 관계
- 왼쪽 자식의 인덱스 = (부모의 인덱스) * 2
- 오른쪽 자식의 인덱스 = (부모의 인덱스) * 2 + 1
- 부모의 인덱스 = (자식의 인덱스) / 2
힙(heap) 구조체
#define MAX_ELEMENT 200
typedef struct{
int key;
}element;
typedef struct{
element heap[MAX_ELEMENT];
int heap_size
}HeapType;
힙(heap) 삽입 연산
- 새로운 요소가 들어오면 새로운 노드를 heap의 마지막 노드에 이어서 삽입
- 삽입 후에 새로운 노드를 부모 노드들과 교환해서 heap의 조건 만족
- Upheap 연산 알고리즘 사용
- 새로운 키(k) 삽입 후 heap의 성질 만족 확인
- 삽입된 노드 ~ 루트 노드까지 k와 비교
- 삽입된 노드 ~ 루트 노드까지 k와 비교
- 키 k 부모 노드(비교한 노드) 보다 크면 교환
작으면 upheap 알고리즘 종료- heap의 높이가 O(log n)이므로 upheap 연산은 O(log n)이다
- 새로운 키(k) 삽입 후 heap의 성질 만족 확인
힙(heap) 삭제 연산
- 최대 heap에서는 가장 큰 키 값을 가진 노드를 삭제하는 것
- 시간복잡도는 삽입 연산과 같음 → O(log n)
- 루트 노드 삭제
- 마지막 노드를 루트 노드로 이동
- 루트에서 단말 노드까지의 경로에 있는 노드들을 교환하여 heap 조건 만족
- downheap 연산 알고리즘 사용
힙(heap) 정렬
- heap을 이용하여 정렬을 구현
- 정렬해야 할 n개의 요소들을 최대 heap에 삽입
- 한 번에 하나씩 요소를 heap에서 삭제하여 저장
- 삭제되는 요소들은 값이 증가되는 순서(최소 heap)의 경우
- 하나의 요소를 heap에 삽입하거나 삭제할 때 시간
- O(log n) 만큼 소요되고 요소의 개수가 n개이므로 전체적으로 O(nlogn)
- O(log n) 만큼 소요되고 요소의 개수가 n개이므로 전체적으로 O(nlogn)
- N개의 데이터 중 오름차순/내림차순으로 X개 뽑고자 할 때 특히 유용하게 쓰인다.
힙(heap) 트리 구현
#include <stdio.h>
#include <stdlib.h>
#define MAX_ELEMENT 200
typedef struct{
int key;
}element;
typedef struct{
element heap[MAX_ELEMENT];
int heap_size;
}HeapType;
// 생성 함수
HeapType* create(){
return (HeapType*)malloc(sizeof(HeapType));
}
// 초기화 함수
void init(HeapType* h){
h -> heap_size = 0;
}
// 현재 요소의 개수가 heap_size인 히프 h에 item을 삽입한다.
// 삽입함수(최대 heap)
void insert_max_heap(HeapType *h, element item){
int i;
i = ++(h->heap_size);
// 트리를 거슬러 올라가면서 부모 노드와 비교
while((i != 1) && item.key > h -> heap[i/2].key){
h -> heap[i] = h -> heap[i/2];
i /= 2;
}
h ->heap[i] = item;
}
// 삭제 함수(최대 heap)
element delete_max_heap(HeapType* h){
int parent, child;
element item, temp;
item = h -> heap[1];
temp = h -> heap[(h->heap_size)--];
parent = 1;
child = 2;
while(child <= h -> heap_size){
// 현재 노드의 자식노드 중 더 큰 자식노드를 찾는다.
if((child < h -> heap_size &&
(h -> heap[child].key < h -> heap[child+1].key))) child++;
if(temp.key >= h -> heap[child].key) break;
// 한단계 아래로 이동
h->heap[parent] = h ->heap[child];
parent = child;
child *= 2;
}
h -> heap[parent] = temp;
return item;
}
// 우선 순위 큐인 heap를 이용한 정렬
void heap_sort(element a[],int n)
{
int i;
HeapType *h;
h = create();
init(h);
for(i = 0; i < n; i++) insert_max_heap(h,a[i]);
for(i = n-1; i >= 0; i--) a[i] = delete_max_heap(h);
}
int main(){
element e1 = {10}, e2 = {5}, e3 = {30};
element e4,e5,e6;
HeapType* heap;
heap = create();
init(heap);
insert_max_heap(heap, e1);
insert_max_heap(heap, e2);
insert_max_heap(heap, e3);
e4 = delete_max_heap(heap);
printf("< %d > ", e4.key);
e5 = delete_max_heap(heap);
printf("< %d > ", e5.key);
e6 = delete_max_heap(heap);
printf("< %d > ", e6.key);
free(heap);
printf("\n");
element a[] = {1,4,2,5};
// heap sort 이전
printf("a = ");
for(int i = 0; i < 4; i++) printf("%d ", a[i]);
heap_sort(a,4);
printf("\n");
// heap sort 이후
printf("a = ");
for(int i = 0; i < 4; i++) printf("%d ", a[i]);
}
<출력>
< 30 > < 10 > < 5 >
a = 1 4 2 5
a = 1 2 4 5
최소 Heap 트리 구현
void insert_min_heap(HeapType *h, element item){
int i;
i = ++(h->heap_size);
// 트리를 거슬러 올라가면서 부모 노드와 비교
while((i != 1) && item.key < h -> heap[i/2].key){
h -> heap[i] = h -> heap[i/2];
i /= 2;
}
h ->heap[i] = item;
}
// 삭제 함수
element delete_min_heap(HeapType* h){
int parent, child;
element item, temp;
item = h -> heap[1];
temp = h -> heap[(h->heap_size)--];
parent = 1;
child = 2;
while(child <= h -> heap_size){
// 현재 노드의 자식노드 중 더 큰 자식노드를 찾는다.
if((child < h -> heap_size &&
(h -> heap[child].key < h -> heap[child+1].key))) child++;
if(temp.key >= h -> heap[child].key) break;
// 한단계 아래로 이동
h->heap[parent] = h ->heap[child];
parent = child;
child *= 2;
}
h -> heap[parent] = temp;
return item;
}
최소 Heap 트리 응용 : 머신 스케줄링
// ------- 구현한 최소 힙 트리 함수 ------- //
#define JOBS 7
#define MACHINES 3
int main(){
int jobs[JOBS] = {8,7,6,5,3,2,1}; // 작업은 정렬되어 있다고 가정
element m = {0,0};
HeapType* h;
h = create();
init(h);
for(int i = 0; i < MACHINES; i++){
m.id = i+1;
m.avail = 0;
insert_min_heap(h,m);
}
// 최소 heap에서 기계를 꺼내서 작업을 할당하고 사용가능 시간을 증가시킨 후
// 다시 최소 히프 추가
for(int i = 0; i < JOBS; i++){
m = delete_min_heap(h);
printf("JOBS %d을 시간 =%d부터 시간 =%d까지 기계%d번에 할당\n"
,i,m.avail,m.avail + jobs[i]-1,m.id);
m.avail += jobs[i];
insert_min_heap(h,m);
}
return 0;
}
<출력>
JOBS 0을 시간 =0부터 시간 =7까지 기계1번에 할당
JOBS 1을 시간 =0부터 시간 =6까지 기계3번에 할당
JOBS 2을 시간 =7부터 시간 =12까지 기계3번에 할당
JOBS 3을 시간 =8부터 시간 =12까지 기계1번에 할당
JOBS 4을 시간 =13부터 시간 =15까지 기계3번에 할당
JOBS 5을 시간 =13부터 시간 =14까지 기계1번에 할당
JOBS 6을 시간 =15부터 시간 =15까지 기계1번에 할당
Heap 정렬 응용 : 허프만 코드
- 이진트리는 각 글자의 빈도가 알려져 있는 메시지의 내용을 압축하는 데 사용 가능
- 이런 종류의 이진트리 → 허프만 코딩 트리
- 이런 종류의 이진트리 → 허프만 코딩 트리
- 허프만 코드 생성 절차
허프만 코드 프로그램
#include <stdio.h>
#include <stdlib.h>
#define MAX_ELEMENT 100
// 트리 구조체
typedef struct TreeNode{
int weight; // 빈도수
char ch; // 문자
struct TreeNode *left_child;
struct TreeNode *right_child;
}TreeNode;
typedef struct{
TreeNode *ptree; //가리키는 노드
char ch;
int key;
}element;
typedef struct{
element heap[MAX_ELEMENT];
int heap_size;
}HeapType;
// 생성 함수
HeapType* create(){
return (HeapType*)malloc(sizeof(HeapType));
}
// 초기화 함수
void init(HeapType* h){
h -> heap_size = 0;
}
// 현재 요소의 개수가 heap_size인 히프 h에 item을 삽입한다.
// 삽입함수
void insert_min_heap(HeapType *h, element item){
element delete_min_heap(HeapType* h){}
// 위에서 구현한 최소 heapTree
TreeNode *make_tree(TreeNode *left, TreeNode* right){
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
node -> left_child = left;
node -> right_child = right;
return node;
}
void destory_tree(TreeNode* root){
if(root == NULL) return;
destory_tree(root -> left_child);
destory_tree(root -> right_child);
free(root);
}
// 단말 노드인지 확인
int is_leaf(TreeNode* root){
return !(root->left_child) && !(root->right_child);
}
// 배열 출력
void print_array(int codes[],int n){
for(int i = 0; i < n; i++) printf("%d",codes[i]);
printf("\n");
}
// 출력 함수
void print_codes(TreeNode* root, int codes[], int top){
// 1을 저장하고 순환호출한다.
if(root -> left_child){
codes[top] = 1;
print_codes(root->left_child,codes,top+1);
}
// 0을 저장하고 순환호출한다.
if(root -> right_child){
codes[top] = 0;
print_codes(root->right_child,codes,top+1);
}
if(is_leaf(root)){
printf("%c", root ->ch);
print_array(codes,top);
}
}
//허프만 코드
void huffman_tree(int freq[], char ch_list[], int n){
int i;
TreeNode *node, *x;
HeapType* heap;
element e, e1, e2;
int codes[100];
int top = 0;
heap = create();
init(heap);
for(i = 0; i<n; i++){
node = make_tree(NULL,NULL);
e.ch = node -> ch = ch_list[i];
e.key = node->weight = freq[i];
e.ptree = node;
insert_min_heap(heap,e);
}
for(i=1;i<n;i++){
// 최소값을 갖는 두 개의 노드 삭제
e1 = delete_min_heap(heap);
e2 = delete_min_heap(heap);
// 두개의 노드를 합친다.
x = make_tree(e1.ptree,e2.ptree);
e.key = x->weight = e1.key + e2.key;
e.ptree = x;
insert_min_heap(heap,e);
}
e = delete_min_heap(heap);
print_codes(e.ptree,codes,top);
destory_tree(e.ptree);
free(heap);
}
int main(){
char ch_list[] = {'s','l','n','t','e'};
int freq[] = {15,12,8,6,5};
huffman_tree(freq,ch_list,5);
return 0;
}
<출력>
t1
n01
s001
e0001
l0000
추가구현
최소 힙 트리 - 입력받아서 저장하고 우선순위대로 일 출력하기
#include <stdio.h>
#include <stdlib.h>
#define MAX_ELEMENT 200
typedef struct{
int num;
char* text;
}element;
typedef struct{
element heap[MAX_ELEMENT];
int heap_size;
}HeapType;
// 생성 함수
HeapType* create(){
return (HeapType*)malloc(sizeof(HeapType));
}
// 초기화 함수
void init(HeapType* h){
h -> heap_size = 0;
}
void insert_min_heap(HeapType *h, element item){
int i;
i = ++(h->heap_size);
// 트리를 거슬러 올라가면서 부모 노드와 비교
while((i != 1) && item.num < h -> heap[i/2].num){
h -> heap[i] = h -> heap[i/2];
i /= 2;
}
h ->heap[i] = item;
}
// 삭제 함수
element delete_min_heap(HeapType* h){
int parent, child;
element item, temp;
item = h -> heap[1];
temp = h -> heap[(h->heap_size)--];
parent = 1;
child = 2;
while(child <= h -> heap_size){
// 현재 노드의 자식노드 중 더 큰 자식노드를 찾는다.
if((child < h -> heap_size &&
(h -> heap[child].num< h -> heap[child+1].num))) child++;
if(temp.num >= h -> heap[child].num) break;
// 한단계 아래로 이동
h->heap[parent] = h ->heap[child];
parent = child;
child *= 2;
}
h -> heap[parent] = temp;
return item;
}
#define JOBS 7
#define MACHINES 3
int main(){
char input;
HeapType* h = create();
init(h);
while(1){
printf("삽입(i), 삭제(d): ");
scanf("%s",&input);
char *txt = (char*)malloc(sizeof(char) * 10);
switch(input){
case('i'):
element item;
printf("\n 할일 : ");
scanf("%s",txt);
item.text = txt;
printf("\n 우선순위 : ");
scanf("%d", &item.num);
insert_min_heap(h,item);
break;
case('d'):
printf("%s\n",delete_min_heap(h).text);
break;
case('q'):
printf("프로그램을 종료합니다\n");
exit(1);
break;
default:
printf("잘못입력하셨습니다. 다시 입력하세요\n");
break;
}
}
}
<출력>
삽입(i), 삭제(d): i
할일 : 옷 입기
우선순위 : 삽입(i), 삭제(d): 잘못입력하셨습니다. 다시 입력하세요
삽입(i), 삭제(d): i
할일 : do work
우선순위 : 삽입(i), 삭제(d): 잘못입력하셨습니다. 다시 입력하세요
삽입(i), 삭제(d): i
할일 : i
우선순위 : f
삽입(i), 삭제(d): 잘못입력하셨습니다. 다시 입력하세요
삽입(i), 삭제(d): i
할일 : djfjf
우선순위 : 1
삽입(i), 삭제(d): x
잘못입력하셨습니다. 다시 입력하세요
삽입(i), 삭제(d): q
junpyohong@hongjunpyoui-MacBookPro 자료구조 % ./DS18HongJunpyo01
삽입(i), 삭제(d): i
할일 : 안녕
우선순위 : 2
삽입(i), 삭제(d): i
할일 : 짐 옮기기
우선순위 : 삽입(i), 삭제(d): 잘못입력하셨습니다. 다시 입력하세요
삽입(i), 삭제(d): i
할일 : 짐 옮기기
우선순위 : 삽입(i), 삭제(d): 잘못입력하셨습니다. 다시 입력하세요
삽입(i), 삭제(d): i
할일 : 기다리기
우선순위 : 3
삽입(i), 삭제(d): 걷i
할일 : 걷기
우선순위 : 1
삽입(i), 삭제(d): d
걷기
삽입(i), 삭제(d): d
짐
삽입(i), 삭제(d): d
기다리기
삽입(i), 삭제(d): d
짐
삽입(i), 삭제(d): q
연결리스트로 Heap 트리 구현(Max-Heap)
#include <stdio.h>
#include <stdlib.h>
// 연결리스트 구조체
typedef struct LinkedList
{
int data;
struct LinkedList *link; // 자기 참조 구조체
} LinkedList;
LinkedList *create()
{
return (LinkedList *)malloc(sizeof(LinkedList));
}
LinkedList *init(LinkedList *head)
{
head->data = 0;
head->link = NULL;
}
int is_empty(LinkedList *head)
{
return (head == NULL);
}
int is_full(LinkedList *head)
{
return 0;
}
LinkedList *insert_max_heap(LinkedList *head, int item)
{
if (head == NULL)
{
head = create();
head->data = item;
return head;
}
LinkedList *p = head;
LinkedList *pre = NULL;
LinkedList *node = (LinkedList *)malloc(sizeof(LinkedList));
node->data = item;
while (p != NULL)
{
if (p->data <= item)
{
if (pre == NULL)
{
head = node;
head->link = p;
}
else
{
node->link = p;
pre->link = node;
}
return head;
}
pre = p;
p = p->link;
}
pre->link = node;
node->link = NULL;
return head;
}
// 첫번째 노드를 삭제하는 함수
int delete_max_heap(LinkedList *head)
{
LinkedList *removed = head;
head = head->link;
int value = removed->data;
free(removed);
return value;
}
int find_max(LinkedList *head)
{
return head->data;
}
// 노드 출력
void print(LinkedList *head)
{
// 노드가 NULL일때까지 즉, 마지막 노드까지 반복
for (LinkedList *p = head; p != NULL; p = p->link)
{
printf("%d ", p->data);
}
printf("\n");
}
int main()
{
LinkedList *h = create();
h = insert_max_heap(h, 1);
h = insert_max_heap(h, 5);
h = insert_max_heap(h, 2);
h = insert_max_heap(h, 4);
h = insert_max_heap(h, 5);
h = insert_max_heap(h, 2);
h = insert_max_heap(h, 3);
print(h);
printf(" 가장 큰 값 : %d\n", find_max(h));
}
<출력>
5 4 3 2 2 1 0
가장 큰 값 : 5