正题
左偏树是用来解决堆合并的问题的。
如例题:【模板】左偏树(可并堆)
如果用普通的堆来做,那么时间复杂度是的,所以不行。
我们要学习一种高级的数据结构来解决这个事情:左偏树。
首先,左偏树是构造出来的,所以他自己本身有几个定义:
1.定义左偏树的外节点为左节点为空或者右节点为空的节点。
2.定义左偏树的一个节点的距离为该节点到与其最近的一个外节点的距离,外节点的距离为0,空节点的距离为-1.
几个性质:
1.堆的性质:一个非叶子结点的值一定大于(在小根堆中是小于)子节点的值。
2.左偏性质:一个非叶子节点的左儿子的距离一定大于或等于右子树的距离。(特有性质,不影响堆的性质)
3.节点的距离等于右儿子的距离+1(这个也是显然的,因为是最近的外节点,而且第二个性质
4.每个节点的左子树和右子树都满足左偏树的性质(因为上面的性质是对于所有节点而言的
定理1:如果一棵左偏树的根节点的距离一定,那么节点数最少的左偏树是一棵完全二叉树。
定理2:如果一棵左偏树的根节点的距离为k,那么最少节点数为。
定理3:如果一棵左偏树的节点个数为n,那么最大距离为,证明可由定理2得到。
有了几个定理,我们就能完成几个操作:
1.合并操作
思考怎么运用定理3.
首先,很明显想到递归右子树!
操作:
1.将要合并x和y。
2.每次比较x根的值和y根的值,令x根的值更小(若y更小,则交换)
3.递归x根的右子树,和y整棵树,继续进入操作1,直到有一个空为止。
4.令x根的右子树为递归后返回后的指针。
5.维护左偏性质(比较左右儿子的距离)
6.运用性质3更新该点的距离。
7.返回x,因为x可能被交换。
完成操作。
int merge(int x,int y){
if(x==0 || y==0) return x+y;
if(t[x]>t[y] || (t[x]==t[y] && x>y)) swap(x,y);
rs[x]=merge(rs[x],y);f[rs[x]]=x;
if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
dis[x]=dis[rs[x]]+1;
return x;
}
好像是没有错的!正确性显然,因为每一次都让小的当根。
分析时间复杂度,假设x树n个点,y树m个点。
每次只会往右儿子走,所以相当于只会递归根的距离那么多层,所以总时间复杂度是的。
剩下的操作与这个合并操作结合将会变得很简单。
2.删除操作
如果根节点有信息就pushdown下去,否则就直接删除根节点,(把根的两个儿子设为0,两个儿子的父亲设为0),然后合并即可,返回的指针直接继承在原根上面即可。
void del(int x){
int fx=findpa(x);
printf("%d\n",t[root[fx]]);
tf[root[fx]]=true;
root[fx]=merge(ls[root[fx]],rs[root[fx]]);
}
3.n个元素合并在一起操作。
第一种方法:暴力合并即可
第二种方法:把n个元素扔进队列,每次取出队列前两个进行合并。
那么总的时间复杂度是:
很优秀。
其他操作暂时没有接触到,无非就是类同于线段树打lazy标记。
后面做的例题会贴出来。