• 可并堆——左偏树


    定义

    左偏树(Leftist Tree)是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right)外,还有两个属性,键值和距离(dist)。

    先引入一个概念

    外节点:一个左子树为空或者右子树为空即可在其子树并入新元素的节点

    距离:父节点到外节点最少的经历的边数

    所以对于外节点,dist(i)=0;

    性质

    左偏树的本质是一颗有序的二叉树,故满足堆性质

    • 节点的键值小于或等于它的左右子节点的键值

    左偏树,顾名思义,是一颗向左偏的树,因为左边节点多而右边稀少,那么顺着右子树查找外节点一定比顺着左子树查找经过的边数少,这又引出了左偏树的两条性质

    • 节点的左子节点的距离不小于右子节点的距离

    • 节点的左子节点右子节点也是一颗左偏树

    因为一个节点的dist实质上就是这个节点一直顺着右边查找外节点的距离,那么

    • 节点的距离等于它的右子节点的距离加1

    即dist(rt)=dist(r)+1;又因为外节点的dist定为0,而它的右节点为空,根据此性质,故空节点的dist为-1

    左偏树还有一些其他的性质和距离与节点的关系,可以参考 左偏树的特点及其应用——黄源河

    合并

    在合并两个堆的时候,左右子树会渐渐变的平衡,但要时刻保持左偏树的性质,即当发现左子树的dist小于右子树的dist时,swap

    还是以小根堆为例
    image

    image

    image

    操作

    首先定义一些东西

    struct node
    {
        int l,r,key,dist;//左、右儿子,键值,距离
        node(int d=-1) {dist=d;}
    }t[N];
    
    int fa[N];//用并查集记录每颗左偏树堆顶编号 
    

    合并

    结合上方图示理解

    int Merge(int x,int y)//x,y为两棵树堆顶的编号
    {
        //如果一颗树为空,返回另一棵树
        if(!x) return y;  
        if(!y) return x;  
        if(t[x].key > t[y].key) swap(x,y);//选择x为新树,若其根节点键值大于y,则交换
        t[x].r=Merge(t[x].r,y);//然后将y与x的右子树合并
        fa[t[x].r]=x;//将右子树的父节点赋为x
        if(t[t[x].l].dist < t[t[x].r].dist) swap(t[x].l,t[x].r);//为保证左子节点的距离不小于右子节点的距离,合并完后判断是否需要交换左右子树
        t[x].dist=t[t[x].r].dist+1;//最后更新根节点的dist值
        return x;
    }
    

    插入

    可看做与只有单点的左偏树合并

    void push(int x,int y)//将y插入到编号为x的左偏树中
    {
        Merge(x,y);
    }
    

    删除

    可看做将当前节点的左右儿子合并

    void pop(int x)//删除编号为x的节点所在左偏树的堆顶元素
    {
        x=findf(x);//先找到堆顶编号
        int tmp=Merge(t[x].l,t[x].r);
        fa[x]=fa[t[x].l]=fa[t[x].r]=tmp;//重置父亲
    }
    

    查询

    int top(int x)//返回编号为x的节点所在左偏树的堆顶的键值
    {
        return t[findf(x)].key;
    }
    

    模板

    luogu3377

    注意当堆中有多个最小值时,删除原序列中靠前的

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #define N 100005
    using namespace std;
    
    int n,m; 
    int fa[N];
    
    struct node
    {
        int l,r,key,dist;
        node(int d=-1) {dist=d;}
    }t[N];
    
    int findf(int x)
    {
    	if(x==fa[x]) return x;
    	return fa[x]=findf(fa[x]);
    }
    
    void con(int x,int y)
    {
    	x=findf(x),y=findf(y);
    	fa[x]=y;
    }
    
    int top(int x){return t[findf(x)].key;} 
    
    int Merge(int x,int y)
    {
        if(!x) return y;  
        if(!y) return x;  
        if(t[x].key > t[y].key||(t[x].key==t[y].key&&x>y)) swap(x,y);
        t[x].r=Merge(t[x].r,y);fa[t[x].r]=x;
        if(t[t[x].l].dist < t[t[x].r].dist) swap(t[x].l,t[x].r);
        t[x].dist=t[t[x].r].dist+1;
        return x;
    }
    
    void pop(int x)
    {
        x=findf(x);
        int tmp=Merge(t[x].l,t[x].r);
        fa[x]=fa[t[x].l]=fa[t[x].r]=tmp;
        t[x].l=t[x].r=t[x].key=0;t[x].dist=-1;//因为数据有可能给到已经被删过的点,所以需要将被删除节点清空
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++) {scanf("%d",&t[i].key);t[i].dist=0;fa[i]=i;}
    	for(int i=1;i<=m;i++)
    	{
    		int opt,x,y;scanf("%d%d",&opt,&x);
    		if(opt==1)
    		{
    			scanf("%d",&y);
    			if(!t[x].key||!t[y].key) continue;//若这个数被删除,不合并 
    			int fx=findf(x),fy=findf(y);
    			if(fx!=fy) Merge(fx,fy);//不在一个集合里才合并 
    		}
    		else 
    		{
    			if(!t[x].key) printf("-1
    ");
    			else {printf("%d
    ",top(x));pop(x);}	
    		}
    	}
    	return 0;
    }
    

    参考来源

    百度百科
    https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin#2

    图解数据结构(9)——左偏树 http://www.cnblogs.com/yc_sunniwell/archive/2010/06/28/1766756.html

    可并堆?左偏树? ——MaxMercer http://blog.csdn.net/maxmercer/article/details/75136683

    左偏树(可并堆)——yew1eb http://blog.csdn.net/yew1eb/article/details/19349099

  • 相关阅读:
    考研打卡_Day04
    考研打卡_Day03
    考研打卡-Day02
    吾日三省-归隐
    为什么要写博客?
    用C语言写一个Helloworld_实现第一步编译运行
    C语言中的结构体是怎么定义的_怎么使用?
    C语言的常用的数据类型有哪些_所占字节分别是多少
    Vim编辑器中查找关键词命令_查找与替换命令_多窗口命令
    Vim的基本操作命令与光标移动命令
  • 原文地址:https://www.cnblogs.com/XYZinc/p/7371761.html
Copyright © 2020-2023  润新知