[算法学习笔记]左偏树的基本操作及其应用
简介
左偏树是一种可以快速支持合并等操作的堆, 是可并堆中代码复杂度最低,也最容易理解的一种(注意左偏树的每一棵子树都为左偏树)
性质
左偏树是一种二叉树, 除了有二叉树的左右儿子,还有2个属性,键和距离。下面是左偏树的一些基本性质。
-
节点的键值小于或等于左右子节点的键值。这是左偏树的堆性质。
-
节点的左子节点的距离不小于右子节点的距离。 这是左偏树的左偏性质。
-
节点的距离等于它的右子节点距离加一。 若无则为0.
-
若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树。(显然啊)
基本操作
数组定义
struct Point{
int val,ls,rs,dis,fa;
//val 为 权值
//ls,rs 为左右子树
//dis为距离
//fa 为 father!
}tree[maxn];
Merge
顾名思义, Merge操作就是把两个左偏树并起来, 注意并后的左偏树一定要满足上面的四条性质。现在我们令x, y为要合并的两个堆的根, 下面即是步骤。
- 如果\(x\) 或 \(y\) 为空(即等于0) 返回 x + y(就是\(x\) 或 \(y\) 非零的那一个)
- 为了保持一定是val大的的并到小的上面, 所以\(if(tree[x].val > tree[y].val) swap(x, y);\)(大根堆则反之)
- 现在我们将\(y\)树并到\(x\)的右子树上.(因为\(x\)的val比\(y\)的小, 所以并到他的子树中, 又因为这棵树是左偏的,你就把它并到右子树中吧)
- 为了维持上面的第二条性质, 若\(tree[tree[x].ls].dis < tree[tree[x].rs].dis\) 则 \(swap(tree[x].ls, tree[x].rs)\)
- 更新x的距离
int merge(int x, int y)
{
if (x == 0 || y == 0)
return x+y;
if ((tree[x].val > tree[y].val) || (tree[x].val == tree[y].val && x > y)) swap(x,y);
tree[x].rs = merge(tree[x].rs,y);
tree[tree[x].rs].fa = x;
if(tree[tree[x].ls].dis < tree[tree[x].ls].dis)
swap(tree[x].ls,tree[x].rs);
tree[x].dis = tree[tree[x].rs].dis + 1;
return x;
}
Insert
Insert操作为插入一个新的节点, 把这个节点看做一颗新的左偏树即可
int New(int x, int y){
tree[++tot].val = y;
tree[tot].ls = tree[tot].rs = 0;
return merge(x, y);
}
Delete
把根提出来, 合并左右节点就好了
root[x]= merge(tree[x].ls, tree[x].rs);
Top
emmm, \(Top = tree[x].val\) (x 为 堆的根)
例题
模板
以洛谷p3377为例, 因为此题需要维护联通性, 所以需要像并查集一样暴力找父亲(不能路径压缩) 代码丑, 建议不看, 以后会更新的
#include <bits/stdc++.h>
using namespace std;
int n,m;
struct Point{
int val,ls,rs,dis,fa;
}tree[1000010];
template<class T>
void read(T &a){
T s = 0, w = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') w = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
s = (s << 1) + (s << 3) + (c ^ 48);
c = getchar();
}
a = w*s;
}
int merge(int x, int y){
if (x == 0 || y == 0)
return x+y;
if ((tree[x].val > tree[y].val) || (tree[x].val == tree[y].val && x > y)) swap(x,y);
tree[x].rs = merge(tree[x].rs,y);
tree[tree[x].ls].fa = x;
if(tree[tree[x].ls].dis < tree[tree[x].rs].dis){
swap(tree[x].ls,tree[x].rs);
}
tree[x].dis = tree[tree[x].rs].dis + 1;
return x;
}
int find(int x){
if(tree[x].fa == 0) return x;
return find(tree[x].fa);
}
int main(){
read(n); read(m);
tree[0].dis = -1;
for (int i = 1; i <= n; i++){
int x;
cin>>x;
tree[i].val = x;
}
for (int i = 1; i <= m; i++){
int opx;
read(opx);
if(opx == 1){
int x,y;
read(x); read(y);
if(tree[x].val == -1 || tree[y].val == -1) continue;
int xx = find(x), yy = find(y);
if(xx == yy) continue;
merge(xx,yy);
}
if(opx == 2){
int x;
read(x);
int xx = find(x);
printf("%d\n",tree[xx].val);
if(tree[xx].val == -1) continue;
tree[xx].val = -1;
int ls = tree[xx].ls;, rs = tree[xx].rs;
tree[xx].ch[0] = tree[xx].ch[1] = 0;
tree[ls].fa = 0,tree[rs].fa = 0;
merge(ls,rs);
}
}
return 0;
}