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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了平衡树初阶——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 {
 3     Elem data;   //节点数据域
 4     int height;    //节点为根的树的高度
 5     struct LNode *left,*right;   //左子树和右子树
 6     struct LNode()   //构造函数
 7     {
 8         height=0;
 9         left=right=NULL;
10     }
11 }LNode,*avlTree;
12 //这样定义LNode是节点的类型,avlTree则是节点的指针类型。

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

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

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

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

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

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

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

1 void createAvl(avlTree &root,Elem data[],int n,bool sign)
2 {
3     int i;
4     root=NULL;
5     for(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 {
 3     if(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     }
 9     return NULL;
10 }

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

 1 avlTree _delete(avlTree &root)
 2 {
 3     avlTree ret=root;
 4     if(root->left) ret=_delete(root->left);
 5     else 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     }
15     else
16     {
17         root=NULL;
18         return ret;
19     }
20     rotateAvl(root);   //检验失衡并进行旋转
21     return ret;
22 }

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

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

View Code

相关题目:HNOI 2002 POJ3481 POJ2352 POJ1442

NOI2004 郁闷的出纳员

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 Tree
36 
37 和上述两种二叉树比起来,SBT可能是最像真正平衡二叉树吧。
38 
39 SBT能够保证树的高度在lgn,这样对于插入,删除操作都能够准确保证时间复杂度在O(lgn)
40 
41 Maintain操作事实上理解起来也是挺简单的,至于证明参见CQF神牛的 《SBT》 

模版:

  1 int cnt, rt;
  2 
  3 struct Tree
  4 {
  5     int key, size, son[2];
  6 }T[MAXN];
  7 
  8 inline void PushUp(int x)
  9 {
 10     T[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;
 16     T[cnt].key=key;
 17     T[cnt].size=1;
 18     T[cnt].son[0]=T[cnt].son[1]=0;
 19     return cnt;
 20 }
 21 
 22 void Rotate(int p, int &x)
 23 {
 24     int y=T[x].son[!p];
 25     T[x].son[!p]=T[y].son[p];
 26     T[y].son[p]=x;
 27     PushUp(x);
 28     PushUp(y);
 29     x=y;
 30 }
 31 
 32 void Maintain(int &x, int p) //维护SBT的!p子树
 33 {
 34     if(T[T[T[x].son[p]].son[p]].size > T[T[x].son[!p]].size)
 35         Rotate(!p, x);
 36     else 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);
 38     else return;
 39     Maintain(T[x].son[0], 0);
 40     Maintain(T[x].son[1], 1);
 41     Maintain(x, 0);
 42     Maintain(x, 1);
 43 }
 44 
 45 inline int Prev() //返回比根值小的最大值 若无返回0
 46 {
 47     int x=T[rt].son[0];
 48     if(!x) return 0;
 49     while(T[x].son[1])
 50         x=T[x].son[1];
 51     return x;
 52 }
 53 
 54 inline int Succ() //返回比根值大的最小值 若无返回0
 55 {
 56     int x=T[rt].son[1];
 57     if(!x) return 0;
 58     while(T[x].son[0])
 59         x=T[x].son[0];
 60     return x;
 61 }
 62 
 63 void Insert(int key, int &x)
 64 {
 65     if(!x) x=Newnode(key);
 66     else
 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 {
 76     if(!x) return 0;
 77     if(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     }
 93     else
 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 {
103     if(!x) return 0;
104     if(p == T[T[x].son[0]].size+1)
105         return x;
106     if(p > T[T[x].son[0]].size+1)
107         return GetPth(p-T[T[x].son[0]].size-1, T[x].son[1]);
108     else
109         return GetPth(p, T[x].son[0]);
110 }
111 
112 int GetRank(int key, int &x) //找出值<=key的节点个数
113 {
114     if(!x) return 0;
115     if(T[x].key <= key)
116         return T[T[x].son[0]].size+1+GetRank(key, T[x].son[1]);
117     else
118         return GetRank(key, T[x].son[0]);
119 }

View Code

相关题目:POJ 3481

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

提高题:

[NOI2005]维修数列

[POJ3580]SuperMemo

[HNOI2004]宠物收养所

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

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

以上是关于平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板超详解的主要内容,如果未能解决你的问题,请参考以下文章

AVL树(平衡二叉查找树)

平衡二叉查找树AVL

二叉树初阶OJ题

数据结构14.自平衡二叉查找树_AVL树

AVL平衡二叉树

平衡二叉树(AVL树)