一、左倾堆的介绍
左倾堆(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()是外部接口,它的作用是删除左倾堆的最小节点。
原文地址:https://www.cnblogs.com/Long-w/p/9786173.html