有段時間沒看演算法跟資料結構了,今天看了『優先佇列』,並寫成筆記。
為什麼需要優先佇列?
在一個佇列中的作業,有的時候我們需要優先處理某些作業。
或者說我們就是就是想要佇列裡面的作業有一個優先順序的概念,可供自由的調整。
這就要優先佇列來完成這些任務。
二叉堆
官方的定義:
『二叉堆』是一顆被完全填滿的二叉樹,底層上的元素從左到右填入,這也叫『完全二叉樹』
我的理解滿足以下條件的二叉樹
- 除葉子節點外所有節點都被填滿
- 底層上的元素從左往右填「右邊可能沒有元素」
一個重要的發現是,我們可以用陣列而不是指標來表示一個二叉堆的結構。
如果用陣列的下標 key 來表示節點位置,1 為 root 節點,那麼任意第 i 個節點,它的左兒子是 2*i
,右兒子是 2*i+1
PS:這個動手畫一下就理解了。
堆序
堆序就是始終要讓最小或者最大的關鍵字在根節點上『最小堆』、『最大堆』。
也就是:任意節點一定大於它的後裔。
結構體定義「陣列實現」,關鍵的兩個難點
- 插入元素,同時要維持堆序
- 彈出最大、最小值「root 節點」,找到最大、最小值到根節點
插入並維持堆序
定義 size
為當前陣列中元素個數「節點個數」。插入一個元素 X,首先 size + 1
,我們計劃用這個位置來放新插入的元素 X,因為 X 可能小可能大,所以我們要找到 X 在陣列「二叉堆」中合適的位置以維持「堆序」。
我們假設 X 填入 size + 1
的空位,從 size + 1
開始,比較 (size + 1) / 2
「父節點」與 X,X 小則 父子交換,一直重複直到 X 大於其中一個節點,或者 X 到 root 節點,這個過程叫『上溢』;此時完成插入。
刪除
刪除時候會讓 root 節點為空,我們需要在陣列中找出最小元素填入 root 節點。
root 節點被刪除了,這時就有個空位。將這個空位跟它的子節點中小的節點交換『下溢』,直到空位到最後,將最後的元素填入空位即完成刪除。
標頭檔案
typedef int element_type;
struct heap_struct;
typedef struct heap_struct *priority_queue;
priority_queue initialize(int max_elements);
void destroy(priority_queue h);
void insert(element_type x, priority_queue h);
element_type delete_min(priority_queue h);
element_type find_min(priority_queue h);
int is_empty(priority_queue h);
int is_full(priority_queue h);
void random_heap(int size, priority_queue h);
void print_heap(priority_queue h);
void test();
struct heap_struct {
int capacity;
int size;
element_type *elements;
};
heap.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "heap.h"
#define MIN_PQ_SIZE 5
#define error(str) fatal_error(str)
#define fatal_error(str) fprintf(stderr, "%s\n", str), exit(1)
priority_queue initialize(int max_elements)
{
priority_queue h;
if (max_elements < MIN_PQ_SIZE)
fatal_error("Prioity queue size is too small");
h = (priority_queue)malloc(sizeof(struct heap_struct));
if (NULL == h)
fatal_error("out of space");
h->elements = malloc(sizeof(element_type) * (max_elements + 1) );
if (NULL == h->elements)
fatal_error("out of space");
h->capacity = max_elements;
h->size = 0;
h->elements[1] = 0;
return h;
}
void destroy(priority_queue h)
{
if (NULL == h)
fatal_error("empty priority queue");
free(h->elements);
h->elements = NULL;
free(h);
h = NULL;
}
void insert(element_type x, priority_queue h)
{
int i;
if (is_full(h))
fatal_error("priority queue is full");
for (i = ++h->size; h->elements[ i / 2 ] > x; i /= 2)
h->elements[ i ] = h->elements[ i / 2 ];
h->elements[ i ] = x;
}
element_type delete_min(priority_queue h)
{
int i, child;
element_type min_ele, last_ele;
last_ele = h->elements[h->size--];
min_ele = h->elements[1];
for (i = 1; i * 2 <= h->size; i = child) {
child = i * 2; // 左兒子
if (child != h->size && h->elements[child+1] < h->elements[child]) // 左右兒子中取小
child = child + 1;
if (last_ele > h->elements[child]) // 為假的情況 child 為最後一個元素,否則違背堆序
h->elements[i] = h->elements[child]; // 下溢一個
else
break;
}
h->elements[i] = last_ele;
return min_ele;
}
element_type find_min(priority_queue h)
{
return h->elements[1];
}
int is_empty(priority_queue h)
{
return h->size == 0;
}
int is_full(priority_queue h)
{
return h->size == h->capacity;
}
void random_heap(int size, priority_queue h)
{
int i;
srand((unsigned)time(NULL));
for (i = 0; i < size; i++)
insert(rand() % 50, h);
}
void print_heap(priority_queue h)
{
int i;
printf("\t\t Array \n");
for (i = 1; i < h->size; i++)
printf("\t%d", h->elements[i]);
printf("\n");
}
void test()
{
priority_queue h;
h = initialize(10);
random_heap(8, h);
print_heap(h);
delete_min(h);
print_heap(h);
insert(3, h);
print_heap(h);
}
int main(int argc, char const *argv[])
{
test();
return 0;
}
- 優先佇列的定義
- 二叉堆的定義跟性質
- 二叉堆的插入跟刪除
- 最小堆的實現