一、平衡树
知识点:
(1)、平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。 最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。
(2)、对一棵查找树(search tree)进行查询/新增/删除 等动作, 所花的时间与树的高度h 成比例, 并不与树的容量 n 成比例。如果可以让树维持矮矮胖胖的好身材, 也就是让h维持在O(lg n)左右, 完成上述工作就很省时间。能够一直维持好身材, 不因新增删除而长歪的搜寻树, 叫做balanced search tree(平衡树)。平衡树有很多种, 其中有几类树维持平衡的方法。
(3)、红黑树:红黑树的平衡是在插入和删除的过程中取得的。对一个要插入的数据项,插入程序要检查不会破坏树一定的特征。如果破坏了,程序就会进行纠正,根据需要更改树的结构。通过维持树的特征,保持了树的平衡。
红黑树有两个特征:
①节点都有颜色
② 在插入和删除过程中,要遵循保持这些颜色的不同排列的规则。
红黑规则:
1. 每一个节点不是红色的就是黑色的
2. 根总是黑色的
3. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定成立)
4. 从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点。(空子节点是指非叶节点可以接子节点的位置。换句话说,就是一个有右子节点的节点可能接左子节点的位置,或是有左子节点的节点可能接右子节点的位置)
(4)、AVL树:AVL树 ,它或者是一颗空二叉树,或者是具有下列性质的二叉树:
① 其根的左右子树高度之差的绝对值不能超过1;
② 其根的左右子树都是二叉平衡树。
AVL树查找的时间复杂度为O(logN),因为树一定是平衡的。但是由于插入或删除一个节点时需要扫描两趟树,依次向下查找插入点,依次向上平衡树,AVL树不如红黑树效率高,也不如红黑树常用。
(5)、Treap
Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap并不一定是。
(6)由来:平衡二叉树是基于二分法的策略提高数据的查找速度的二叉树的数据结构;
(7)特点:
①平衡二叉树是采用二分法思维把数据按规则组装成一个树形结构的数据,用这个树形结构的数据减少无关数据的检索,大大的提升了数据检索的速度;平衡二叉树的数据结构组装过程有以下规则:
②非叶子节点只能允许最多两个子节点存在,每一个非叶子节点数据分布规则为左边的子节点小当前节点的值,右边的子节点大于当前节点的值(这里值是基于自己的算法规则而定的,比如hash值);
③平衡树的层级结构:因为平衡二叉树查询性能和树的层级(h高度)成正比、为了保证树的结构左右两端数据大致平衡降低二叉树的查询难度一般会采用一种算法机制实现节点数据结构的平衡,实现了这种算法的有比如AVL、Treap、红黑树,使用平衡二叉树能保证数据的左右两边的节点层级相差不会大于1.,通过这样避免树形结构由于删除增加变成线性链表影响查询效率,保证数据平衡的情况下查找数据的速度近于二分法查找;
⑻ 总结平衡二叉树特点:
①非叶子节点最多拥有两个子节点;
②非叶子节值大于左边子节点、小于右边子节点;
③树的左右两边的层级数相差不会大于1;
④没有值相等重复的节点;
⑼ 二叉树的优点:
①二叉排序树是一种比较有用的折衷方案。 数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦。 链表与之相反,删除和插入元素很快,但查找很慢。二叉排序树就既有链表的好处,也有数组的好处。 在处理大批量的动态的数据是比较有用。
②文件系统和数据库系统一般都采用树(特别是B树)的数据结构数据,主要为排序和检索的效率。二叉树是一种最基本最典型的排序树,用于教学和研究树的特性,本身很少在实际中进行应用,因为缺点太明显了(看看教科书怎么说的)。就像冒泡排序一样,虽然因为效率问题并不实用,单不失一种教学例子的好手段。
⑽平衡二叉树都有哪些应用:
①二叉树支持动态的插入和查找,保证操作在O(height)时间,这就是完成了哈希表不便完成的工作,动态性。但是二叉树有可能出现worst-case,如果输入序列已经排序,则时间复杂度为O(N)
平衡二叉树/红黑树就是为了将查找的时间复杂度保证在O(logN)范围内。所以如果输入结合确定,所需要的就是查询,则可以考虑使用哈希表,如果输入集合不确定,则考虑使用平衡二叉树/红黑树,保证达到最大效率
②平衡二叉树主要优点集中在快速查找。
如果你知道SGI/STL的set/map底层都是用红黑树(平衡二叉树的一种)实现的,相信你会对这些树大有兴趣。
例题:
Luogu 3369:
代码实现:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int N=5e5+10; 7 int js,t,opt,b,x,y,z,rt; 8 int v[N],p[N],s[N],c[N][2]; 9 inline int read() { 10 int n=0,f=1;char c=getchar(); 11 while (c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} 12 while (c<='9'&&c>='0') {n=(n<<3)+(n<<1)+c-'0';c=getchar();} 13 return n*f; 14 } 15 inline void up_date(int x){ 16 s[x]=s[c[x][0]]+s[c[x][1]]+1; 17 } 18 inline int work(int u){ 19 v[++js]=u; 20 s[js]=1; 21 p[js]=rand(); 22 return js; 23 } 24 inline int merge(int x,int y){ 25 if(!x||!y) return x+y; 26 if(p[x]<p[y]){ 27 c[x][1]=merge(c[x][1],y); 28 up_date(x); 29 return x; 30 } else { 31 c[y][0]=merge(x,c[y][0]); 32 up_date(y); 33 return y; 34 } 35 } 36 inline void IUV(int now,int k,int &x,int &y){ 37 if(!now) x=y=0; 38 else { 39 if(v[now]<=k) x=now,IUV(c[now][1],k,c[now][1],y); 40 else y=now,IUV(c[now][0],k,x,c[now][0]); 41 up_date(now); 42 } 43 } 44 inline int IU(int now,int k){ 45 while(true){ 46 if(k<=s[c[now][0]]) now=c[now][0]; 47 else if(k==s[c[now][0]]+1) return now; 48 else k-=s[c[now][0]]+1,now=c[now][1]; 49 } 50 } 51 int main() { 52 t=read(); 53 rt=0; 54 while(t--) { 55 opt=read(),b=read(); 56 if(opt==1) { 57 IUV(rt,b,x,y); 58 rt=merge(merge(x,work(b)),y); 59 } else if(opt==2){ 60 IUV(rt,b,x,z); 61 IUV(x,b-1,x,y); 62 y=merge(c[y][0],c[y][1]); 63 rt=merge(merge(x,y),z); 64 } else if(opt==3) { 65 IUV(rt,b-1,x,y); 66 printf("%d\n",s[x]+1); 67 rt=merge(x,y); 68 } else if(opt==4) { 69 printf("%d\n",v[IU(rt,b)]); 70 } else if(opt==5) { 71 IUV(rt,b-1,x,y); 72 printf("%d\n",v[IU(x,s[x])]); 73 rt=merge(x,y); 74 } else if(opt==6){ 75 IUV(rt,b,x,y); 76 printf("%d\n",v[IU(y,1)]); 77 rt=merge(x,y); 78 } 79 } 80 return 0; 81 }
Luogu 3391(文艺平衡树):
代码实现:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int N=1e5+10; 7 int fa[N],c[N][2],s[N],w[N]; 8 int n,m,rt,L,R; 9 inline int read() { 10 int n=0,f=1;char ch=getchar(); 11 while (ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();} 12 while (ch<='9'&&ch>='0') {n=(n<<3)+(n<<1)+ch-'0';ch=getchar();} 13 return n*f; 14 } 15 inline void push_up(int x){ 16 s[x]=s[c[x][0]]+s[c[x][1]]+1; 17 } 18 inline void push_down(int x){ 19 if(w[x]){ 20 swap(c[x][0],c[x][1]); 21 w[c[x][0]]^=1,w[c[x][1]]^=1,w[x]=0; 22 } 23 } 24 inline void rotate(int x,int &k){ 25 int y=fa[x],z=fa[y],v; 26 if(c[y][0]==x) v=1; 27 else v=0; 28 if(y==k) k=x; 29 else { 30 if(c[z][0]==y) c[z][0]=x; 31 else c[z][1]=x; 32 } 33 c[y][v^1]=c[x][v],fa[c[y][v^1]]=y; 34 c[x][v]=y,fa[y]=x,fa[x]=z; 35 push_up(x),push_up(y); 36 } 37 inline void splay(int x,int &k){ 38 while(x!=k){ 39 int y=fa[x],z=fa[y]; 40 if(y!=k){ 41 if((c[y][0]==x)^(c[z][0]==y)) rotate(x,k); 42 else rotate(y,k); 43 } 44 rotate(x,k); 45 } 46 } 47 inline void build(int l,int r,int f){ 48 if(l>r) return ; 49 int mid=(l+r)>>1; 50 if(mid<f) c[f][0]=mid; 51 else c[f][1]=mid; 52 fa[mid]=f,s[mid]=1; 53 if(l==r) return ; 54 build(l,mid-1,mid),build(mid+1,r,mid); 55 push_up(mid); 56 } 57 inline int find(int x,int k){ 58 push_down(x); 59 int u=s[c[x][0]]; 60 if(k==u+1) return x; 61 if(k<=u) return find(c[x][0],k); 62 else return find(c[x][1],k-u-1); 63 } 64 inline void work(int l,int r){ 65 int x=find(rt,l),y=find(rt,r+2); 66 splay(x,rt),splay(y,c[x][1]); 67 int z=c[y][0]; 68 w[z]^=1; 69 } 70 int main(){ 71 n=read(),m=read(); 72 rt=(n+3)/2; 73 build(1,n+2,rt); 74 for(int i=1;i<=m;++i){ 75 L=read(),R=read(); 76 work(L,R); 77 } 78 for(int i=2;i<=n+1;++i) printf("%d ",find(rt,i)-1); 79 return 0; 80 }
二、二叉查找树
知识点:
二叉查找树定义:
在二叉树的基础上,每个节点有一个权值,若每个节点满足:
1、其左子树所有权值小于等于自身权值
2、其右子树所有权值大于等于自身权值
二叉查找树满足:按其中序遍历输出权值,那么权值不降。
二叉查找树有一个明显的缺点:
特殊构造的数据能使树的深度达到O(n)级别,也就是每次查询或修改的最坏时间复杂度会达到O(n)。
三、Treap
知识点:
Treap一词来源于Tree(树)与Heap(堆)的结合。
其原理是,对于每个节点,赋予一个随机权值,在构建平衡树时,使得原权值满足二叉查找树的性质,随机权值满足堆的性质,利用随机性,限制树的深度。Treap的优点为常数小。