天天育儿网,内容丰富有趣,生活中的好帮手!
天天育儿网 > 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】...

平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】...

时间:2022-04-08 23:31:56

相关推荐

平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】...

平衡树初阶——AVL平衡二叉查找树

一、什么是二叉树

1.什么是树。

计算机科学里面的树本质是一个树状图。树首先是一个有向无环图,由根节点指向子结点。但是不严格的说,我们也研究无向树。所谓无向树就是将有向树的所有边看成无向边形成的树状图。树是一种递归的数据结构,所以我们研究树也是按照递归的方式去研究的。

2.什么是二叉树。

我们给出二叉树的递归定义如下:

(1)空树是一个二叉树。

(2)单个节点是一个二叉树。

(3)如果一棵树中,以它的左右子节点为根形成的子树都是二叉树,那么这棵树本身也是二叉树。

二、BST

1.什么是二叉排序树。

二叉排序树是一种二叉树,它满足树的中序遍历是有序的。

2.BSTBinary Search Tree)。

二叉查找树是一种最普通的二叉排序树,一般称作BST,也称为B-树。在这棵树中,满足在任意一个子树中,都满足左子树 < 根节点 < 右子树,即该树的中序遍历满足从小到大排序的结果。

3.如何构造一个二叉排序树?

很显然,要想构造一个BST,就必须在插入节点时,满足下面的原则:

(1)如果节点为空,则直接插入到该节点。

(2)如果节点不为空,且要插入的值小于等于当前节点的值,那么则将该节点插入到左子树当中。

(3)如果节点不为空,且要插入的值大于当前节点的值,那么则将该节点插入到右子树当中。

4.利用BST的性质对一个数组进行剃重。

由于BST有二叉排序树的性质,我们可以利用这样的性质对一个待定数组进行剃重。原理很简单,只需要在插入的时候如果已经发现了相等的节点的话,那么则不进行插入即可。也就是说,只有该树没有的节点,我们才进行相应的插入操作。

三、BST的相关操作

