1. 程式人生 > >10、【堆】左傾堆

10、【堆】左傾堆

eap n) 節點 lin 中序 pat 後序 () new

一、左傾堆的介紹

左傾堆(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、【堆】左傾堆