zoukankan      html  css  js  c++  java
  • 左偏树 学习笔记

    左偏树

    左偏树到底是什么呢??? 左偏树实际上是可合并的堆。

    他的节点不仅存了他的权值,还存了一个比较重要的信息 \(dis\)

    dis 的定义: 一个节点到他的子树中的叶子节点的最近距离。

    维护\(dis\)有什么用呢?

    当我们合并两个堆时,可能会导致堆退化成一条链,这样我们的询问操作的复杂度就会变成 O(n)。

    这显然不是我们想看到的。

    而左偏树就是靠 \(dis\) 来维护左右两颗子树的平衡。

    性质

    1. 节点的权值小于他儿子节点的取值。

    2. 左偏树的任意节点的左儿子的距离都要比右儿子大(不然他怎么叫左偏树呢雾

    3. 左偏树任意节点的距离等于右儿子的距离加一。

    证明如下:

    根据左偏性,可以得到他左儿子的距离要小于右儿子的距离,
    而左偏树中距离的定义是一个节点到离其最近的外节点的距离,故为Dis(RightSon)+1。

    1. 一个n个节点的左偏树的距离最大为 log(n+1)-1(不需要掌握

    证明如下:

    若左偏树的距离为一定值,则结点数最少的左偏树是完全二叉树。

    结点最少的话,就是左右儿子距离一样,这就是完全二叉树了。

    若一棵左偏树的距离为k,则这棵左偏树至少有 \(2^{k+1}-1\) 个节点。

    距离为k的完全二叉树高度也是k,节点数就是 \(2^{k+1}-1\) 个。

    这样就可以证明性质四了。

    因为 n>= \(2^{k+1}-1\),所以 k<=log(n+1)-1。

    操作

    一 合并操作

    首先,当我们要合并堆时,让要保持堆的性质,假设我们要维护小根堆,我们就要让根节点权值较小的放上边。

    然后再让另一个树与他的右子树合并。

    那么为什么要合并右子树呢???

    因为左子树的距离是大于右子树的,合并左子树显然不优。

    当我们合并完后,可能会出现这样一种情况,左子树的距离小于右子树,这时我们直接交换一下就行了。

    代码

    int merage(int x,int y)//返回根节点
    {
    	if(x == 0 || y == 0) return x + y;//如果有一个为空,直接返回,不用合并
    	if(tr[x].val > tr[y].val) swap(x,y);//要满足堆的性质
    	tr[x].rc = merage(tr[x].rc,y);//合并右子树和另外一棵树
    	fa[tr[x].rc] = x;
    	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);//满足性质二
    	tr[x].dis = tr[tr[x].rc].dis + 1;//满足性质三
    	return x;
    }
    

    二 插入一个点

    一个点就相当于一棵树,直接合并就行了。

    代码同上。

    三 删除根节点

    我们要删根节点,等于把根节点孤立出来,这时候我们只需要合并左右两颗子树就行了

    代码

    void del(int x)
    {
    	tr[x].val = -1;//标记x已经被删除
    	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;//先把左右儿子的父亲都设为他自己,方便以后合并
    	fa[x] =  merage(tr[x].lc,tr[x].rc);//保持原有的父子关系不变
    }
    

    例题

    P3377 模板左偏树

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N = 1e6+7;
    int n,m,opt,x,y;
    int fa[N],dis[N];
    struct node{
    	int val;
    	int lc,rc;
    	int fa,dis;
    }tr[N];
    int inline read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    int find(int x){
        if(tr[x].fa == x) return x;
        else return tr[x].fa = find(tr[x].fa);
    }
    int merage(int x,int y){
    	if(x == 0 || y == 0) return x+y;
    	if(tr[x].val > tr[y].val || (tr[x].val == tr[y].val && x > y)){
    		swap(x,y);
    	}
    	tr[x].rc = merage(tr[x].rc,y);
    	tr[tr[x].rc].fa = x;
    	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
    	tr[x].dis = tr[tr[x].rc].dis + 1;
    	return x;
    }
    void del(int x){
    	tr[x].val = -1;
    	tr[tr[x].lc].fa = tr[x].lc, tr[tr[x].rc].fa = tr[x].rc;
    	tr[x].fa = merage(tr[x].lc,tr[x].rc);
    }
    int main(){
    	n = read(), m = read();
    	for(int i = 1; i <= n; i++){
    		tr[i].fa = i;
            tr[i].val = read();
    	}
    	while(m--){
    		opt = read();
    		if(opt == 1){
    			x = read(); y = read();
    			int fx = find(x) , fy = find(y);
    			if(tr[x].val == -1 || tr[y].val == -1) continue;
    			if(fx == fy) continue;
    			tr[fx].fa = tr[fy].fa = merage(fx,fy);
    		}
    		else{
    			x = read();
    			if(tr[x].val == -1) cout<<-1<<endl;
    			else{
    				int y = find(x);
    				printf("%d\n",tr[y].val);
    				del(y);
    			}
    		}
    	}
    	return 0;
    }
    

    P2713 罗马游戏

    左偏树的裸题,和模板差不多

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N = 1e6+10;
    char opt;
    int n,t,x,y,fa[N];
    struct node{
    	int lc,rc;
    	int val,dis;
    }tr[N];
    inline int read()
    {
    	int s = 0, w = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
    	return s * w;
    }
    int find(int x)
    {
    	if(fa[x] == x) return x;
    	else return fa[x] = find(fa[x]);
    }
    int merage(int x,int y)
    {
    	if(x == 0 || y == 0) return x + y;
    	if(tr[x].val > tr[y].val) swap(x,y);
    	tr[x].rc = merage(tr[x].rc,y);
    	fa[tr[x].rc] = x;
    	if(tr[tr[x].lc].dis < tr[tr[x].rc].dis) swap(tr[x].lc,tr[x].rc);
    	tr[x].dis = tr[tr[x].rc].dis + 1;
    	return x;
    }
    void del(int x)
    {
    	tr[x].val = -1;
    	fa[tr[x].lc] = tr[x].lc; fa[tr[x].rc] = tr[x].rc;
    	fa[x] =  merage(tr[x].lc,tr[x].rc);
    }
    int main()
    {
    	n = read();
    	for(int i = 1; i <= n; i++)
    	{
    		fa[i] = i;
    		tr[i].val = read();
    	}
    	t = read();
    	while(t--)
    	{
    		cin>>opt;
    		if(opt == 'M')
    		{
    			x = read(); y = read();
    			int xx = find(x), yy = find(y);
    			if(tr[x].val == -1 || tr[y].val == -1) continue;
    			if(xx == yy) continue;
    			fa[xx] = fa[yy] = merage(xx,yy);
    		}
    		else if(opt == 'K')
    		{
    			x = read();
    			int xx = find(x);
    			if(tr[x].val == -1)
    			{
    				printf("%d\n",0);
    				continue;
    			}
    			printf("%d\n",tr[xx].val);
    			del(xx);
    		}
    	}
    	return 0;
    }
    

    P1456 Monkey King

    每次打架可以看成是删除两个堆的堆顶,插入一个数,合并两个堆。

    左偏树随便维护一下就行。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N = 1e5+10;
    int n,m,x,y,root,fa[N];
    struct node
    {
    	int lc,rc,dis,val;
    }tr[N];
    inline int read()
    {
        int s = 0, w = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
        while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
        return s * w;
    }
    int find(int x)
    {
    	if(fa[x] == x) return x;
    	else return fa[x] = find(fa[x]);
    }
    int merage(int x,int y)
    {
    	if(!x || !y) return x + y; 
    	if(tr[x].val < tr[y].val) swap(x,y);
    	tr[x].rc = merage(tr[x].rc,y);
    	fa[tr[x].rc] = x;
    	if(tr[tr[x].rc].dis > tr[tr[x].lc].dis) swap(tr[x].lc,tr[x].rc);
    	tr[x].dis = tr[tr[x].rc].dis + 1;
    	return x;
    }  
    int del(int x)
    {
    	fa[tr[x].lc] = tr[x].lc, fa[tr[x].rc] = tr[x].rc;
    	int root = merage(tr[x].lc,tr[x].rc);
    	fa[x] = root;
    	return root;
    } 
    int main()
    {
    	while(scanf("%d",&n) != EOF)
    	{
    		for(int i = 1; i <= n; i++) tr[i].lc = tr[i].rc = tr[i].dis = 0;
    		for(int i = 1; i <= n; i++) fa[i] = i, tr[i].val = read();
    		m = read();
    		for(int i = 1; i <= m; i++)
    		{
    			x = read(); y = read();
    			int fx = find(x), fy = find(y);
    			if(fx == fy) printf("%d\n",-1);
    			else
    			{
    				root = del(fx);
    				tr[fx].lc = tr[fx].rc = 0; fa[fx] = fx; tr[fx].val /= 2;
    				fx = merage(fx,root);
    				root = del(fy);
    				tr[fy].lc = tr[fy].rc = 0; fa[fy] = fy; tr[fy].val /= 2;
    				fy = merage(fy,root);
    				fx = merage(fx,fy);
    				printf("%d\n",tr[fx].val);
    			} 
    		}
    	}
    }
    

    ENDING

  • 相关阅读:
    QT学习——dialog、widget、mainwindow的区别和选择
    剑指offer——二叉树的深度
    位运算实现加减乘除四则运算
    剑指offer——求两个整数和
    C++常用设计模式
    从编程实现角度学习 Faster R-CNN(附极简实现)
    剑指offer——最小的k个数
    剑指offer——对称二叉树
    java 定时器
    rocketmq consumer接收到的MessageExt中各个字段的说明
  • 原文地址:https://www.cnblogs.com/genshy/p/13394970.html
Copyright © 2011-2022 走看看