資料結構 優先佇列 介面定義與實現分析

2022-07-05 06:39:07 字數 4374 閱讀 1684

顧名思義,優先佇列將資料按優先順序順序排列。乙個優先佇列由許多有序的元素構成,優先順序最高的元素可以有效而快速的確定。

例如,用來做負載均衡的伺服器,當連線請求到達時,優先佇列可以告知當前哪台伺服器是處理此連線請求最佳的伺服器。一般情況下,最空閒的伺服器獲取的優先順序最高,因為它可以最好地處理服務請求。

優先佇列的基本介面包含,1、初始化優先佇列;2、銷毀優先佇列;3、向優先佇列中插入乙個元素;4、提取優先佇列頂部的元素(釋放);5、獲取優先佇列中優先順序最高的元素(不釋放);6、統計優先佇列中元素的數量。

我們以名稱pqueue作為優先順序佇列的名稱,下面是各界面的定義:

pqueue_init

void pqueue_init(pqueue *pqueue, int (*compare)(const void *key1,const void *key2), void (*destroy)(void *data));

返回值:無

描述初始化優先佇列pqueue。

在對優先佇列進行其他操作之前必須先呼叫初始化函式。在優先佇列形成過程中,函式指標compare會被優先佇列的各種操作呼叫,用來維持優先佇列的堆特性。如果佇列中較大的值有較高的優先順序,那麼當key1>key2時,函式返回1;當key1=key2時,函式返回-1。如果相反,佇列中較小的值有較高的優先順序,那麼函式的返回結果相反。引數destroy是另乙個函式指標,通過呼叫pqueue_destroy來釋放動態分配的記憶體空間。例如,如果乙個優先佇列包含使用malloc動態分配記憶體的資料,那麼當銷毀佇列時,destroy會呼叫free來釋放記憶體空間。當乙個結構化資料報含若干動態分配記憶體的資料成員時,destroy應該指向乙個使用者自定義的析構函式來釋放資料成員和結構本身的記憶體空間。如果優先佇列中的資料不需要釋放,那麼destroy應指向null。

複雜度:o(1)

pqueue_destory

void pqueue_destroy(pqueue *pqueue);

返回值:無

描述銷毀優先佇列pqueue

在呼叫pqueue_destroy之後不再允許進行其他操作,除非再次呼叫pqueue_init。

pqueue_destory會從優先佇列中提取所有的元素,在刪除每個元素的同時呼叫pqueue_init中destroy所指向的銷毀函式(前提是此函式指標不為null)。

複雜度o(n),其中n是優先佇列中結點的個數。

pqueue_insert

int pqueue_insert(pqueue *pqueue, const void *data);

返回值:如果插入元素成功,返回0,否則返回-1。

描述:向優先佇列pqueue中插入乙個元素。新元素包含乙個指向data的指標,只要結點仍然存在於優先佇列中,此指標就一直有效。與data相關的記憶體空間由函式的呼叫者來管理。

複雜度:o(lg n),其中n是優先佇列中結點的個數。

pqueue_extract

int pqueue_extract(pqueue *pqueue, void **data);

返回值:如果元素提取成功返回0;否則返回-1。

描述:從優先佇列pqueue中提取優先佇列頂部的元素。返回時,data指向已提取元素中儲存的資料。與data有關的記憶體空間將由函式的呼叫者來管理。

複雜度:o(lg n),其中n是優先佇列中結點的個數。

pqueue_peek

void *pqueue_peek(const pqueue *pqueue);

返回值:優先佇列中優先順序最高的元素;如果隊列為空,那麼返回null。

描述:獲取優先佇列pqueue中優先順序最高元素的巨集。

複雜度:o(1)

pqueue_size

int pqueue_size(const pqueue *pqueue);

返回值:優先佇列中的結點個數。

描述:獲取優先佇列pqueue結點個數的巨集。

複雜度:o(1)

實現優先佇列最常用而簡單的方法就是維護乙個有序資料集。優先順序最高的元素位於資料集的頭部。然而,插入或提取元素之後必須重新排列資料集,這是乙個複雜度為o(n)的操作(n為資料集元素的個數)。

因此,更好的方法就是用乙個區域性有序的堆來實現優先佇列。位於堆頂部的結點優先順序最高,而且插入或提取資料之後重新排列堆的複雜度僅為o(lg n)。

優先佇列與堆的操作基本相同,優先佇列僅僅比堆多了乙個介面而已。因此,我們可以通過typedef heap pqueue這種簡單的方式來實現乙個優先佇列。

