作式堆分析和實現
定義:
左式堆(Leftist Heaps)又稱作最左堆、左傾堆,是計算機語言中較為常用的一個資料結構。左式堆作為堆的一種,保留了堆的一些屬性。第1,左式堆仍然以二叉樹的形式構建;第2,左式堆的任意結點的值比其子樹任意結點值均小(最小堆的特性)。但和一般的二叉堆不同,左式堆不再是一棵完全二叉樹(Complete tree),而且是一棵極不平衡的樹。
那麼為什麼要使用左式堆,左式堆同樣是優先佇列,並且實現的時候使用了指標,不支援直接訪問元素。看起來效果與二叉堆相同,並且實現變得複雜了。使用指標,既可以是缺點,同樣也可以是優點。有了指標之後,就可以支援合併操作了。使用左式堆的目的就是為了能夠支援合併優先佇列。
左式堆的特性:
零路徑長:從X到一個不具有兩個兒子的結點的最短路徑的長。
1. 任一結點的零路徑長比他的諸兒子結點的零路徑長的最小值多1
2. 父節點屬性值小於子節點屬性值;
3. 堆中的任何節點,其左兒子的零路徑長>=右兒子的零路徑長的二叉樹。
左式堆的複雜度
左式堆的操作都是基於合併,而合併僅對右路做合併,而右路結點的數量為總數量的對數關係,所以左式堆的三個操作(合併,刪除,插入)所花的時間為O(logN).
基本操作:
合併:
左式堆的合併操作基於遞迴完成,演算法如下:
1.如果有一棵樹是空樹,則返回另一棵樹;否則遞迴地合併根結點較小的堆的右子樹和根結點較大的堆刪除最小值/最大值:
刪除操作的做法相當的簡單,刪除左式堆的根節點,合併左右子樹即可。
插入:
將需要插入的節點當做一棵左式堆樹,進行合併即可。
編碼實現:
左式堆定義:
左式堆的實現依賴指標,那麼首先是與基礎的二叉樹相同。因為需要維護Npl值,所以每個節點裡需要新增Npl。
.h檔案定義如下:
[cpp] view plain copy print?- #ifndef _LEFTIST_HEAP
- #define _LEFTIST_HEAP
- struct
- typedef TreeNode * PriorityQueue;
- typedefint ElementType;
- PriorityQueue merge(PriorityQueue H1, PriorityQueue H2);
- ElementType findMin(PriorityQueue H);
- int isEmpty(PriorityQueue H);
- PriorityQueue deleteMin(PriorityQueue H);
- PriorityQueue insert(ElementType X, PriorityQueue H);
- void PrintTree(PriorityQueue T);
- void PrintTree(PriorityQueue T, int depth);
- void PrintDepth(ElementType A, int depth);
- #endif
- struct TreeNode
- {
- ElementType Element;
- PriorityQueue Left;
- PriorityQueue Right;
- int Npl;
- };
#ifndef _LEFTIST_HEAP
#define _LEFTIST_HEAP
struct TreeNode;
typedef TreeNode * PriorityQueue;
typedef int ElementType;
PriorityQueue merge(PriorityQueue H1, PriorityQueue H2);
ElementType findMin(PriorityQueue H);
int isEmpty(PriorityQueue H);
PriorityQueue deleteMin(PriorityQueue H);
PriorityQueue insert(ElementType X, PriorityQueue H);
void PrintTree(PriorityQueue T);
void PrintTree(PriorityQueue T, int depth);
void PrintDepth(ElementType A, int depth);
#endif
struct TreeNode
{
ElementType Element;
PriorityQueue Left;
PriorityQueue Right;
int Npl;
};
合併操作:
合併操作的基本方式就如上演算法所描述,可以使用遞迴的方式也可以使用非遞迴的方式。在這裡我使用遞迴的方式實現。
[cpp] view plain copy print?- PriorityQueue merge(PriorityQueue H1, PriorityQueue H2)
- {
- if(H1 == NULL)
- return H2;
- if(H2 == NULL)
- return H1;
- if(H1->Element < H2->Element)
- return merge1(H1, H2);
- else
- return merge1(H2, H1);
- }
- PriorityQueue merge1(PriorityQueue H1, PriorityQueue H2)
- {
- if(H1->Left == NULL)
- H1->Left = H2;
- else
- {
- H1->Right = merge(H1->Right, H2);
- if(H1->Left->Npl < H1->Right->Npl)
- switchChildren(H1);
- H1->Npl = H1->Right->Npl +1;
- }
- return H1;
- }
PriorityQueue merge(PriorityQueue H1, PriorityQueue H2)
{
if(H1 == NULL)
return H2;
if(H2 == NULL)
return H1;
if(H1->Element < H2->Element)
return merge1(H1, H2);
else
return merge1(H2, H1);
}
PriorityQueue merge1(PriorityQueue H1, PriorityQueue H2)
{
if(H1->Left == NULL)
H1->Left = H2;
else
{
H1->Right = merge(H1->Right, H2);
if(H1->Left->Npl < H1->Right->Npl)
switchChildren(H1);
H1->Npl = H1->Right->Npl +1;
}
return H1;
}
插入:
在優先佇列裡插入是不檢查是否有重複資料的。
- /*左式堆插入不檢查是否有重複的資料*/
- PriorityQueue insert(ElementType X, PriorityQueue H)
- {
- PriorityQueue newone = (PriorityQueue)malloc(sizeof(TreeNode));
- newone->Element = X;
- newone->Left = newone->Right = NULL;
- newone ->Npl =0;
- H = merge(H, newone);
- return H;
- }
/*左式堆插入不檢查是否有重複的資料*/
PriorityQueue insert(ElementType X, PriorityQueue H)
{
PriorityQueue newone = (PriorityQueue)malloc(sizeof(TreeNode));
newone->Element = X;
newone->Left = newone->Right = NULL;
newone ->Npl =0;
H = merge(H, newone);
return H;
}
刪除最小值:
如上所述,注意檢查是否為空樹即可。
[cpp] view plain copy print?- PriorityQueue deleteMin(PriorityQueue H)
- {
- if(H == NULL)
- return H;
- PriorityQueue Leftchild, Rightchild;
- Leftchild = H->Left;
- Rightchild = H->Right;
- free(H);
- return merge(Leftchild, Rightchild);
- }
PriorityQueue deleteMin(PriorityQueue H)
{
if(H == NULL)
return H;
PriorityQueue Leftchild, Rightchild;
Leftchild = H->Left;
Rightchild = H->Right;
free(H);
return merge(Leftchild, Rightchild);
}
測試截圖:
第一部分是插入0-20資料的左式堆,第二部分是進行了4次刪除最小值的左式堆。
總結:
左式堆的實現還是相當的容易,只有合併操作的地方需要稍微花點功夫理解就行了。在左式堆的基礎上還可以實現斜堆。只需要去除Npl值,並且在每次合併之後互動左右孩子即可(不保證左式堆性質)。斜堆的實現與左式堆基本相同,在這裡就不贅述了。