1. 程式人生 > >作式堆分析和實現

作式堆分析和實現

介紹:

定義:

左式堆(Leftist Heaps)又稱作最左堆、左傾堆,是計算機語言中較為常用的一個資料結構。左式堆作為堆的一種,保留了堆的一些屬性。第1,左式堆仍然以二叉樹的形式構建;第2,左式堆的任意結點的值比其子樹任意結點值均小(最小堆的特性)。但和一般的二叉堆不同,左式堆不再是一棵完全二叉樹(Complete tree),而且是一棵極不平衡的樹。

那麼為什麼要使用左式堆,左式堆同樣是優先佇列,並且實現的時候使用了指標,不支援直接訪問元素。看起來效果與二叉堆相同,並且實現變得複雜了。使用指標,既可以是缺點,同樣也可以是優點。有了指標之後,就可以支援合併操作了。使用左式堆的目的就是為了能夠支援合併優先佇列。

左式堆的特性:

零路徑長:從X到一個不具有兩個兒子的結點的最短路徑的長。

1. 任一結點的零路徑長比他的諸兒子結點的零路徑長的最小值多1

2. 父節點屬性值小於子節點屬性值;

3. 堆中的任何節點,其左兒子的零路徑長>=右兒子的零路徑長的二叉樹。

左式堆的複雜度

       左式堆的操作都是基於合併,而合併僅對右路做合併,而右路結點的數量為總數量的對數關係,所以左式堆的三個操作(合併,刪除,插入)所花的時間為O(logN).

基本操作:
合併:

左式堆的合併操作基於遞迴完成,演算法如下:

1.如果有一棵樹是空樹,則返回另一棵樹;否則遞迴地合併根結點較小的堆的右子樹根結點較大的堆
。 2.使形成的新堆作為較小堆的右子樹。 3.如果違反了左式堆的特性,交換兩個子樹的位置。 4.更新Npl。

刪除最小值/最大值:

刪除操作的做法相當的簡單,刪除左式堆的根節點,合併左右子樹即可。

插入:

將需要插入的節點當做一棵左式堆樹,進行合併即可。

編碼實現:

左式堆定義:

左式堆的實現依賴指標,那麼首先是與基礎的二叉樹相同。因為需要維護Npl值,所以每個節點裡需要新增Npl。

.h檔案定義如下:

[cpp] view plain copy print?
  1. #ifndef _LEFTIST_HEAP
  2. #define _LEFTIST_HEAP
  3. struct
     TreeNode;  
  4. typedef TreeNode * PriorityQueue;  
  5. typedefint ElementType;  
  6. PriorityQueue merge(PriorityQueue H1, PriorityQueue H2);  
  7. ElementType findMin(PriorityQueue H);  
  8. int isEmpty(PriorityQueue H);  
  9. PriorityQueue deleteMin(PriorityQueue H);  
  10. PriorityQueue insert(ElementType X, PriorityQueue H);  
  11. void PrintTree(PriorityQueue T);  
  12. void PrintTree(PriorityQueue T, int depth);  
  13. void PrintDepth(ElementType A, int depth);  
  14. #endif
  15. struct TreeNode  
  16. {  
  17.     ElementType Element;  
  18.     PriorityQueue Left;  
  19.     PriorityQueue Right;  
  20.     int Npl;  
  21. };  
#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?
  1. PriorityQueue merge(PriorityQueue H1, PriorityQueue H2)  
  2. {  
  3.     if(H1 == NULL)  
  4.         return H2;  
  5.     if(H2 == NULL)  
  6.         return H1;  
  7.     if(H1->Element < H2->Element)  
  8.         return merge1(H1, H2);  
  9.     else
  10.         return merge1(H2, H1);  
  11. }  
  12. PriorityQueue merge1(PriorityQueue H1, PriorityQueue H2)  
  13. {  
  14.     if(H1->Left == NULL)  
  15.         H1->Left = H2;  
  16.     else
  17.     {  
  18.         H1->Right = merge(H1->Right, H2);  
  19.         if(H1->Left->Npl < H1->Right->Npl)  
  20.             switchChildren(H1);  
  21.         H1->Npl = H1->Right->Npl +1;  
  22.     }  
  23.     return H1;  
  24. }  
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;
}
插入:

在優先佇列裡插入是不檢查是否有重複資料的。

[cpp] view plain copy print?
  1. /*左式堆插入不檢查是否有重複的資料*/
  2. PriorityQueue insert(ElementType X, PriorityQueue H)  
  3. {  
  4.     PriorityQueue newone = (PriorityQueue)malloc(sizeof(TreeNode));  
  5.     newone->Element = X;  
  6.     newone->Left = newone->Right = NULL;  
  7.     newone ->Npl =0;  
  8.     H = merge(H, newone);  
  9.     return H;  
  10. }  
/*左式堆插入不檢查是否有重複的資料*/
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?
  1. PriorityQueue deleteMin(PriorityQueue H)  
  2. {  
  3.     if(H == NULL)  
  4.         return H;  
  5.     PriorityQueue Leftchild, Rightchild;  
  6.     Leftchild = H->Left;  
  7.     Rightchild = H->Right;  
  8.     free(H);  
  9.     return merge(Leftchild, Rightchild);  
  10. }  
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值,並且在每次合併之後互動左右孩子即可(不保證左式堆性質)。斜堆的實現與左式堆基本相同,在這裡就不贅述了。