為了實現這些介面,我們只需要將優先佇列的相應操作定義成堆的操作就可以了。優先佇列中獨有的操作pqueue_peek與pqueue_extract相類似,只是pqueue_peek返回佇列中優先順序最高的元素而不刪除它。

示例:優先佇列的標頭檔案

/*

pqueue.h

*/#ifndef pqueue_h

#define pqueue_h#include

"heap.h"/*

將優先佇列作為堆實現

*/typedef heap pqueue;

/*優先佇列的公共介面

*/#define pqueue_init heap_init

#define pqueue_destroy heap_destroy

#define pqueue_insert heap_insert

#define pqueue_extract heap_extract

#define pqueue_peek(pqueue)((pqueue)->tree == null ? null : (pqueue)->tree[0])

#define pqueue_size heap_size

#endif

//pqueue_h

很多大型快遞公司每天要處理數以百萬計的包裹,將這些包裹按照優先順序排序是非常重要的。這在投遞的人力、物力有限的情況下尤為重要。在這種情況下,具有高優先順序的包裹往往優先投遞。例如,一架用於投遞服務的飛機,如果它每天只跑乙個往返,那麼那些要求第二天就要投遞的包裹在當天就應該將上飛機。

確保包裹能夠按照正確的優先順序順序送達指定目的地的方法是,將包裹的資訊按照正確的優先順序順序儲存在乙個優先佇列中。包裹分揀時,首先掃瞄包裹的資訊,並將資訊錄入系統中。對於每個掃瞄過的包裹,其資訊將會按照優先順序順序儲存到佇列中,以便包裹在系統中傳遞時,具有最高優先順序的包裹將首先傳遞。

示例列舉兩個函式get_parcel和put_parcel,它們都是用來操作乙個包含包裹資訊parcel的優先佇列。parcel在parcel.h中定義,此處不再列舉。乙個分揀器呼叫put_parcel將乙個包裹資訊載入到系統中。parcel中傳遞給put_parcel的乙個成員變數代表優先序號。put_parcel將乙個包裹插入乙個優先佇列中,並按照優先順序找到它在佇列中的位置。當分揀器準備在系統中傳遞下乙個包裹時,它會呼叫get_parcel。函式get_parcel會取到最高優先順序的包裹,這樣就能保證包裹按照正確的順序處理。

優先佇列是管理包裹的最佳方法,因為某些場合,我們只關心下乙個優先順序最高的包裹是哪乙個。這樣可以避免維護包裹完全有序的系統開銷。get_parcel和put_parcel的時間複雜度都是o(lg n),因為這兩個函式分別只呼叫了複雜度為o(lg n)的函式pqueue_extract和pqueue_insert。

示例:包裹分揀函式的示例

/*

parcels.c

*/#include

#include

#include

#include

#include

/*get_parcel 從優先佇列提取包裹

*/int get_parcel(pqueue *parcels,parcel *parcel)

}return0;

}/*put_parcel 將包裹插入優先佇列

*/int put_parcel(pqueue *parcels,const parcel *parcel)

資料結構與演算法 優先佇列

優先佇列按照佇列的方式正常入隊,但按照優先順序出隊。有兩種實現方式 堆 二插堆 多項式堆等等 和二叉搜尋樹。這裡重點講解二叉堆,關於二叉搜尋樹的內容見這篇文章。堆是一種特殊的完全二叉樹。大根堆 完全二叉樹的任一節點都比其孩子節點大。小根堆 完全二叉樹的任一節點都比其孩子節點小。堆的向下調整 假設根節...

資料結構與演算法 優先佇列

英雄聯盟遊戲裡面防禦塔都有乙個自動攻擊功能,小兵排著隊進入防禦塔的攻擊範圍,防禦塔先攻擊靠得最近的小兵,這時候大炮車的優先順序更高 因為系統判定大炮車對於防禦塔的威脅更大 所以防禦塔會優先攻擊大炮車。而當大炮車陣亡,剩下的全部都是普通小兵,這時候離得近的優先順序越高,防禦塔優先攻擊距離更近的小兵。優...

資料結構 優先佇列

優先佇列是允許至少下列兩種操作的資料結構 insert 插入 deletermin 刪除最小者 它的工作室找出 返回 刪除優先佇列最小的元素。插入操作等於enqueue 入隊 而detemin則是佇列中dequeue 出隊 在優先佇列中的等價操作。determin函式也變更它的輸入。二叉堆 結構性 ...