左偏树
左偏树
左偏树是一种可并堆,能够在 \(O(log n)\) 的时间复杂度内实现。
性质
- 满足二叉堆的性质。
- 左子树的 \(dis\) 值大于右子树的 \(dis\) 值。
\(dis\) 值定义为到子树中最近的左儿子或右儿子为空的节点的距离。
注意:此处的 \(dis\) 指的不是深度,左偏树的深度是没有保证的,一条向左的链也是左偏树。
根据左偏树的性质,很显然它满足递归结构。
合并
假设合并两个小根堆。
首先满足性质1,让权值小的作为根,再合并根的右子树与另一个堆。
为满足性质2,如果左子树的 \(dis\) 值小于右子树,就交换左右子树。
int Merge(int x, int y) {
if(!x || !y) return x | y;
if(val[x] > val[y]) swp(x, y);
rs[x] = Merge(rs[x], y); fa[rs[x]] = x;
if(dis[ls[x]] < dis[rs[x]]) swp(ls[x], rs[x]);
dis[x] = dis[ls[x]] + 1;
return x;
}
根据左偏树的性质,每递归一层 \(dis\) 值都会减一。一棵有 \(n\) 个节点的树 \(dis\) 最大为 \(O(log n)\)。所以时间复杂度为 \(O(log n)\)。
弹出堆顶
即删除堆顶,合并左右子树, 将原堆顶和左右儿子的父亲设为新堆顶,将原堆顶的左右儿子和 \(dis\) 值清空。
之所以要讲原堆顶的父亲也设为新堆顶,是因为并查集使用了路径压缩,导致子树内可能有节点直接连向原堆顶。
int Pop(int x) {
fa[ls[x]] = fa[rs[x]] = fa[x] = Merge(ls[x], rs[x]);
ls[x] = rs[x] = dis[x] = 0;
return val[x];
}