10、【堆】左傾堆
一、左傾堆的介紹
左傾堆(leftist tree 或 leftist heap),又被成為左偏樹、左偏堆,最左堆等。
它和二叉堆一樣,都是優先隊列實現方式。當優先隊列中涉及到"對兩個優先隊列進行合並"的問題時,二叉堆的效率就無法令人滿意了,而本文介紹的左傾堆,則可以很好地解決這類問題。
左傾堆的定義
左傾堆是一棵二叉樹,它的節點除了和二叉樹的節點一樣具有左右子樹指針外,還有兩個屬性:鍵值和零距離。
(01) 鍵值的作用是來比較節點的大小,從而對節點進行排序。
(02) 零距離(英文名NPL,即Null Path Length)則是從一個節點到一個"最近的不滿節點"的路徑長度。不滿節點是指該該節點的左右孩子至少有有一個為NULL。葉節點的NPL為0,NULL節點的NPL為-1。
左傾堆的基本性質:
[性質1] 節點的鍵值小於或等於它的左右子節點的鍵值。
[性質2] 節點的左孩子的NPL >= 右孩子的NPL。
[性質3] 節點的NPL = 它的右孩子的NPL + 1。
左傾堆,顧名思義,是有點向左傾斜的意思了。它在統計問題、最值問題、模擬問題和貪心問題等問題中有著廣泛的應用。此外,斜堆是比左傾堆更為一般的數據結構。當然,今天討論的是左傾堆,關於斜堆,以後再撰文來表。
前面說過,它能和好的解決"兩個優先隊列合並"的問題。實際上,左傾堆的合並操作的平攤時間復雜度為O(lg
n),而完全二叉堆為O(n)。合並就是左傾樹的重點,插入和刪除操作都是以合並操作為基礎的。插入操作,可以看作兩顆左傾樹合並;刪除操作(移除優先隊列中隊首元素),則是移除根節點之後再合並剩余的兩個左傾樹。
二、左傾堆解析
1. 左傾堆基本定義
1 template <class T> 2 class LeftistNode{ 3 public: 4 T key; // 關鍵字(鍵值) 5 int npl; // 零路經長度(Null Path Length) 6 LeftistNode *left; // 左孩子 7 LeftistNode *right; // 右孩子 8 9 LeftistNode(T value, LeftistNode *l, LeftistNode *r):10 key(value), npl(0), left(l),right(r) {} 11 };
LeftistNode是左傾堆對應的節點類。
1 template <class T> 2 class LeftistHeap { 3 private: 4 LeftistNode<T> *mRoot; // 根結點 5 6 public: 7 LeftistHeap(); 8 ~LeftistHeap(); 9 10 // 前序遍歷"左傾堆" 11 void preOrder(); 12 // 中序遍歷"左傾堆" 13 void inOrder(); 14 // 後序遍歷"左傾堆" 15 void postOrder(); 16 17 // 將other的左傾堆合並到this中。 18 void merge(LeftistHeap<T>* other); 19 // 將結點(key為節點鍵值)插入到左傾堆中 20 void insert(T key); 21 // 刪除結點(key為節點鍵值) 22 void remove(); 23 24 // 銷毀左傾堆 25 void destroy(); 26 27 // 打印左傾堆 28 void print(); 29 private: 30 31 // 前序遍歷"左傾堆" 32 void preOrder(LeftistNode<T>* heap) const; 33 // 中序遍歷"左傾堆" 34 void inOrder(LeftistNode<T>* heap) const; 35 // 後序遍歷"左傾堆" 36 void postOrder(LeftistNode<T>* heap) const; 37 38 // 交換節點x和節點y 39 void swapNode(LeftistNode<T> *&x, LeftistNode<T> *&y); 40 // 合並"左傾堆x"和"左傾堆y" 41 LeftistNode<T>* merge(LeftistNode<T>* &x, LeftistNode<T>* &y); 42 // 將結點(z)插入到左傾堆(heap)中 43 LeftistNode<T>* insert(LeftistNode<T>* &heap, T key); 44 // 刪除左傾堆(heap)中的結點(z),並返回被刪除的結點 45 LeftistNode<T>* remove(LeftistNode<T>* &heap); 46 47 // 銷毀左傾堆 48 void destroy(LeftistNode<T>* &heap); 49 50 // 打印左傾堆 51 void print(LeftistNode<T>* heap, T key, int direction); 52 };
LeftistHeap是左傾堆類,它包含了左傾堆的根節點,以及左傾堆的操作。
2. 合並
合並操作是左傾堆的重點。合並兩個左傾堆的基本思想如下:
(1) 如果一個空左傾堆與一個非空左傾堆合並,返回非空左傾堆。
(2) 如果兩個左傾堆都非空,那麽比較兩個根節點,取較小堆的根節點為新的根節點。將"較小堆的根節點的右孩子"和"較大堆"進行合並。
(3) 如果新堆的右孩子的NPL > 左孩子的NPL,則交換左右孩子。
(4) 設置新堆的根節點的NPL = 右子堆NPL + 1
1 /* 2 * 合並"左傾堆x"和"左傾堆y" 3 */ 4 template <class T> 5 LeftistNode<T>* LeftistHeap<T>::merge(LeftistNode<T>* &x, LeftistNode<T>* &y) 6 { 7 if(x == NULL) 8 return y; 9 if(y == NULL) 10 return x; 11 12 // 合並x和y時,將x作為合並後的樹的根; 13 // 這裏的操作是保證: x的key < y的key 14 if(x->key > y->key) 15 swapNode(x, y); 16 17 // 將x的右孩子和y合並,"合並後的樹的根"是x的右孩子。 18 x->right = merge(x->right, y); 19 20 // 如果"x的左孩子為空" 或者 "x的左孩子的npl<右孩子的npl" 21 // 則,交換x和y 22 if(x->left == NULL || x->left->npl < x->right->npl) 23 { 24 LeftistNode<T> *tmp = x->left; 25 x->left = x->right; 26 x->right = tmp; 27 } 28 // 設置合並後的新樹(x)的npl 29 if (x->right == NULL || x->left == NULL) 30 x->npl = 0; 31 else 32 x->npl = (x->left->npl > x->right->npl) ? (x->right->npl + 1) : (x->left->npl + 1); 33 34 return x; 35 } 36 37 /* 38 * 將other的左傾堆合並到this中。 39 */ 40 template <class T> 41 void LeftistHeap<T>::merge(LeftistHeap<T>* other) 42 { 43 mRoot = merge(mRoot, other->mRoot); 44 }
merge(x, y)是內部接口,作用是合並x和y這兩個左傾堆,並返回得到的新堆的根節點。
merge(other)是外部接口,作用是將other合並到當前堆中。
3. 添加
1 /* 2 * 將結點插入到左傾堆中,並返回根節點 3 * 4 * 參數說明: 5 * heap 左傾堆的根結點 6 * key 插入的結點的鍵值 7 * 返回值: 8 * 根節點 9 */ 10 template <class T> 11 LeftistNode<T>* LeftistHeap<T>::insert(LeftistNode<T>* &heap, T key) 12 { 13 LeftistNode<T> *node; // 新建結點 14 15 // 新建節點 16 node = new LeftistNode<T>(key, NULL, NULL); 17 if (node==NULL) 18 { 19 cout << "ERROR: create node failed!" << endl; 20 return heap; 21 } 22 23 return merge(mRoot, node); 24 } 25 26 template <class T> 27 void LeftistHeap<T>::insert(T key) 28 { 29 mRoot = insert(mRoot, key); 30 }
insert(heap, key)是內部接口,它是以節點為操作對象的。
insert(key)是外部接口,它的作用是新建鍵值為key的節點,並將其加入到當前左傾堆中。
4. 刪除
1 /* 2 * 刪除結點,返回根節點 3 * 4 * 參數說明: 5 * heap 左傾堆的根結點 6 * 返回值: 7 * 根節點 8 */ 9 template <class T> 10 LeftistNode<T>* LeftistHeap<T>::remove(LeftistNode<T>* &heap) 11 { 12 if (heap == NULL) 13 return NULL; 14 15 LeftistNode<T> *l = heap->left; 16 LeftistNode<T> *r = heap->right; 17 18 // 刪除根節點 19 delete heap; 20 21 return merge(l, r); // 返回左右子樹合並後的新樹 22 } 23 24 template <class T> 25 void LeftistHeap<T>::remove() 26 { 27 mRoot = remove(mRoot); 28 }
remove(heap)是內部接口,它是以節點為操作對象的。
remove()是外部接口,它的作用是刪除左傾堆的最小節點。
10、【堆】左傾堆