首页 > 其他分享 >左偏树/可并堆

左偏树/可并堆

时间:2024-07-19 17:19:10浏览次数:6  
标签:dui rs int 左偏 节点 dis

      

 

1.什么是左偏树?


 

上面的树都是左偏树。

先引出一个概念,dis等于节点到它子树里面最近的叶子节点的距离,特别地叶子节点的dis等于0。

观察上图我们可以感性理解左偏树,就是左子树的深度大于等于右子树,看上去整个树向左偏。

再看一眼就可以总结出几条性质:

1.左儿子的dis<=右儿子的dis(左偏性质)

2.节点的dis=右儿子的dis+1(因为存的是最近的叶子节点,右边的叶子一定离得更近)

3.每个节点的值一定小于等于儿子节点的值(堆性质)

不要问为什么,只有满足这些条件的才叫左偏树。

同时由上面的性质可以推出:任何时候,节点数为n的左偏树,距离最大为log(n+1)−1。

证明:

对于一棵dis=k的树,需要的最少的节点数是满二叉树(少一个点dis就等于k-1)。

若一棵左偏树的距离为k,则这棵左偏树至少有2k+1−1个点。

n>=2k+1-1

n+1>=2k+1

log(n+1)>=k+1

log(n+1)-1>=k

因为上面这个性质可得左偏树的深度有限制,两个左偏树进行合并可以用Θ(logn)的复杂度。

2.基本操作


1.merge(合并)

两个左偏堆合并,要满足堆的性质,所以从两个根中选出小的一个当根,大的那个与右子树合并即可递归到只有一或两个节点的情况。

因为深度最大为log(n+1)所以一次复杂度Θ(logn)。

inline int merge(int x,int y)
{
    if(!x||!y)return x+y;//边界情况
    if(dui[x].v>dui[y].v||(dui[x].v==dui[y].v&&x>y))swap(x,y);//使x为小的那个的根
    rs=merge(rs,y);//递归,将y与右子树合并
    if(dui[ls].dis<dui[rs].dis)swap(ls,rs);//堆建完后,保证左偏堆性质交换左右子树
    dui[ls].rt=dui[rs].rt=dui[x].rt=x;//更新根
    dui[x].dis=dui[rs].dis+1;//更新dis
    return x;
}

2.pop(删除x节点所在堆的最小值/最大值)

inline void pop(int x)
{
    dui[x].v=-1;
    dui[ls].rt=ls;dui[rs].rt=rs;//更新左右子树的根
    dui[x].rt=merge(ls,rs);//将左右子树合成新的堆
}

3.Del:(删除任意(x)编号节点)

将x删掉,将x的左右儿子合并,然后接到f[x]的儿子处。

因为这时可能不满足节点的dis=右儿子的dis+1的性质。

所以向上更改。

void pushup(int x)
{
    if(x==f[x])return ;//达到根节点,返回
    if(t[x].d!=t[rs(x)].d+1)//不满足左偏性质,更新
    {
        t[x].d=t[rs(x)].d+1;
        pushup(f[x]);
    }
}
void del(int x)
{
    int fx=f[x];//x的父亲
    int u=merge(t[x].ch[0],t[x].ch[1]);//合并左右儿子
    f[u]=fx;//更新合并后的节点的信息
    if(t[fx].ch[0]==x)t[fx].ch[0]=u;
    else t[fx].ch[1]=u;
    t[x].val=t[x].ch[0]=t[x].ch[1]=t[x].d=0;
    pushup(x);//遍历检查左偏性质
}

4.Push(插入x节点)

新建一个节点,将其初始化为x

因为这个节点也可以视为一个堆,可以直接合并

5.并查集存堆找根并路径压缩

inline int get(int x)
{
    return dui[x].rt==x?x:dui[x].rt=get(dui[x].rt);
}

3.例题:


 

https://www.luogu.com.cn/problem/P3377

#include <bits/stdc++.h>
using namespace std;
#define ls dui[x].son[0]
#define rs dui[x].son[1]
int n,m; 
struct node
{
    int son[2],rt,v,dis;
}dui[100010];
inline int merge(int x,int y)
{
    if(!x||!y)return x+y;
    if(dui[x].v>dui[y].v||(dui[x].v==dui[y].v&&x>y))swap(x,y);
    rs=merge(rs,y);
    if(dui[ls].dis<dui[rs].dis)swap(ls,rs);
    dui[ls].rt=dui[rs].rt=dui[x].rt=x;
    dui[x].dis=dui[rs].dis+1;
    return x;
}
inline int get(int x)
{
    return dui[x].rt==x?x:dui[x].rt=get(dui[x].rt);
}
inline void pop(int x)
{
    dui[x].v=-1;
    dui[ls].rt=ls;dui[rs].rt=rs;
    dui[x].rt=merge(ls,rs);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&dui[i].v);
        dui[i].rt=i;
    }
    for(int i=1;i<=m;i++)
    {
        int op,x,y;
        scanf("%d%d",&op,&x);
        if(op==1)
        {
            scanf("%d",&y);
            if(dui[x].v==-1||dui[y].v==-1)continue;
            int f1=get(x),f2=get(y);
            if(f1==f2)continue;
            else merge(f1,f2);            
        }
        else
        {
            if(dui[x].v==-1)printf("-1\n");
            else printf("%d\n",dui[get(x)].v),pop(get(x));
        }
    }
    return 0;
} 

