『演算法與資料結構』優先佇列 二叉堆

jerrkill發表於2019-02-21

有段時間沒看演算法跟資料結構了,今天看了『優先佇列』,並寫成筆記。

為什麼需要優先佇列?

在一個佇列中的作業,有的時候我們需要優先處理某些作業。
或者說我們就是就是想要佇列裡面的作業有一個優先順序的概念,可供自由的調整。

這就要優先佇列來完成這些任務。

二叉堆

官方的定義:

『二叉堆』是一顆被完全填滿的二叉樹,底層上的元素從左到右填入,這也叫『完全二叉樹』

我的理解滿足以下條件的二叉樹

  • 除葉子節點外所有節點都被填滿
  • 底層上的元素從左往右填「右邊可能沒有元素」

一個重要的發現是,我們可以用陣列而不是指標來表示一個二叉堆的結構。
如果用陣列的下標 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;
}

file

  • 優先佇列的定義
  • 二叉堆的定義跟性質
  • 二叉堆的插入跟刪除
  • 最小堆的實現

高度自律,深度思考,以勤補拙

相關文章