lec4|Leftist Heaps & Skew Heaps¶
约 919 个字 33 行代码 8 张图片 预计阅读时间 3 分钟
复杂度
-
Leftist Heap:Insert、DeleteMin、Merge在worst case和average case下均是\(O(\text{logN})\)
-
Skew Heap:Insert、DeleteMin、Merge在worst case下为\(O(N)\);average case下是\(O(\text{logN})\)
Leftist Heaps¶
-
左斜堆,或者说左偏堆(Leftist Heap),它相比于普通的堆,更好的一点在于它支持快速的堆合并操作。“左偏”,并不断将新的东西往右侧合并,来实现每次都是往相对小的那一侧塞进东西,进而相对保证了这个
-
由于左偏堆不再是一个完全二叉树,所以我们不能再像维护大根堆小跟堆那样用数组来维护它了。
-
一个左偏堆的节点维护了左右子堆的地址、自身的键值、和一个“距离(NPL)”。
Null Path Length
-
NPL就是到没有两个child的节点的最短距离
-
NULL节点的Npl是-1
-
如果一个节点的左孩子或右孩子为空节点,则该节点的 Npl 为 0,这种节点被称为外节点;
-
如果一个节点的左孩子和右孩子都不为空,则该节点的 Npl 为 \(Npl = \text{min}(Npl_{\text{left child}},Npl_{\text{right child}})+1\)
- 左偏堆首先满足即堆的性质(最大堆或最小堆),其次满足「左偏」性质——节点的左孩子的 Npl 不小于右孩子的 Npl。
Properties
-
节点的 \(Npl = Npl_{\text{right child}}+1\)
-
如果 \(Npl_i = N\),那么以
i
为根的子树至少是一个\(N+1\)的完美二叉树,至少有\(2^{N+1}-1\)个节点
- 左偏堆通过维护整个堆“左偏”,并不断往右侧合并,来实现每次都是往
Npl
相对小的那一侧塞进东西,进而保证了这个堆的相对平衡性。
Merge¶
- 作为左偏堆的核心操作,合并操作自然就是要在满足性质的条件下,合并两个左偏堆。大致思路就是先维护堆的性质,在回溯时维护左偏性质,所以实际上它是一个先自上而下再自下而上的过程。
递归式
- 递归式先比较当前两个待合并子树的根结点的键值,选择较小(较大)的那个作为根结点,其左子树依然为左子树,右子树更新为「右子树和另一个待合并子树的合并结果」。
LeftistHeapNode* merge(LeftistHeapNode *x, LeftistHeapNode *y){
// merge时一方为NULL,则说明仅剩下对方,然后递归停止
if(x == NULL) return y;
if(y == NULL) return x;
// 选取 较小 的作为主根
if(x->val > y->val)
swap(x,y);
// 更新的右子树就是 原右子树和对方 merge后的结果
x->Right = merge(x->Right, y);
// 如果左子树为空 或者 Npl小于右子树 就swap
if(x->Left = NULL || x->Left->Npl < x->Right->Npl)
swap(x->Left, x->Right);
// 更新 Npl
x->Npl = x->Right->Npl + 1;
// x是主根,所以return x
return x;
}
Skew Heaps¶
-
左偏堆的一个简单版本
-
先交换左右子树,然后在左子树上递归merge,直到最后的节点Npl为0 -> 不交换,停止
摊还分析¶
heavy node & light node
-
对于一个子堆H来说,如果 size(H.right_descendant) >= \(\frac{1}{2}\)size(H), 则H是 heavy node,否则是light node
-
即至少一半的子节点都在右支,则为重节点
- 下面是神奇的势能函数定义
-
然后我们就会发现一些性质
-
如果一个节点是 heavy node,并且在其右子树发生了合并(包括翻转),那么它一定变为一个 light node;
-
如果一个节点是 light node,并且在其右子树发生了合并(包括翻转),那么它可能变为一个 heavy node;
-
合并过程中,如果一个节点的 heavy/light属性 发生变化,那么它原先一定在堆的最右侧路径上;
-