左偏树
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; }
来源:https://www.cnblogs.com/Melacau/p/leftist_tree.html