Leftist Heap & Skew Heap
约 1151 个字 42 行代码 预计阅读时间 6 分钟
Leftist Heap¶
Definition¶
左偏堆,亦称左偏树(Leftist Tree),是一种用于实现优先级队列的 Binary Heap 数据结构,它具有和堆一样的大小顺序,但表现形式为非平衡二叉树,因此不能使用数组表示和维护。
左偏堆的关键特征之一是计算和维护每个节点的 Null Path Length ,该长度定义为从节点 X 到最近的 Null 节点的距离,在程序结构中通常使用 dist(X)
或 Npl(X)
表示
- 定义
Npl(NULL) = -1
,如果一个节点有 Null 子节点,则其 Npl 为 0 Npl(X) = Min{Npl(Left Child),Npl(Right Child)}+1
对于左偏堆任一节点,都有 \(Npl(Left\ Child)\ge Npl(Right\ Child)\) ,并能得到以下定理:
- <1> 从根到最右侧叶的路径是从根到叶的最短距离(这里采用拓展二叉树的形式,即叶节点为 Null 节点)
- <2> 如果到最右侧路径上有 \(r\) 个节点,则左偏堆起码有 \(2^r-1\) 个节点,这也意味着对于具有 \(N\) 个节点的左偏堆,到最右侧叶节点的路径长度为 \(O(\log N)\)
路径长度受限是减少维护代价的基础
根据以上定义,左偏堆的声明通常如下:
Operation¶
对左偏堆的操作主要是 Merge ,其它操作都可以通过 Merge 来调整。
DeleteMin()
或ExtractMin
可以通过删除 root 并为左右子树调用 Merge 来完成Insert()
可以通过 Merge 原来的左偏堆以及单个节点的树来完成
Function | Complexity |
---|---|
Get Min | \(O(1)\) |
Delete Min | \(O(\log N)\) |
Insert | \(O(\log N)\) |
Merge | \(O(\log N)\) |
由于右子树较小,因此 Merge 的通常想法是将右子树与其它树再进行 Merge。以下是其抽象步骤:
- 将值较小的根作为新根,其 Left 子树不变
- 递归合并其 Right 子树和另一棵树
- 递归返回之前,自下而上更新各个 Merge 操作的根的 Npl;如果不符合左偏堆性质,则交换左右子树
从修佬笔记抄来的递归实现方式:
课件里的递归实现方式:
迭代实现则需要使用栈结构
相比于其它 Binary Heap ,Leftist Heap 的优缺点如下:
Advantage of Leftist Heap:
DeleteMin
和Merge
操作时间复杂度为 \(O(\log N)\) ,是此操作最有效的数据结构之一- 相对于其它 Binary Heap(如斐波那契堆),Letist Heap 的实现更加简单
Disadvantage of Leftist Heap:
Insert
操作时间复杂度为 \(O(\log N)\) ,比其它 Binary Heap 慢(如二叉堆通过插入建堆方式耗时 \(O(N)\),则均摊操作消耗为 \(O(1)\))- Leftist Heap 使用了更多空间,因为它需要维护空路径长度的值
Skew Heap¶
Definition¶
斜堆(Skew Heap)是左偏堆的简单形式,具有和左偏堆类似的性质,但又有所不同。
其特点是在进行 Merge 操作时,无条件进行左右子树的 Swap,这样就不需要在递归调用结束时额外判断左右子树的 Npl 是否符合要求。
均摊分析¶
对 Skew Heap 的 Merge 操作进行均摊分析,定义势能函数 \(\phi(D_i)\) 为以 \(D_i\) 为根节点的树的 Heavy Nodes 的数量,其中 Heavy Nodes 定义为右子树的节点数大于等于左子树的节点数的 Node。
那么对于 Heavy Nodes 以及相对的 Light Nodes,有如下性质:
- 任意不在右路径的节点,其在归并前后左右子树不变化,即轻重情况也不变
- 右路径上所有重节点在归并后全部变为轻节点,而轻节点不一定变为重节点
根据以上性质可以进行 Merge 操作的均摊分析。
由于非右路径节点不参与轻重变化,所以可以忽略它们的数量。以 \(l_1,h _1\) 分别指代第一颗树右路径上轻节点和重节点的数量,\(l,h\) 分别指代其它轻重节点,则最坏情况下,假设右路径上所有轻节点都变为重节点:
又右路径长度受限于 \(O(\log N)\) ,则 \(T_{amortized} = O(\log N)\)