4.最后


本文是在阅读ShuraEye和_Orchidany的大作后有感而发

标签:dui,rs,int,左偏,节点,dis
From: https://www.cnblogs.com/storms11/p/18311927

相关文章

  • 左偏树
    前言左偏树是一种可并堆,顾名思义,它支持快速合并。定义定义外界点为孩子数量小于等于\(2\)个的节点,\(dis(u)\)表示节点\(u\)到最近的外节点经过的边数减\(1\)。特别的,空节点的\(dis\)为\(-1\)。定义节点\(u\)权值为\(val(u)\),左、右儿子分别为\(ls(u),rs(u)\)。左......
  • 【博客】左偏树
    左偏树前言左偏树是一棵向左偏的树左偏树是一种能在\(O(\logn)\)之内完成合并的可并堆长这样我们常用左偏树完成以下操作在指定集合中插入一个元素查询集合中最高优先级的元素删除集合中最高优先级的元素删除指定元素合并两个集合性质首先我们要知道左偏树的......
  • 左偏树
    左偏树是一种可并堆(一系列的堆),支持以下操作:删除一个堆的最值。查询一个堆的最值。新建一个堆,只包含一个元素。合并两个堆。这个复杂度是\(O(\log)\)的。左偏树是一颗二叉树。定义“外结点”为儿子数量不等于\(2\)的结点,定义每个结点的\(dist\)为该结点到最......
  • 左偏树/可合并堆
    左偏树/可合并堆代码笔记代码思路主体部分:合并堆(即merge函数)大堆左偏,把小堆和大堆的右儿子合并。感性理解:堆的形态将比较平衡。辅助部分:并查集维护堆关系简化部分:自定义数据类型(structBheap)注意事项:堆的最大数量是\(n+m\)注意考虑堆被删空等细节情况(尤其是题目......
  • 左偏树/可并堆
    20231107左偏树/可并堆将左偏树/可并堆做一个小结,不写我可能就要忘了。。。左偏树,顾名思义,就是保证左子树深度一定大于右子树,同时需要满足堆的性质,于是在合并两个堆的时候的时间复杂度就为\(\logn\),感觉是非常易懂的,具体实现的细节还是有一些。注意我们会用到并查集和......
  • 数据结构——左偏树/可并堆学习笔记
    引入作为树形数据结构的一员——堆,对于取极值拥有着优秀的复杂度,但是,合并两个堆却成为了一个问题。除了朴素算法外,还有什么算法可以合并两个堆呢?正文那么,可并堆是个啥呢?简单来说,它是一个支持合并操作的二叉堆(好像是废话)。首先,简单介绍一下二叉堆的性质,学过的读者可自行跳过。......
  • 【学习笔记】左偏树
    左偏树属于可并堆的一种,可并堆,也就是可以在较低的时间复杂度下完成对两个堆的合并。定义及性质对于一棵二叉树,定义外节点为左儿子或右耳子为空的节点,定义其的\(dist\)为\(1\),而不是外节点的\(dist\)为其到子树中最近的外节点距离\(+1\)。空节点的\(dist\)为\(0\)。例......
  • 左偏树
    模板: #include<bits/stdc++.h>usingnamespacestd;constintMAXN=1e5+10;intn,m,heap[MAXN];intfa[MAXN],ls[MAXN],rs[MAXN],dis[MAXN];booldel[MAXN];intfind(intx){returnx==fa[x]?x:fa[x]=find(fa[x]);}intMerge(int......
  • 左偏树
    左偏树左偏树是一种可以让我们在\(O(\logn)\)的时间复杂度内进行合并的堆式数据结构。为了方便以下的左偏树为小根堆来讨论。定义外结点:左儿子或者右儿子是空节点的结点。距离:一个结点\(x\)的距离\(dis[x]\)定义为其子树中与结点\(x\)最近的外结点到\(x\)的距离......
  • 【P4331 [BalticOI 2004]】Sequence 数字序列 题解(左偏树维护动态区间中位数)
    左偏树维护动态区间中位数。传送门P4331BalticOI2004Sequence数字序列。Solution1我的思路和题解前半部分完全重合了((如果按照单调不增去分割\(a\)序列的话,对于每一段我们能很简单地得出它的最佳答案:中位数。发现严格单调很难做,很难拿捏,考虑对\(a\)序列的每一项都进......