1.建树(createTree

BST的建立是基于一个数组进行的,本质上是把数组中的数按顺序插入的树中。可以想象,,每插入一个数,平均时间复杂度为O(logn),所以建树的平均时间复杂度为O(nlogn)

2.查找某一个值dsearchTree

如果我们需要在BST上查找一个值d,那么我们需要从根节点开始,按照下面的思路进行递归查询:

(1)如果当前节点为空,则未找到,返回NULL。

(2)如果当前节点不为空,且当前节点的值等于d,那么则找到,返回当前节点。

(3)如果当前节点不为空,且当前节点的值大于d,那么则递归在左子树中寻找。

(4)如果当前节点不为空,且当前节点的值小于d,那么则递归在右子树中寻找。

可以想象,查找操作的平均时间复杂度为O(logn)

3.删除一个值ddeleteTree

如果我们想要删除一个值d的节点,那么显然首先需要找到该节点。如果没有找到,则删除操作失败,如果找到,继续下面的操作即可:

(1)如果找到的节点的右子树为空,那么直接用该节点的左节点替换当前节点即可。

(2)如果找到的节点的右子树不为空,且右子树的左子树不为空,则递归找该右子树的左子树。

(3)如果找到的节点的右子树不为空,且右子树的左子树为空,则:

①如果找到的该节点的右节点为空,则返回当前节点,用这个节点去替换需要删除的点即可。

②如果找到的该节点的右子树不为空,则首先用该右子树替换找到的节点,在用找到的节点替换需要删除的节点即可。

显然,删除操作的平均时间复杂度为O(logn)

四、AVL平衡二叉查找树

1.什么是平衡二叉树。

平衡二叉树是一种二叉排序树,并且满足树中任意一个节点的左右子树的高度保持平衡。

2.什么是AVL

AVL是一种二叉查找树,并且满足树中任意一个节点的左右子树的高度差的绝对值小于等于1,即保持平衡系数不大于1。AVL也称B-BST(Balanced - Binary Search Tree),而AVL的名称只是与这种数据结构的作者有关。

3.引例:为什么会产生AVL

我们为什么需要研究AVL,换句话说,为什么我们要重视BST的平衡性能呢?我们看下面的一个例子。

我们用1,2,3,4,5,6,7,8,9来进行建树。我们发现,这样建树的结果如下:

可以看出,这样二叉树实际上退化为了一个链表。在最坏的情况下,插入和删除的时间复杂度都退化为了O(n)

很显然,树的平衡性越好,这种退化越不明显。所以为了保持BST的高效,我们研究AVL是必要的。

4.如何保持AVL的平衡?

既然要保持左右子树高度差小于等于1,那么我们一定需要在节点里面定义一个新的属性,用来记录该节点为根的树的高度。由于建树的过程是递归的,所以树的高度的更新也是递归完成的。通过更新高度,我们就可以知道什么时候左右子树的高度差大于1了,这个时候产生了失衡。一旦某一个节点开始失衡,那么这个时候必须通过旋转操作使二叉树达到一个新的平衡。

五、AVL的相关操作

1.旋转操作(rotateAvl

如果在某一个时刻二叉树发生了失衡,我们就需要对二叉树进行相应的旋转使得二叉树重新达到平衡。这个旋转并不是随意的,我们还要保证BST的基本性质,那就是中序遍历必须有序才行。

我们总结二叉树失衡的原因,可以归纳为以下四种情况(其中圆形节点代表失衡有关的节点,方形节点代表子树)

归纳后发现,对于情况(1)(2),都是由于左子树高度大于右子树高度形成的失衡。对于情况(3)(4)则是相反的情况。且情况(1)(3)互为镜像,情况(2)(4)互为镜像。所以我们只以(1)(2)种情况作为例子,(3)(4)情况的道理同出一辙。

对于情况(1),左子树高度大于右子树高度,而在左子树中,依然是左子树高度大于右子树高度。对于这样的情况,我们需要以1为根进行右转(rightRotate),右转的结果是,2变为新的根,而1则成为2的右节点,2原本的右子树则成为了1的左子树,即,如下图:

对于情况(2),左子树高度大于右子树高度,而在左子树中,左子树高度小于右子树高度。对于这样的情况,我们需要首先需要以2(leftRotate)为根进行左转,左转的结果是3变为1的左子树,而2变为3的左子树,而3原本的左子树成了2的右子树。如下图所示:

之后就转化为了情况(1),故只需要在以1为根进行右转即可。

通过这样的旋转操作,AVL重新达到了平衡。

这个旋转操作的时间复杂度是O(1)的。

2.高度更新操作。

由于高度更新是递归进行的,所以我们选择回溯的阶段进行高度更新。而一个节点的高度应该是左子树高度和右子树高度的最大值再加1。

高度更新在整个AVL中是必要的,不管建树过程中,还是插入操作,或者是删除操作中,我们都需要时时刻刻对高度进行更新,只有正确的更新高度,才能判断二叉树是否失衡。而一旦失衡,我们就需要进行上述的旋转操作,这些是相辅相承的。

高度更新的时间复杂度也是O(1)的。

3.插入操作(insertAvl

在插入操作中,由于插入的新节点,有可能使原本的二叉树产生了失衡,故应该进行相应的旋转操作。故,这样插入操作能稳定在平均O(logn)的时间复杂度内。

4.删除操作(deleteAvl

再删除操作中,由于删除了节点,也有可能是原本平衡的二叉树产生失衡,故也应该进行相应的旋转操作。故,这样删除操作能稳定在O(logn)的时间复杂度内。

六、代码实现(C/C++)

1.对于节点数据类型的处理:

1 #define Elem int //这样Elem就是节点中数据的类型。

2.节点结构体的二叉链表

1 typedef struct LNode 2 { 3Elem data; //节点数据域 4int height; //节点为根的树的高度 5struct LNode *left,*right; //左子树和右子树 6struct LNode() //构造函数 7{ 8 height=0; 9 left=right=NULL;10}11 }LNode,*avlTree;12 //这样定义LNode是节点的类型,avlTree则是节点的指针类型。

3.右旋转子操作,返回旋转之后的根节点指针

1 avlTree _rightRotate(avlTree &r) 2 { 3int lh=0,rh=0; 4avlTree t=r->left; 5r->left=t->right; 6if(r->left) lh=r->left->height; 7if(r->right) rh=r->right->height; 8r->height=max(lh,rh)+1; 9t->right=r;10if(t->left==NULL) t->height=1;11else t->height=max(t->left->height,r->height)+1;12return t;13 }

4.左旋转子操作,返回旋转之后的根节点指针

1 avlTree _leftRotate(avlTree &r) 2 { 3int lh=0,rh=0; 4avlTree t=r->right; 5r->right=t->left; 6if(r->left) lh=r->left->height; 7if(r->right) rh=r->right->height; 8r->height=max(lh,rh)+1; 9t->left=r;10if(t->right==NULL) t->height=1;11t->height=max(t->height,r->height)+1;12return t;13 }

5.旋转主算法(发生失衡,按照规则进行旋转操作)

1 void rotateAvl(avlTree &root) 2 { 3int lh=0,rh=0; 4if(root->left) lh=root->left->height; 5if(root->right) rh=root->right->height; 6root->height=max(lh,rh)+1; 7if(abs(lh-rh)>1) 8{ 9 avlTree tp;10 int lp=0,rp=0;11 if(lh>rh) tp=root->left;12 else tp=root->right;13 if(tp->left!=NULL) lp=tp->left->height;14 if(tp->right!=NULL) rp=tp->right->height;15 if(lh>rh&&lp<rp) root->left=_leftRotate(tp);16 if(lh<rh&&lp>rp) root->right=_rightRotate(tp);17 if(lh>rh) root=_rightRotate(root);18 else root=_leftRotate(root);19}20 }

6.插入操作,向二叉树r插入d。这里用sign标记表示是否进行剃重,如果signtrue则剃重,signfalse则表示可重复。

1 void insertAvl(avlTree &r,Elem d,bool sign) 2 { 3//递归出口 4if(r==NULL) { 5 r=new LNode(); 6 r->data=d; 7 r->height++; 8 return; 9}10if(d<=r->data)11{12 if(d==r->data&&sign) return; 13 insertAvl(r->left,d,sign);14}15else 16{17 insertAvl(r->right,d,sign);18}19rotateAvl(r); //检验失衡并进行旋转20 }

7.根据data数组建树。这里用sign标记表示是否进行剃重,如果signtrue则剃重,signfalse则表示可重复。

1 void createAvl(avlTree &root,Elem data[],int n,bool sign)2 {3int i;4root=NULL;5for(i=0;i<n;i++)6{7 insertAvl(root,data[i],sign);8}9 }

8.查询root里面的数据d所在节点,如果查询成功,则返回该节点。若d数据不存在,则查询失败,返回NULL

1 avlTree searchAvl(avlTree root,Elem d) 2 { 3if(root!=NULL) 4{ 5 if(d==root->data) return root; 6 else if(d<root->data) return searchAvl(root->left,d); 7 else searchAvl(root->right,d); 8} 9return NULL;10 }

9.在删除中寻找实际需要删除的点,返回实际点。

1 avlTree _delete(avlTree &root) 2 { 3avlTree ret=root; 4if(root->left) ret=_delete(root->left); 5else if(root->right) 6{ 7 ret=root->right; 8 int t=root->data; 9 root->data=root->right->data;10 ret->data=t;11 root->height=1;12 root->right=NULL;13 return ret;14}15else16{17 root=NULL;18 return ret;19}20rotateAvl(root); //检验失衡并进行旋转21return ret;22 }

10.删除主操作算法,删除root上的data数据所在节点

1 void deleteAvl(avlTree &root,Elem data) 2 { 3avlTree ret; 4if(!root) return; 5if(root->data!=data) //未找到该节点,首先寻找该节点 6{ 7 if(data<root->data) ret=root->left; 8 else ret=root->right; 9 deleteAvl(ret,data); //递归寻找10}11else//找到该节点12{13 if(!root->right) //右子树为空14 {15 root=root->left;16 }17 else //右子树不为空18 {19 avlTree t=_delete(root->right); //寻找实际删除的节点20 root->data=t->data;21 free(t);22 }23}24rotateAvl(root); //检验失衡并进行旋转25 }

于是我又找到了一份不错的模版总结,仅供参考!

Treap树

核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn)

Treap模板:

1 #include <cstdio> 2 #include <cstring> 3 #include <ctime> 4 #include <iostream> 5 #include <algorithm> 6 #include <cstdlib> 7 #include <cmath> 8 #include <utility> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #define max(x,y) ((x)>(y)?(x):(y)) 14 #define min(x,y) ((x)>(y)?(y):(x)) 15 #define INF 0x3f3f3f3f 16 #define MAXN 100005 17 18 using namespace std; 19 20 int cnt=1,rt=0; //节点编号从1开始 21 22 struct Tree 23 { 24int key, size, pri, son[2]; //保证父亲的pri大于儿子的pri 25void set(int x, int y, int z) 26{ 27 key=x; 28 pri=y; 29 size=z; 30 son[0]=son[1]=0; 31} 32 }T[MAXN]; 33 34 void rotate(int p, int &x) 35 { 36int y=T[x].son[!p]; 37T[x].size=T[x].size-T[y].size+T[T[y].son[p]].size; 38T[x].son[!p]=T[y].son[p]; 39T[y].size=T[y].size-T[T[y].son[p]].size+T[x].size; 40T[y].son[p]=x; 41x=y; 42 } 43 44 void ins(int key, int &x) 45 { 46if(x == 0) 47 T[x = cnt++].set(key, rand(), 1); 48else 49{ 50 T[x].size++; 51 int p=key < T[x].key; 52 ins(key, T[x].son[!p]); 53 if(T[x].pri < T[T[x].son[!p]].pri) 54 rotate(p, x); 55} 56 } 57 58 void del(int key, int &x) //删除值为key的节点 59 { 60if(T[x].key == key) 61{ 62 if(T[x].son[0] && T[x].son[1]) 63 { 64 int p=T[T[x].son[0]].pri > T[T[x].son[1]].pri; 65 rotate(p, x); 66 del(key, T[x].son[p]); 67 } 68 else 69 { 70 if(!T[x].son[0]) 71 x=T[x].son[1]; 72 else 73 x=T[x].son[0]; 74 } 75} 76else 77{ 78 T[x].size--; 79 int p=T[x].key > key; 80 del(key, T[x].son[!p]); 81} 82 } 83 84 int find(int p, int &x) //找出第p小的节点的编号 85 { 86if(p == T[T[x].son[0]].size+1) 87 return x; 88if(p > T[T[x].son[0]].size+1) 89 find(p-T[T[x].son[0]].size-1, T[x].son[1]); 90else 91 find(p, T[x].son[0]); 92 } 93 94 int find_NoLarger(int key, int &x) //找出值小于等于key的节点个数 95 { 96if(x == 0) 97 return 0; 98if(T[x].key <= key) 99 return T[T[x].son[0]].size+1+find_NoLarger(key, T[x].son[1]);100else101 return find_NoLarger(key, T[x].son[0]); 102 }

View Code

相关题目:POJ 3481 1442 2352

Splay Tree(伸展树)

核心就是 过程Splay(x, y),即将x节点转移到y节点的子节点上面(其中y是x的祖先)。

利用其中双旋的优势能够保证查询复杂度均摊为O(lgn)

一开始理解有些困难,其实实际上不做深入的理解就是,双旋的过程就是一个建立相对平衡的二叉树的一个过程。

》对于二叉树,最极端的情况就是线性插入,使得整棵二叉树退化为一条链。比如你查询链的最后一个节点,之后再次查询第一个节点。

1)若只是单旋通过Splay(x, 0)将最后一个节点移动到根节点,需要O(n)复杂度,而查询第一个节点时又需要O(n)复杂度,来来往往就退化成一条链了。

2)若是双旋Splay(x, 0)将最后一个节点移动到根节点上时,移动过程中建立起了相对平衡的二叉树,需要O(n),也就是查询第一个节点时,大概是需要O(lgn)复杂度。这就降低了复杂度。可以证明,总的每个操作的均摊复杂度是O(lgn)。

具体证明可以参见 杨思雨《伸展树的基本操作与应用》

I 用于维护单调队列 :(以key为维护对象保证单调)

常用版:(支持相同值)

1 Struct Tree{ 2 3 int key, size, fa, son[2]; 4 5 } 6 7 void PushUp(int x); 8 9 void Rotate(int x, int p); //0左旋 1右旋10 11 void Splay(int x, int To) //将x节点插入到To的子节点中12 13 int find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处14 15 int prev() //返回比根值小的最大值 若无返回0 若有将其转移到根处16 17 int succ() //返回比根值大的最小值 若无返回0 若有将其转移到根处18 19 void Insert(int key) //插入key 并且将该节点转移到根处20 21 void Delete(int key) //删除值为key的节点 若有重点只删其中一个 x的前驱移动到根处22 23 int GetPth(int p) //获得第p小的节点 并将其转移到根处24 25 int GetRank(int key) //获得值<=key的节点个数 并将其转移到根处 若<key只需将<=换为<

模板:

1 int cnt=1, rt=0; 2 3 struct Tree 4 { 5int key, size, fa, son[2]; 6void set(int _key, int _size, int _fa) 7{ 8 key=_key; 9 size=_size; 10 fa=_fa; 11 son[0]=son[1]=0; 12} 13 }T[MAXN]; 14 15 inline void PushUp(int x) 16 { 17T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+1; 18 } 19 20 inline void Rotate(int x, int p) //0左旋 1右旋 21 { 22int y=T[x].fa; 23T[y].son[!p]=T[x].son[p]; 24T[T[x].son[p]].fa=y; 25T[x].fa=T[y].fa; 26if(T[x].fa) 27 T[T[x].fa].son[T[T[x].fa].son[1] == y]=x; 28T[x].son[p]=y; 29T[y].fa=x; 30PushUp(y); 31PushUp(x); 32 } 33 34 void Splay(int x, int To) //将x节点插入到To的子节点中 35 { 36while(T[x].fa != To) 37{ 38 if(T[T[x].fa].fa == To) 39 Rotate(x, T[T[x].fa].son[0] == x); 40 else 41 { 42 int y=T[x].fa, z=T[y].fa; 43 int p=(T[z].son[0] == y); 44 if(T[y].son[p] == x) 45 Rotate(x, !p), Rotate(x, p); //之字旋 46 else 47 Rotate(y, p), Rotate(x, p); //一字旋 48 } 49} 50if(To == 0) rt=x; 51 } 52 53 int find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处 54 { 55int x=rt; 56while(x && T[x].key != key) 57 x=T[x].son[key > T[x].key]; 58if(x) Splay(x, 0); 59return x; 60 } 61 62 int prev() //返回比根值小的最大值 若无返回0 若有将其转移到根处 63 { 64int x=T[rt].son[0]; 65if(!x) return 0; 66while(T[x].son[1]) 67 x=T[x].son[1]; 68Splay(x, 0); 69return x; 70 } 71 72 int succ() //返回比根值大的最小值 若无返回0 若有将其转移到根处 73 { 74int x=T[rt].son[1]; 75if(!x) return 0; 76while(T[x].son[0]) 77 x=T[x].son[0]; 78Splay(x, 0); 79return x; 80 } 81 82 void Insert(int key) //插入key 并且将该节点转移到根处 83 { 84if(!rt) 85 T[rt = cnt++].set(key, 1, 0); 86else 87{ 88 int x=rt, y=0; 89 while(x) 90 { 91 y=x; 92 x=T[x].son[key > T[x].key]; 93 } 94 T[x = cnt++].set(key, 1, y); 95 T[y].son[key > T[y].key]=x; 96 Splay(x, 0); 97} 98 } 99 100 void Delete(int key) //删除值为key的节点 若有重点只删其中一个 x的前驱移动到根处101 {102int x=find(key);103if(!x) return;104int y=T[x].son[0];105while(T[y].son[1])106 y=T[y].son[1];107int z=T[x].son[1];108while(T[z].son[0])109 z=T[z].son[0];110if(!y && !z)111{112 rt=0;113 return;114}115if(!y)116{117 Splay(z, 0);118 T[z].son[0]=0;119 PushUp(z);120 return;121}122if(!z)123{124 Splay(y, 0);125 T[y].son[1]=0;126 PushUp(y);127 return;128}129Splay(y, 0);130Splay(z, y);131T[z].son[0]=0;132PushUp(z);133PushUp(y);134 }135 136 int GetPth(int p) //获得第p小的节点 并将其转移到根处137 {138if(!rt) return 0;139int x=rt, ret=0;140while(x)141{142 if(p == T[T[x].son[0]].size+1)143 break;144 if(p>T[T[x].son[0]].size+1)145 {146 p-=T[T[x].son[0]].size+1;147 x=T[x].son[1];148 }149 else150 x=T[x].son[0];151}152Splay(x, 0);153return x;154 }155 156 int GetRank(int key) //获得值<=key的节点个数 并将其转移到根处 若<key只需将<=换为<157 {158if(!rt) return 0;159int x=rt, ret=0, y;160while(x)161{162 y=x;163 if(T[x].key <= key)164 {165 ret+=T[T[x].son[0]].size+1;166 x=T[x].son[1];167 }168 else169 x=T[x].son[0];170}171Splay(y, 0);172return ret;173 }

View Code

完全版:(支持相同值,支持区间删除,支持懒惰标记)

1 Struct Tree{ 2 3 int key, num, size, fa, son[2]; 4 5 } 6 7 void PushUp(int x); 8 9 void PushDown(int x);10 11 int Newnode(int key, int fa); //新建一个节点并返回12 13 void Rotate(int x, int p); //0左旋 1右旋14 15 void Splay(int x, int To); //将x节点移动到To的子节点中16 17 int GetPth(int p, int To); //返回第p小的节点 并移动到To的子节点中18 19 int Find(int key); //返回值为key的节点 若无返回0 若有将其转移到根处20 21 int Prev(); //返回根节点的前驱22 23 int Succ(); //返回根结点的后继24 25 void Insert(int key); //插入key值26 27 void Delete(int key); //删除值为key的节点28 29 int GetRank(int key); //获得值<=key的节点个数30 31 void Delete(int l, int r); //删除值在[l, r]中的节点

模板:

1 int cnt, rt; 2 int Add[MAXN]; 3 4 struct Tree{ 5int key, num, size, fa, son[2]; 6 }T[MAXN]; 7 8 inline void PushUp(int x) 9 { 10T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+T[x].num; 11 } 12 13 inline void PushDown(int x) 14 { 15if(Add[x]) 16{ 17 if(T[x].son[0]) 18 { 19 T[T[x].son[0]].key+=Add[x]; 20 Add[T[x].son[0]]+=Add[x]; 21 } 22 if(T[x].son[1]) 23 { 24 T[T[x].son[1]].key+=Add[x]; 25 Add[T[x].son[1]]+=Add[x]; 26 } 27 Add[x]=0; 28} 29 } 30 31 inline int Newnode(int key, int fa) //新建一个节点并返回 32 { 33++cnt; 34T[cnt].key=key; 35T[cnt].num=T[cnt].size=1; 36T[cnt].fa=fa; 37T[cnt].son[0]=T[cnt].son[1]=0; 38return cnt; 39 } 40 41 inline void Rotate(int x, int p) //0左旋 1右旋 42 { 43int y=T[x].fa; 44PushDown(y); 45PushDown(x); 46T[y].son[!p]=T[x].son[p]; 47T[T[x].son[p]].fa=y; 48T[x].fa=T[y].fa; 49if(T[x].fa) 50 T[T[x].fa].son[T[T[x].fa].son[1] == y]=x; 51T[x].son[p]=y; 52T[y].fa=x; 53PushUp(y); 54PushUp(x); 55 } 56 57 void Splay(int x, int To) //将x节点移动到To的子节点中 58 { 59while(T[x].fa != To) 60{ 61 if(T[T[x].fa].fa == To) 62 Rotate(x, T[T[x].fa].son[0] == x); 63 else 64 { 65 int y=T[x].fa, z=T[y].fa; 66 int p=(T[z].son[0] == y); 67 if(T[y].son[p] == x) 68 Rotate(x, !p), Rotate(x, p); //之字旋 69 else 70 Rotate(y, p), Rotate(x, p); //一字旋 71 } 72} 73if(To == 0) rt=x; 74 } 75 76 int GetPth(int p, int To) //返回第p小的节点 并移动到To的子节点中 77 { 78if(!rt || p > T[rt].size) return 0; 79int x=rt; 80while(x) 81{ 82 PushDown(x); 83 if(p >= T[T[x].son[0]].size+1 && p <= T[T[x].son[0]].size+T[x].num) 84 break; 85 if(p > T[T[x].son[0]].size+T[x].num) 86 { 87 p-=T[T[x].son[0]].size+T[x].num; 88 x=T[x].son[1]; 89 } 90 else 91 x=T[x].son[0]; 92} 93Splay(x, 0); 94return x; 95 } 96 97 int Find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处 98 { 99if(!rt) return 0;100int x=rt;101while(x)102{103 PushDown(x);104 if(T[x].key == key) break;105 x=T[x].son[key > T[x].key];106}107if(x) Splay(x, 0);108return x;109 }110 111 int Prev() //返回根节点的前驱 非重点112 {113if(!rt || !T[rt].son[0]) return 0;114int x=T[rt].son[0];115while(T[x].son[1])116{117 PushDown(x);118 x=T[x].son[1];119}120Splay(x, 0);121return x;122 }123 124 int Succ() //返回根结点的后继 非重点125 {126if(!rt || !T[rt].son[1]) return 0;127int x=T[rt].son[1];128while(T[x].son[0])129{130 PushDown(x);131 x=T[x].son[0];132}133Splay(x, 0);134return x;135 }136 137 void Insert(int key) //插入key值138 {139if(!rt)140 rt=Newnode(key, 0);141else142{143 int x=rt, y=0;144 while(x)145 {146 PushDown(x);147 y=x;148 if(T[x].key == key)149 {150 T[x].num++;151 T[x].size++;152 break;153 }154 T[x].size++;155 x=T[x].son[key > T[x].key];156 }157 if(!x)158 x=T[y].son[key > T[y].key]=Newnode(key, y);159 Splay(x, 0);160}161 }162 163 void Delete(int key) //删除值为key的节点1个164 {165int x=Find(key);166if(!x) return;167if(T[x].num>1)168{169 T[x].num--;170 PushUp(x);171 return;172}173int y=T[x].son[0];174while(T[y].son[1])175 y=T[y].son[1];176int z=T[x].son[1];177while(T[z].son[0])178 z=T[z].son[0];179if(!y && !z)180{181 rt=0;182 return;183}184if(!y)185{186 Splay(z, 0);187 T[z].son[0]=0;188 PushUp(z);189 return;190}191if(!z)192{193 Splay(y, 0);194 T[y].son[1]=0;195 PushUp(y);196 return;197}198Splay(y, 0);199Splay(z, y);200T[z].son[0]=0;201PushUp(z);202PushUp(y);203 }204 205 int GetRank(int key) //获得值<=key的节点个数206 {207if(!Find(key))208{209 Insert(key);210 int tmp=T[T[rt].son[0]].size;211 Delete(key);212 return tmp;213}214else215 return T[T[rt].son[0]].size+T[rt].num;216 }217 218 void Delete(int l, int r) //删除值在[l, r]中的所有节点 l!=r219 {220if(!Find(l)) Insert(l);221int p=Prev();222if(!Find(r)) Insert(r);223int q=Succ();224if(!p && !q)225{226 rt=0;227 return;228}229if(!p)230{231 T[rt].son[0]=0;232 PushUp(rt);233 return;234}235if(!q)236{237 Splay(p, 0);238 T[rt].son[1]=0;239 PushUp(rt);240 return;241}242Splay(p, q);243T[p].son[1]=0;244PushUp(p);245PushUp(q);246 }

View Code

相关题目:HNOI 2002 POJ3481 POJ2352 POJ1442

NOI 郁闷的出纳员

II 用于维护序列: (以序列下标为对象维护,相当于对区间操作)(能够完成线段树的操作及其不能完成的操作)

1 Struct Tree{ 2 3 int key, sum, size, fa, son[2]; 4 5 } 6 7 支持操作: 8 9 void PushUp(int x);10 11 void PushDown(int x);12 13 int MakeTree(int l, int r, int a[]); //新建一个子树返回根节点14 15 void Rotate(int x, int p); //0左旋 1右旋16 17 void Splay(int x, int To); //将x节点移动到To的子节点中18 19 int Select(int p, int To); //将第p个数移动到To的子节点中 并返回该节点20 21 int Find(int key); //返回值为key的节点 若无返回0 若有将其转移到根处22 23 int Prev(); //返回根节点的前驱24 25 int Succ(); //返回根结点的后继26 27 void Insert(int p, int l, int r, int a[]) //将a[l .. r]的数插入到下标为p后面28 29 void Delete(int l, int r); //删除区间[l, r]中的节点30 31 int Query(int l, int r); //返回[l, r]的和32 33 待补充。。34 35 Size Balance Tree36 37 和上述两种二叉树比起来,SBT可能是最像真正平衡二叉树吧。38 39 SBT能够保证树的高度在lgn,这样对于插入,删除操作都能够准确保证时间复杂度在O(lgn)40 41 Maintain操作事实上理解起来也是挺简单的,至于证明参见CQF神牛的 《SBT》

模版:

1 int cnt, rt; 2 3 struct Tree 4 { 5int key, size, son[2]; 6 }T[MAXN]; 7 8 inline void PushUp(int x) 9 { 10T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+1; 11 } 12 13 inline int Newnode(int key) 14 { 15++cnt; 16T[cnt].key=key; 17T[cnt].size=1; 18T[cnt].son[0]=T[cnt].son[1]=0; 19return cnt; 20 } 21 22 void Rotate(int p, int &x) 23 { 24int y=T[x].son[!p]; 25T[x].son[!p]=T[y].son[p]; 26T[y].son[p]=x; 27PushUp(x); 28PushUp(y); 29x=y; 30 } 31 32 void Maintain(int &x, int p) //维护SBT的!p子树 33 { 34if(T[T[T[x].son[p]].son[p]].size > T[T[x].son[!p]].size) 35 Rotate(!p, x); 36else if(T[T[T[x].son[p]].son[!p]].size > T[T[x].son[!p]].size) 37 Rotate(p, T[x].son[p]), Rotate(!p, x); 38else return; 39Maintain(T[x].son[0], 0); 40Maintain(T[x].son[1], 1); 41Maintain(x, 0); 42Maintain(x, 1); 43 } 44 45 inline int Prev() //返回比根值小的最大值 若无返回0 46 { 47int x=T[rt].son[0]; 48if(!x) return 0; 49while(T[x].son[1]) 50 x=T[x].son[1]; 51return x; 52 } 53 54 inline int Succ() //返回比根值大的最小值 若无返回0 55 { 56int x=T[rt].son[1]; 57if(!x) return 0; 58while(T[x].son[0]) 59 x=T[x].son[0]; 60return x; 61 } 62 63 void Insert(int key, int &x) 64 { 65if(!x) x=Newnode(key); 66else 67{ 68 T[x].size++; 69 Insert(key, T[x].son[key > T[x].key]); 70 Maintain(x, key > T[x].key); 71} 72 } 73 74 bool Delete(int key, int &x) //删除值为key的节点 key可以不存在 75 { 76if(!x) return 0; 77if(T[x].key == key) 78{ 79 if(!T[x].son[0]) 80 { 81 x=T[x].son[1]; 82 return 1; 83 } 84 if(!T[x].son[1]) 85 { 86 x=T[x].son[0]; 87 return 1; 88 } 89 int y=Prev(); 90 T[x].size--; 91 return Delete(T[x].key, T[x].son[0]); 92} 93else 94 if(Delete(key, T[x].son[key > T[x].key])) 95 { 96 T[x].size--; 97 return 1; 98 } 99 }100 101 int GetPth(int p, int &x) //返回第p小的节点102 {103if(!x) return 0;104if(p == T[T[x].son[0]].size+1)105 return x;106if(p > T[T[x].son[0]].size+1)107 return GetPth(p-T[T[x].son[0]].size-1, T[x].son[1]);108else109 return GetPth(p, T[x].son[0]);110 }111 112 int GetRank(int key, int &x) //找出值<=key的节点个数113 {114if(!x) return 0;115if(T[x].key <= key)116 return T[T[x].son[0]].size+1+GetRank(key, T[x].son[1]);117else118 return GetRank(key, T[x].son[0]);119 }

View Code

相关题目:POJ 3481

上述题均为用于测试平衡树基本操作的题目。

提高题:

[NOI]维修数列

[POJ3580]SuperMemo

[HNOI]宠物收养所

在文章的最后贴上一个二叉树专题训练/contest/84416;jsessionid=E73DCD38F70FF141A029A2DB5958B2F1

喜欢的点个赞并订阅我们,我们将会提供最优质的文章供大家学习参考,欢迎大家一起学习QAQ

如果觉得《平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】...》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。