# Chap. 09 - Priority Queue > 參考書目 : Fundamentals of Data Structures in C (2nd), Ellis H., Sartaj S., Susan A.-F. > > 其他科目內容請參見 [[Here]](https://hackmd.io/@ChenZE/By4SOO6Jyl) ## Content * [Sec. 9.1 Single and double end priority queue](#Sec-91-Single-and-double-end-priority-queue) * [1. Single-end priority queue](#1-Single-end-priority-queue) * [1.1 Max priority queue](#11-Max-priority-queue) * [1.2 Min priority queue](#12-Min-priority-queue) * [1.3 Implement of single-end priority queue](#13-Implement-of-single-end-priority-queue) * [2. Double-end priority queue](#2-Double-end-priority-queue) * [2.1 Implement of double-end priority queue](#21-Implement-of-double-end-priority-queue) ## Sec. 9.1 Single and double end priority queue 優先權佇列(priority queue)是一種 queue 的變形,集合中的所有元素都包含一個對應的優先權(priority)並根據此優先權進行排列。可分為單向優先權佇列(single-end priority queue)與雙向優先權佇列(double-end priority queue)。單向優先權佇列常使用 4 種方式實作: 1. Array 2. Linked list 3. Heap 4. Binary tree 其中又以 binary heap 最常使用。 > 關於 heap 與 binary heap 的介紹請參閱 [Chap. 05 - Tree](https://hackmd.io/kpb3BthlTkm4ISQrhCIWWA#2-Maximum-heap) ### 1. Single-end priority queue 單向優先權佇列依據排列順序不同,又可再分為最小優先權與最大優先權,兩者都支援以下的運算: * 取出(peak)一個最小 or 最大優先權的元素 * 插入(push)一個任意優先權的元素 * 刪除(pop)一個最小 or 最大優先權的元素 以 binary heap 的實作為例,上述的運算可以做到以下的時間複雜度 | Peak | Push | Pop | | :-: | :-: | :-: | | $O(1)$ | $O(\log n)$ | $O(\log n)$ | :::info 後續的實作 code 中皆以下面兩個 max heap 與 min heap 舉例。另外,此處以陣列實作的 binary heap 都是以 0-index 系統為主。 ::: > The following code is saved in `PriorityQueue.c`. ```c= /** * The following is an example of max priority queue which is the output of newMaxPrior * 82 * / \ * 49 44 * The following is an example of min priority queue which is the output of newMinPrior * 5 * / \ * 8 12 * Both of them use 0-index */ element *newMaxPrior(){ element *ptr; MALLOC(ptr, sizeof(element) * MAX_SIZE); ptr[0].priority = 82; ptr[1].priority = 49; ptr[2].priority = 44; return ptr; } element *newMinPrior(){ element *ptr; MALLOC(ptr, sizeof(element) * MAX_SIZE); ptr[0].priority = 5; ptr[1].priority = 8; ptr[2].priority = 12; return ptr; } ``` #### 1.1 Max priority queue 最大優先權佇列的三種運算實作如下: > The following code is saved in `PriorityQueue.c`. ```c= // operation for max priority queue element getMaxPrior(element *pq){ /** get element with max priority in max priority queue * Parameters * ------------ * pq: a max priority queue */ return pq[0]; } void pushMaxPrior(element *pq, element item, int *n){ if (*n >= MAX_SIZE){ fprintf(stderr, "The queue us FULL.\n"); exit(EXIT_FAILURE); } // bubble up int i = (*n)++; // index of new element int parent = (i - 1) / 2; while ((i != 0) && (pq[parent].priority < item.priority)){ pq[i] = pq[parent]; i = parent; parent = (i - 1) / 2; } pq[i] = item; } element popMaxPrior(element *pq, int *n){ if ((*n) <= 0){ fprintf(stderr, "The queue us EMPTY."); exit(EXIT_FAILURE); } // bubble down element res = pq[0]; // root element temp = pq[--(*n)]; // take last element and minor 1 int parent = 0, child = 2 * parent + 1; while (child < *n){ // check right node exist and compare two children if ((child + 1 < *n) && (pq[child].priority < pq[child+1].priority)){ child++; } // if the parent is larger than the largest child, stop if (temp.priority > pq[child].priority){ break; } // otherwise, move to next level pq[parent] = pq[child]; parent = child; child = 2 * parent + 1; } pq[parent] = temp; return res; } ``` #### 1.2 Min priority queue 最小優先權佇列的三種運算實作如下: > The following code is saved in `PriorityQueue.c`. ```c= // operation for min priority queue element getMinPrior(element *pq){ return pq[0]; } void pushMinPrior(element *pq, element item, int *n){ if (*n >= MAX_SIZE){ fprintf(stderr, "The priority queue is FULL.\n"); exit(EXIT_FAILURE); } // bubble up int i = (*n)++; // last index int parent = (i - 1) / 2; while (i != 0 && pq[parent].priority > item.priority){ pq[i] = pq[parent]; i = parent; parent = (i - 1) / 2; } pq[i] = item; } element popMinPrior(element *pq, int *n){ if (*n <= 0){ fprintf(stderr, "The priority queue is EMPTY.\n"); exit(EXIT_FAILURE); } // bubble down element res = pq[0]; element temp = pq[--(*n)]; int parent = 0, child = 2 * parent - 1; while (child < *n){ if ((child + 1 < *n) && pq[child].priority > pq[child+1].priority){ child++; } if (pq[parent].priority > pq[child].priority){ pq[parent] = pq[child]; parent = child; child = 2 * parent + 1; } else { break; } pq[parent] = temp; } return res; } ``` #### 1.3 Implement of single-end priority queue > The following code is save in `PriorityQueue-implement.c`. ```c= #include "PriorityQueueADT.h" #include <stdio.h> #include <stdlib.h> int main(void){ element *maxPQ = newMaxPrior(); int maxPQLen = 3; element *minPQ = newMinPrior(); int minPQLen = 3; element item1 = {16}, item2 = {72}, item3 = {31}, item4 = {64}; // push to maxPQ element item5 = {101}, item6 = {11}, item7 = {23}, item8 = {10}; // push to minPQ element maxPrior, minPrior; // max priority queue puts("before push: "); printPQ(maxPQ, maxPQLen); // [82, 46, 44] pushMaxPrior(maxPQ, item1, &maxPQLen); pushMaxPrior(maxPQ, item2, &maxPQLen); pushMaxPrior(maxPQ, item3, &maxPQLen); pushMaxPrior(maxPQ, item4, &maxPQLen); puts("After push: "); printPQ(maxPQ, maxPQLen); // [82, 72, 64, 16, 49, 31, 44] puts("After pop: "); printf("pop element: %d\n", popMaxPrior(maxPQ, &maxPQLen).priority); // 82 printPQ(maxPQ, maxPQLen); // [72, 49, 64, 16, 44, 31] // min priority queue puts("before push: "); printPQ(minPQ, minPQLen); // [5, 8, 12] pushMinPrior(minPQ, item5, &minPQLen); pushMinPrior(minPQ, item6, &minPQLen); pushMinPrior(minPQ, item7, &minPQLen); pushMinPrior(minPQ, item8, &minPQLen); puts("After push: "); printPQ(minPQ, minPQLen); // [5, 8, 10, 101, 11, 23, 12] puts("After pop: "); printf("pop element: %d\n", popMinPrior(minPQ, &minPQLen).priority); // 5 printPQ(minPQ, minPQLen); // [8, 11, 10, 101, 12, 23] free(maxPQ); free(minPQ); return 0; } ``` --- 考慮將兩個優先佇列合併的運算,可以再延伸出 2 種資料結構包含此運算: (1) 左傾樹(leftist tree) 與 (2) 二項式堆積(binomial heap),這種結構稱為合併式優先佇列。若再進一步擴充合併式優先佇列: * 刪除任意給定位置的元素 * 減少任意給定位置的元素的優先權 可以再延伸兩種資料結構: (1) 費氏堆積(fibonucci heap) 與 (2) 成對堆積(pairings heap) :::info * 參考書目中有這四種延伸的資料結構,但此處暫時省略 * 優先權佇列的基本操作除了上述三種(取出、插入刪除)以外,有些還包括調整優先權的運算 ::: ### 2. Double-end priority queue 雙向優先權佇列(**d**ouble-**e**nd **p**riority **q**ueue, depq)同樣是 queue 的變形,支援以下的運算: 1. 取出(peak)最小優先權的元素 2. 取出(peak)最大優先權的元素 3. 插入(push)任意優先權的元素 4. 刪除(pop)最小優先權的元素 5. 刪除(pop)最大優先權的元素 由上述幾種運算可知,雙向優先權佇列是一種合併最小與最大優先權佇列的資料結構,常使用 doubly linked list 實作。以 doubly linked list 所實作的雙向優先權佇列可以做到以下的時間複雜度 | peakMin() | peakMax() | push() | popMin() | popMax() | | :-: | :-: | :-: | :-: | :-: | | $O(1)$ | $O(1)$ | $O(n)$ | $O(1)$ | $O(1)$ | :::info 後續以 doubly linked list 實作雙向優先權佇列,且每個 doubly linked list 都有一個標頭節點(header node) ::: > The following code is saved in `DoubleEndPriorityQueue.c`. ```c= // peak nodePointer getMaxPrior(nodePointer pq){ nodePointer firstNode = pq->next; if (!firstNode){ fprintf(stderr, "The priority queue is EMPTY.\n"); return NULL; } return firstNode; } nodePointer getMinPrior(nodePointer pq){ nodePointer firstNode = pq->next; if (!firstNode){ fprintf(stderr, "The priority queue is EMPTY.\n"); return NULL; } while (firstNode->next){ firstNode = firstNode->next; } return firstNode; } // insert void pushPrior(nodePointer pq, nodePointer node){ nodePointer ptr = pq->next; // firstNode // if priority is NULL, node is the first node if (!ptr){ pq->next = node; node->prev = pq; node->next = NULL; return; // if no return, the function will proceed } // traversal the while list to find correct position until // (1) ptr is last or // (2) ptr < node while (ptr->next){ if (ptr->priority < node->priority){ break; } ptr = ptr->next; } // if ptr is last and ptr > node if ((!ptr->next) && ptr->priority >= node->priority){ node->next = ptr->next; node->prev = ptr; ptr->next = node; return; // if no return, the function will proceed } // ptr < node node->next = ptr; // node link to next node->prev = ptr->prev; // node link to prev ptr->prev->next = node; // prev link to node ptr->prev = node; // ptr link to node } // delete nodePointer popMaxPrior(nodePointer pq){ nodePointer ptr = pq->next; if (!ptr){ fprintf(stderr, "The priority queue is EMPTY.\n"); return NULL; } pq->next = ptr->next; // pq link to second node // if second node is not NULL if (ptr->next){ ptr->next->prev = pq; // second node link to pq } // split ptr ptr->next = NULL; ptr->prev = NULL; return ptr; } nodePointer popMinPrior(nodePointer pq){ nodePointer ptr = pq->next; // null list if (!ptr){ fprintf(stderr, "The priority queue is EMPTY.\n"); return NULL; } // traversal whole list to last node // after traversal, ptr is the last node and we have to remove it while (ptr->next){ ptr = ptr->next; } ptr->prev->next = NULL; // the previous of last node link to NULL // split last node ptr->next = NULL; ptr->prev = NULL; return ptr; } ``` #### 2.1 Implement of double-end priority queue ##### Create queue > The following code is saved in `DoubleEndPriorityQueue.c`. ```c= // basic nodePointer createQueue(){ // create header node nodePointer header; MALLOC(header, sizeof(struct node)); header->next = NULL; header->prev = NULL; return header; } nodePointer createNode(int priority){ nodePointer node; MALLOC(node, sizeof(struct node)); node->next = NULL; node->prev = NULL; node->priority = priority; return node; } void printPQ(nodePointer pq){ nodePointer ptr = pq->next; printf("Priority Queue = "); while (ptr){ printf("%d ", ptr->priority); ptr = ptr->next; } puts(""); } void freePQ(nodePointer pq){ nodePointer ptr; while (pq){ ptr = pq; pq = pq->next; free(ptr); } } ``` ##### Implement > The following code is saved in `DoubleEndPriorityQueue-implement.c`. ```c= #include <stdio.h> #include <stdlib.h> #include "DoubleEndPriorityQueueADT.h" int main(){ nodePointer header = createQueue(); nodePointer a, b, c, d, e; a = createNode(82); b = createNode(24); c = createNode(30); d = createNode(16); puts("After push: "); pushPrior(header, a); pushPrior(header, b); pushPrior(header, c); pushPrior(header, d); printPQ(header); // [82, 30, 24, 16] puts("After pop: "); nodePointer maxPrior = getMaxPrior(header); nodePointer minPrior = getMinPrior(header); nodePointer popMax = popMaxPrior(header); nodePointer popMin = popMinPrior(header); printPQ(header); // [30, 24] printf("maxPrior = %d\n", maxPrior->priority); // 82 printf("minPrior = %d\n", minPrior->priority); // 16 printf("popMax = %d\n", popMax->priority); // 82 printf("popMin = %d\n", popMin->priority); // 16 free(popMax); free(popMin); freePQ(header); return 0; } ```