資料結構與演算法分析 (優先佇列)
標籤: Data-Structures
摘要:本節包括二叉堆,左式堆,斜堆和二項佇列幾種資料結構
1. 二叉堆
優先佇列至少支援下列2種操作:Insert,DeleteMIn(DeleteMax)。 優先佇列的實現:
- 連結串列
- 二叉查詢樹
- 二叉堆的陣列實現 堆是完全二叉樹,它的堆序性
優先佇列的宣告:
#ifndef BINHEAP_BINHEAP_H
#define BINHEAP_BINHEAP_H
struct HeapStruct;
typedef struct HeapStruct *PriorityQueue;
PriorityQueue Init(int MaxElements);
void Destroy(PriorityQueue H);
void MakeEmpty(PriorityQueue H);
void Insert(int X, PriorityQueue H);
int DeleteMin(PriorityQueue H);
int FindMin(PriorityQueue H);
int IsEmpty(PriorityQueue H);
int IsFull(PriorityQueue H);
#endif //BINHEAP_BINHEAP_H
struct HeapStruct{
int Capactiy;
int Size;
int *Elems;
};
初始化以及銷燬:
PriorityQueue Init(int MaxElements){
PriorityQueue H;
H = malloc(sizeof(struct HeapStruct));
H->Elems = malloc(sizeof(int)* (MaxElements + 1));
H->Size = 0;
H->Capactiy = MaxElements;
H->Elems[0] = -9999;
return H;
}
void Destroy(PriorityQueue H){
free(H->Elems);
free(H);
}
插入:
陣列大小加1後。在從最後一個元素的父節點回溯到根節點的過程中,比較節點和要插入元素X的大小,如果X小,則將父節點的值轉移到子節點中,直到遇到X比父節點值大的時候才退出迴圈,將X直接放入當前節點中。
void Insert(int X, PriorityQueue H){
int i;
if (IsFull(H)){
return;
}
for(i = ++H->Size;H->Elems[i/2] > X; i /=2){
H->Elems[i] = H->Elems[i/2];
}
H->Elems[i] = X;
H->Elems[0] = H->Elems[1];
}
刪除最小元:
最小堆的最小元是根,直接刪除根。比較根的較小子節點和最後一個元素的大小,如果根的子節點小,則移動到父節點中,否則退出迴圈。
int DeleteMin(PriorityQueue H){
int i,child;
if (IsEmpty(H))
return H->Elems[0];
int X = H->Elems[H->Size--];
int Min = H->Elems[1];
for(i = 1; i * 2 <= H->Size; i = child){
child = i * 2;
if (child != H->Size && H->Elems[child + 1] < H->Elems[child]){
child ++;
}
if (H->Elems[child] < X)
H->Elems[i] = H->Elems[child];
else
break;
}
H->Elems[i] = X;
return Min;
}
構建二叉堆-BUildHeap
以O(N)時間複雜度構建一個最小堆。
PriorityQueue BuildHeap(int *A, int N){
PriorityQueue H;
H = malloc(sizeof(struct HeapStruct));
H->Size = N;
H->Capactiy = N;
H->Elems = malloc(sizeof(int) * (N + 1));
int i;
for( i = N/2; i > 0; i--){
PercolateDown(i,A,N);
}
for (int j = 1; j <= N; ++j) {
H->Elems[j] = A[j];
}
return H;
}
一個關鍵步驟即節點下濾(PercolateDewn) 主要是調整當前下標為index的節點的子樹的堆序性。第一步是找到較小子節點,第二步比較父節點和子節點的大小,如果不滿足堆序性,交換,重複直到滿足堆序性
void PercolateDown(int index, int *A,int N){
int child;
int t;
for( int i = index; i*2<=N; i = child){
child = i * 2;
if (child != N && A[child] > A[child + 1])
child++;
if(A[child] < A[i]){
t = A[i];
A[i] = A[child];
A[child] = t;
}
}
}
2. 左式堆
左式堆建立在二叉樹的基礎上,不同於二叉堆, 左式堆不是完全二叉樹,而且極不平衡。
性質: 零路徑長(NPL):從X到一個沒有左右兒子的節點的最短路徑的長
- 任一節點的NPL比所有兒子節點的NPL的最小值多1
- 最小堆的特性:父節點屬性值小於子節點屬性值
- 堆中任一節點的左兒子的NPL >= 右兒子的NPL
左式堆節點結構
struct TreeNode{
int Elem;
PriorityQueue Left;
PriorityQueue Right;
int Npl;
};
左式堆的基本操作是合併,O(logN)
- 如果有一個堆是空樹,直接返回另一個堆,否則根節點值大的堆和根節點值小的右子樹遞迴合併,形成新的左式堆
- 合併後的新堆作為較小堆的右子樹
- 如果堆根節點的NPL不符合左式堆的特性,交換左右子樹
更新根節點的NPL
PriorityQueue Merge(PriorityQueue H1, PriorityQueue H2){ if (H1 == NULL) return H2; if (H2 == NULL) return H1; if (H1->Elem < H2->Elem) return Merge1(H1,H2); else return Merge1(H2,H1); } PriorityQueue Merge1(PriorityQueue H1, PriorityQueue H2){ if (H1->Left == NULL)// H1是單節點 H1->Left = H2; else{ H1->Right = Merge(H1->Right,H2); if (H1->Left->Npl < H1->Right->Npl) SwapChildren(H1); H1->Npl = H1->Right->Npl + 1; } return H1; }
插入操作可以看成單節點堆和另一個堆合併的情況,O(logN)
PriorityQueue Insert(int X, PriorityQueue H){
Node P;
P = malloc(sizeof(struct TreeNode));
P->Elem = X;
P->Left = P->Right = NULL;
P->Npl = 0;
H = Merge(P,H);
return H;
}
刪除最小數操作直接刪除根節點,將左右子樹合併形成新的左式堆,O(logN)
當全部刪除後,會以從小到大的順序排列元素,可以作為一種排序方法
PriorityQueue DeleteMin(PriorityQueue H){
PriorityQueue LeftHeap,RightHeap;
if (IsEmpty(H))
return NULL;
else{
LeftHeap = H->Left;
RightHeap = H->Right;
free(H);
return Merge(LeftHeap,RightHeap);
}
}
其它操作(上濾,增加或降低關鍵字值)
void PercolateUp(int index, int *A){
int parent;
for (int i = index; i / 2>0 ; i /=2) {
parent = i/2;
if (A[parent] > A[index]){
int t = A[parent];
A[parent] = A[index];
A[index] = t;
}
else
break;
}
}
void DecreaseKey(int index, int Incre, int *A){
A[index] -=Incre;
PercolateUp(index,A);
}
void IncreaseKey(int index, int Incre, int *A,int N){
A[index] +=Incre;
PercolateDown(index,A,N);
}
3. 斜堆
斜堆和左式堆類似於伸展樹和AVL樹,後者都需要在節點中儲存額外的資訊如高度和零路徑
斜堆基本操作也是合併
- 如果一個是空堆,直接返回一個另一個斜堆
- 兩個斜堆都非空,那麼比較兩個根節點,取較小堆的根節點作為新的根節點,較小堆根節點的右子樹與較大堆遞迴合併
- 交換根的左右子樹
與左式堆的合併稍有不同的是第3步
- 如果堆根節點的NPL不符合左式堆的特性,交換左右子樹
- 更新根節點的NPL
4. 二項佇列
二項佇列是二項樹的集合。二項樹Bk由一個帶有兒子B0,B1...Bk-1 的根組成
二項佇列操作
二項佇列結構
struct BinNode{
int Elem;
Position LeftChild;
Position NextSibling;
};
struct Collection{
int CurrentSize;
BinTree TheTrees[MaxSize];
};
查詢最小元
通過搜尋所有的樹根,O(logN)找到
合併
二項佇列按照高度給二項樹排序放在陣列中。
合併相同大小的二項樹例程
BinTree CombineTrees(BinTree T1,BinTree T2){
if(T1->Elem > T2->Elem)
return CombineTrees(T2,T1);
T2->NextSibling = T1->LeftChild;
T1->LeftChild = T2;
return T1;
}
二項佇列的合併類似於加法進位,有8種情況考慮,T1,T2以及Carry分別是二項佇列H1和H2的樹,上一次合併形成的樹。對應的樹合併後放入H1中,清空H2
BinQueue Merge(BinQueue H1, BinQueue H2){
BinTree T1,T2,Carry = NULL;
int i,j;
H1->CurrentSize += H2->CurrentSize;
for (int i = 0,j=1; j < H1->CurrentSize; ++i,j*=2) {
T1 = H1->TheTrees[i];
T2 = H2->TheTrees[i];
switch (!!T1 + 2*!!T2 + 4*!!Carry){
case 0://沒有樹
case 1://只H1存在
break;
case 2://只H2存在
H1->TheTrees[i] = H2;
H2->TheTrees[i] = NULL;
break;
case 3://H1和H2存在
Carry = CombineTrees(T1,T2);
H1->TheTrees[i] = H2->TheTrees[i] = NULL;
break;
case 4://H1和H2不存在,Carry存在
H1->TheTrees[i] = Carry;
Carry = NULL;
break;
case 5://H1和Carray
Carry = CombineTrees(T1,Carry);
H1->TheTrees[i] = NULL;
break;
case 6://H2和Carry
Carry = CombineTrees(T2,Carry);
H2->TheTrees[i] = NULL;
break;
case 7://H1,H2和Carry都存在
H1->TheTrees[i] = Carry;
Carry = CombineTrees(T1,T2);
H2->TheTrees[i] = NULL;
break;
}
}
return H1;
}
插入
插入是特殊情況的合併,建立單節點樹,執行合併
BinQueue Insert(int Item, BinQueue H) {
BinTree NewNode; //二項樹B0
BinQueue OneItem; //只有B0的二項佇列
NewNode = malloc(sizeof(struct BinNode));
NewNode->Elem = Item;
NewNode->LeftChild = NewNode->NextSibling = NULL;
OneItem = Init();
OneItem->CurrentSize = 1;
OneItem->TheTrees[0] = NewNode;
return Merge(H, OneItem); //合併單節點的二項樹構成的二項佇列與H
}
刪除最小元
首先確定最小根的下標。從原二項佇列中刪除當前二項樹稱H,二項樹刪除最小元后形成新的二項佇列DeletedQueue,合併原二項佇列和新的二項佇列
int DeleteMin(BinQueue H){
int i,j;
int index;// array index;
BinQueue DeletedQueue;
Position DeletedTree, OldToot;
int Min;
Min = 9999;
for (int k = 0; k < 10; ++k) {
if(H->TheTrees[k] && H->TheTrees[k]->Elem < Min){
index = k;
Min = H->TheTrees[k]->Elem;
}
}
DeletedTree = H->TheTrees[index];//指向當前最小根節點二項樹
OldToot = DeletedTree;
DeletedTree = DeletedTree->LeftChild;
free(OldToot);
DeletedQueue = Init();
DeletedQueue->CurrentSize = (1<<index) - 1;
for(j = index - 1; j >= 0; j--){
DeletedQueue->TheTrees[j] = DeletedTree;
DeletedTree = DeletedTree->NextSibling;
DeletedQueue->TheTrees[j]->NextSibling = NULL;
}
H->TheTrees[index] = NULL;
H->CurrentSize -=DeletedQueue->CurrentSize + 1;
Merge(H,DeletedQueue);
return Min;
}
總結
優先佇列有幾種實現,標準的二叉堆,以及左式堆,斜堆和二項佇列。在實現上不同,二叉堆利用了陣列的特性,後三者通過指標。左式堆和斜堆類似於伸展樹和AVL樹,都是遞迴的完美例項,需要仔細咀嚼精華。
相關文章
- 演算法與資料結構番外(1):優先佇列演算法資料結構佇列
- 三、資料結構演算法-棧、佇列、優先佇列、雙端佇列資料結構演算法佇列
- 『演算法與資料結構』優先佇列 二叉堆演算法資料結構佇列
- 資料結構與演算法分析——佇列資料結構演算法佇列
- 《資料結構與演算法分析》學習筆記-第六章-優先佇列資料結構演算法筆記佇列
- 資料結構與演算法-佇列資料結構演算法佇列
- 資料結構與演算法——佇列(環形佇列)資料結構演算法佇列
- 資料結構與演算法-棧與佇列資料結構演算法佇列
- javascript資料結構與演算法-佇列JavaScript資料結構演算法佇列
- python資料結構與演算法——棧、佇列與雙端佇列Python資料結構演算法佇列
- 資料結構之PHP(最大堆)實現優先佇列資料結構PHP佇列
- 佇列 ADT 【資料結構與演算法分析 c 語言描述】佇列資料結構演算法
- python演算法與資料結構-佇列(44)Python演算法資料結構佇列
- 資料結構與演算法—稀疏陣列和佇列資料結構演算法陣列佇列
- 資料結構-棧與佇列資料結構佇列
- 《資料結構與演算法》——表、棧和佇列資料結構演算法佇列
- 堆與優先佇列佇列
- 資料結構與演算法(二)佇列、棧、連結串列資料結構演算法佇列
- 02Javascript資料結構與演算法 之 佇列JavaScript資料結構演算法佇列
- IT名企演算法與資料結構題目最優解--棧和佇列演算法資料結構佇列
- 資料結構和演算法(六)佇列資料結構演算法佇列
- 資料結構-佇列資料結構佇列
- 【資料結構-----佇列】資料結構佇列
- 資料結構 - 佇列資料結構佇列
- Java版-資料結構-佇列(陣列佇列)Java資料結構佇列陣列
- 資料結構之「佇列」資料結構佇列
- 資料結構-佇列-樹資料結構佇列
- 資料結構-佇列、棧資料結構佇列
- 結構與演算法(02):佇列和棧結構演算法佇列
- 《JavaScript資料結構與演算法》筆記——第4章 佇列JavaScript資料結構演算法筆記佇列
- Python資料結構與演算法系列四:棧和佇列Python資料結構演算法佇列
- 前端學習 資料結構與演算法 快速入門 系列 —— 佇列和雙端佇列前端資料結構演算法佇列
- 演算法面試(三) 優先佇列演算法面試佇列
- Java版-資料結構-佇列(迴圈佇列)Java資料結構佇列
- PHP優先佇列PHP佇列
- 關於樹的資料結構(二分搜尋樹,堆和優先佇列)資料結構佇列
- C#資料結構與演算法2-C# 棧和佇列C#資料結構演算法佇列
- .NET 6 優先佇列 PriorityQueue 實現分析佇列
- Java優先順序佇列DelayedWorkQueue原理分析Java佇列