【可并堆】【数据结构】左偏树简介

感情迁移 提交于 2020-01-14 06:43:03

左偏树

Noip大概率翻皮水了,然后先继续xjb学习吧,顺便文化课也是翻皮水大队的:(

今天介绍一种特殊的数据结构:可并堆中的一种->左偏树(好吧其实是因为这种简单易懂代码复杂度较低).

基本介绍

左偏树,故名思义,它是颗向左倾斜的树,其实,它还是棵二叉树,再者,它还具有堆的性质,but,它不是堆.

那么显然,左偏树看起来就像是优化堆一些难以用较优复杂度实现的操作,其实主要就是一个操作:合并.

我们知道,传统的二叉堆,是需要暴力合并的,复杂度为\(O(sz1+sz2)\),而本文所涉及的左偏树,复杂度为$O(log_{2} sz1sz2) $.

首先介绍一个定义:一个节点到其子树内最近叶节点的距离称之为这个节点的高度Height,简记为ht.

接下来给出一些左偏树的性质.

基本性质

性质1

堆的性质:对于任意节点P,\(val_{P}\)<(或>)\(val_{lson[p]}\)\(val_{rson[p]}\)

性质2

\[ht_{lson[P]} \geq ht_{rson[P]}\]

左偏树顾名思义,向左倾斜的树,就是这个性质在图像可视化后的诠释.

性质3

\[ht_{P}=ht_{lson[P]}+1\]

很好理解,由性质2以及叶节点ht为0可以得出.

性质4

对于一棵\(n\)节点的左偏树,\(max\{ht\} \leq log_{2}(n+1)-1\)

给出证明如下,设\(max\{ht\}=k\)

那么,显然节点数最少的情况为一棵满二叉树,此时,节点数为\(2^{k+1}-1\)

\(n \geq 2^{k+1}-1\),由数学推导可得性质4.

这个性质主要保证了左偏树的复杂度.

接下来简单介绍一下一些基本操作.

基本操作

1.合并

这是左偏树最基础也是最重要的操作,这里介绍小根堆的情况:

首先,令根节点权值较小的为x,否则为y,记根节点分别为X、Y.

首先在根节点的右子树最右链中找到第一个比Y大的位置,将Y作为其父亲,然后不断递归调用合并Y的右子树和以该节点为根的右子树即可,并维护更新相关信息.

然后发现更新后,右子树的ht可能比左子树大,此时交换两个子树即可.

其实上述合并过程的实现本质是一样的,下面给出参考,对于复杂度的分析,不多给出解释.

inline int merge(int x,int y){
    if (!x||!y) return x+y;
    if (v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y);
    ch[x][1]=merge(ch[x][1],y);
    f[ch[x][1]]=x;
    if (ht[ch[x][0]]<ht[ch[x][1]]) swap(ch[x][0],ch[x][1]);
    ht[x]=ht[ch[x][1]]+1;return x;
}

2.插入

本质上就是将一棵只有一个节点的左偏树与当前树合并.

3.查询最大/最小值

直接返回根结点的权值.

4.删除根节点

合并根节点两棵子树即可.

5.构造一棵新左偏树

法1:

暴力插入

法2:

分治思想,分段处理,直至剩余1个节点,然后返回并merge,复杂度约为O(n).

6.删除任意节点

子树内维护与删除根节点一样,然后向上传递信息并更改,维护性质即可.

模板

给出模板题的模板

见下:

#include <stdio.h>
#define MN 100005
#define R register
inline int read(){
    R int x; R bool f; R char c;
    for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
    for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
    return f?-x:x;
}
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void swap(int &a,int &b){a^=b,b^=a,a^=b;}
int n,q,ch[MN][2],v[MN],ht[MN],f[MN];
inline int gf(int u){
    while(f[u]) u=f[u];
    return u;
}
inline int merge(int x,int y){
    if (!x||!y) return x+y;
    if (v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y);
    ch[x][1]=merge(ch[x][1],y);
    f[ch[x][1]]=x;
    if (ht[ch[x][0]]<ht[ch[x][1]]) swap(ch[x][0],ch[x][1]);
    ht[x]=ht[ch[x][1]]+1;return x;
}
inline void pop(int x){
    v[x]=-1;f[ch[x][0]]=f[ch[x][1]]=0;
    merge(ch[x][0],ch[x][1]);
}
int main(){
    n=read(),q=read();ht[0]=-1;
    for (R int i=1; i<=n; ++i) v[i]=read();
    for (R int i=1; i<=q; ++i)
        if (read()==1){
            R int x=read(),y=read();
            if (!(~v[x])||!(~v[y])) continue;
            x=gf(x),y=gf(y);if (x==y) continue;
            merge(x,y);
        }
        else{
            R int x=read();
            if (!(~v[x])) puts("-1");
            else {x=gf(x);printf("%d\n",v[x]);pop(x);}
        }
    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!