# 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;
}
```