伸展树基础(Splay)

3224: Tyvj 1728 普通平衡树

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 3948  Solved: 1627
[Submit][Status][Discuss]

Description

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

Input

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

Output

对于操作3,4,5,6每行输出一个数,表示对应答案

Sample Input

10

1 106465

4 1

1 317721

1 460929

1 644985

1 84185

1 89851

6 81968

1 492737

5 493598

Sample Output

106465

84185

492737

HINT

1.n的数据范围:n<=100000

2.每个数的数据范围:[-1e7,1e7]

以下代码是模板式题解,并有详细注释。。。

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<iostream>
  4 #include<algorithm>
  5 #include<cmath>
  6 #include<cstring>
  7 #include<queue>
  8 using namespace std;
  9 struct splay
 10 {
 11     int data,lc,rc,fa;
 12     int size;//子树的点,包括自己
 13 }a[100005];
 14
 15 int q,root=0,tot=0;
 16
 17 void update(int x){//算x点的size值
 18     a[x].size=a[a[x].lc].size+a[a[x].rc].size+1;//注意:一定要+1,即算上自己本身
 19 }
 20
 21 void r_rotate(int x){//让x右旋
 22     /*
 23     右旋:
 24     条件:右旋出现在x,y之间,x是y的左儿子
 25     目标:x变为父亲,y变为儿子(一定是右儿子)
 26     步骤:
 27     1.如果x有有儿子,使y的左儿子(原来是x)变成x的右儿子(现在x的右儿子是y)
 28     2.x的父亲变成y原来的父亲,并更新父亲指针
 29     3.更新x与y的父亲儿子指针和size值
 30     */
 31     int y=a[x].fa;//y是x的父亲
 32     //步骤1:
 33     a[y].lc=a[x].rc;//换儿子
 34     if(a[x].rc!=0)//如果x的原右儿子不为空:
 35         a[a[x].rc].fa=y;//使x的原右儿子父亲为y
 36     //步骤2:
 37     a[x].fa=a[y].fa;//换父亲
 38     if (y==a[a[y].fa].lc)//如果y是其父亲的左儿子
 39         a[a[y].fa].lc=x;
 40     else//如果y是其父亲的右儿子
 41         a[a[y].fa].rc=x;
 42     //步骤3:
 43     a[y].fa=x;
 44     a[x].rc=y;//注意:右旋y一定变成x的右儿子
 45     update(y);//更新y的size
 46     update(x);//更新x的size
 47 }
 48 void l_rotate(int x){//左旋方式同右旋
 49     int y=a[x].fa;
 50     a[y].rc=a[x].lc;
 51     if (a[x].lc!=0) a[a[x].lc].fa=y;
 52     a[x].fa=a[y].fa;
 53     if (y==a[a[y].fa].lc) a[a[y].fa].lc=x;
 54     else a[a[y].fa].rc=x;
 55     a[y].fa=x;
 56     a[x].lc=y;
 57     update(y);
 58     update(x);
 59 }
 60 void splay(int x,int s){//将x旋到s的下方
 61     /**/
 62     while (a[x].fa!=s){
 63         if (x==a[a[x].fa].lc)//如果x是其父亲的左儿子
 64             r_rotate(x);//右旋
 65         else//如果x是其父亲的右儿子
 66             l_rotate(x);//左旋
 67     }
 68     update(x);//旋之后更新x的size
 69     if (s==0)//将x旋到0的下方,x变成了根
 70         root=x;
 71 }
 72
 73 int Search(int w){//要插入点的data
 74     int p,x=root;
 75     while(x){
 76         p=x;
 77         //二叉搜索树性质:对于任意节点x,其左子树节点的data小于x的data
 78         //                               其右子树节点的data大于x的data
 79         if (a[x].data>w)//当前节点data大于要插入的data,所以要插入的一定在左子树
 80         x=a[x].lc;
 81
 82         else x=a[x].rc;//否则在右子树
 83     }
 84     return p;
 85 }
 86
 87 void New_Node(int &x,int fa,int key){//对于一个新节点x,使其左右孩子都为0,父亲
 88                      //为fa,节点值为key
 89     x=++tot;//使root=tot
 90     a[x].lc=a[x].rc=0;
 91     a[x].fa=fa;
 92     a[x].data=key;
 93 }
 94 void Insert(int w)//插入操作 插入一个data为w的节点,使其满足搜索二叉树的性质
 95 {
 96     if (root==0)//说明没有根节点,插入根节点
 97     {
 98         New_Node(root,0,w);
 99         return;
100     }
101     //如果不是根节点
102     int i=Search(w);//找到要插到的父亲节点
103     if (w<a[i].data)//要插入的节点的data比其父节点小,所以作为左而子
104         New_Node(a[i].lc,i,w);
105         //否则作为右儿子
106     else New_Node(a[i].rc,i,w);
107
108         splay(tot,0);//将当前新节点旋转至0的下方,即旋转至根(保证中序遍历不变)
109 }
110 int Get(int w){//寻找一个data为w的节点,利用二叉搜索树:右子树.data>节点.data>左子树.data
111     int x=root;
112     int ans=tot+1;
113     while(x!=0){
114         if(a[x].data>w){
115         x=a[x].lc;
116         continue;
117         }
118         if(a[x].data<w){
119         x=a[x].rc;
120         continue;
121         }
122         if(a[x].data==w){
123             ans=x;
124             x=a[x].lc;
125         }
126     }
127     if (ans==tot+1) return -1;
128     return ans;
129 }
130
131 int Getmax(int x){//返回以x为根的树的最大data的节点号
132     while(a[x].rc!=0)
133         x=a[x].rc;
134     return x;
135 }
136
137 int Getmin(int x){//返回以x为根的树的最小data的节点号
138     while(a[x].lc!=0)
139         x=a[x].lc;
140     return x;
141 }
142
143 int Getpre(int x){
144     return Getmax(a[root].lc);
145 }
146
147 int Getne(int x){
148     return Getmin(a[root].rc);
149 }
150
151 void Delet(int w){//删除操作 删除一个data为w的节点,使其满足搜索二叉树的性质
152 /*
153     步骤:
154         1.将要删除的节点移至根。
155         2.查找L的最大节点,此时,L的根没有右子树。
156         3.查找R的最小节点
157         4.删除根,剩下两个子树L(左子树)和R(右子树)。
158 使R成为L的根的右子树。
159 */
160 //    步骤1:
161     int x=Get(w);
162     splay(x,0);//旋至根
163     //步骤2:
164     int pp=Getpre(x);
165     //步骤3:
166     int nn=Getne(x);
167
168     splay(pp,0);//把以x左孩子为根的最大值节点旋至树根
169     splay(nn,root);//把以x右孩子为根的最小值节点旋至树根下面
170     //步骤4:
171     int y=a[x].fa;
172     a[x].fa=0;
173     if (x==a[y].lc) a[y].lc=0;
174     else a[x].lc=0;
175     update(y);
176     update(root);
177 }
178
179 int Find(int w){//返回比w小的节点的个数
180     int x=Get(w);
181     splay(x,0);
182     return a[a[x].lc].size;
183 }
184
185 int Findkth(int x,int k){//在以x节点为根的树中,找第k大
186     int s=a[a[x].lc].size;
187     if (k==s+1) return a[x].data;
188     if (s>=k) return Findkth(a[x].lc,k);
189     else return Findkth(a[x].rc,k-s-1);
190 }
191
192 int getpre(int w){
193     int y=Get(w);
194     Insert(w);
195     if (y!=-1) splay(y,0);
196     int ans=Getmax(a[root].lc);
197     Delet(w);
198     return a[ans].data;
199 }
200
201 int getne(int w){
202     Insert(w);
203     int ans=Getmin(a[root].rc);
204     Delet(w);
205     return a[ans].data;
206 }
207 int main(){
208     root=tot=0;
209     Insert(-50000000);
210     Insert(50000000);
211     scanf("%d",&q);//操作次数
212     while (q--){
213         int x,k;
214         scanf("%d%d",&x,&k);
215         if (x==1) Insert(k);
216         else if (x==2) Delet(k);
217         else if (x==3) printf("%d\n",Find(k));
218         else if (x==4) printf("%d\n",Findkth(root,k+1));
219         else if (x==5) printf("%d\n",getpre(k));
220         else if (x==6) printf("%d\n",getne(k));
221     }
222     return 0;
223 }
时间: 2024-10-12 18:25:18

伸展树基础(Splay)的相关文章

伸展树基础(2)-12.1

在X插入时,展开使得X成为新的根. 查找X时,也要对X或者是因为没找到而路径上最后一个节点进行展开. 初步想法是沿着根往下进行一次遍历,以及而后从底向上的一次遍历.这样太麻烦了啊. 所以本文介绍自顶向下的伸展树:在初始访问路径上就进行一次次的旋转. 我们设X为中间树的根,L存放树T中小于X中的节点,但不存放X的子树的节点,R同理.初始化时X为T的根,L和R为空树 我们设展开函数为Splay(int Item,Position X),也就是说如果Item在X中,那么Item要变成新的根:不在的话,

算法学习:伸展树(splay)

[定义] [平衡树] 每个叶子结点的深度差不超过1的二叉树 [伸展树] [常用问题] splay的操作,通过左旋右旋,将某个结点通过旋转旋转至根节点,使树的结构发生变化,尽可能的平衡 并且因为左旋右旋的性质,当原树是一个二叉排序树的时候,splay依旧能够使原树保持二叉排序树的性质 左旋右旋图片 [模板题] [luogu P3369]普通平衡树 [题意]实现一颗二叉排序树的增删查改 [注]对数据结构的理解见注释 [代码] #include<cstdio> #include<iostrea

伸展树基础(1)

伸展树是:任意M次对树的操作最多花费O(MlogN)的时间. 摊还时间: 如果M次操作运行O(MF),那么摊还时间就是O(F) 展开: 分为三种情况: ①X节点的爸爸就是根节点那么旋转一次即可咯 ②X有爷爷,且为左左左或右右右所谓的zig-zig一字型,那么先对k1-k2转,再对k2-k3转, ③X有爷爷,且为之字型(zig-zag),执行双旋转即可. 展开操作不仅使X变为根节点,其他大部分节点深度也减少了一半.  

伸展树(Splay)复杂度证明

本文用势能法证明\(Splay\)的均摊复杂度,对\(Splay\)的具体操作不进行讲述. 为了方便本文的描述,定义如下内容: 在文中我们用\(T\)表示一棵完整的\(Splay\),并(不严谨地)用\(|T|\)表示\(T\)这棵\(Splay\)的节点数目. 如无特殊说明,小写英文字母(如\(x\),\(y\),\(z\))在本文中表示\(T\)的一个节点,并(不严谨地)用\(|x|\)表示以节点\(x\)为根的子树的大小,\(x\in T\)表示节点\(x\)在\(T\)中. 一般我们默认

[转] Splay Tree(伸展树)

好久没写过了,比赛的时候就调了一个小时,差点悲剧,重新复习一下,觉得这个写的很不错.转自:here Splay Tree(伸展树) 二叉查找树(Binary Search Tree)能够支持多种动态集合操作.因此,在信息学竞赛中,二叉排序树起着非常重要的作用,它可以被用来表示有序集合.建立索引或优先队列等. 作用于二叉查找树上的基本操作的时间是与树的高度成正比的.对一个含n各节点的完全二叉树,这些操作的最坏情况运行时间为O(log n).但如果树是含n个节点的线性链,则这些操作的最坏情况运行时间

浅谈伸展树(Splay)

//本文是一个暂时的小记,有不对的请大佬们指出~ 真正大佬的在这http://blog.csdn.net/clove_unique/article/details/50630280 伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由丹尼尔·斯立特Daniel Sleator和罗伯特·恩卓·塔扬Robert Endre Tarjan在1985年发明的. 在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作

数据结构-伸展树

声明:本文是对某高中生的竞赛论文学习的文章 介绍: 二叉查找树能够支持多种动态集合操作.对于一个含有n个结点的完全二叉树,这些操作的最还情况运行时间是O(lgn),但如果树是含有n个结点的线性链,则这些操作的最坏情况运行时间为O(n).而像红黑树.AVL树这种二叉查找树的变形在最坏情况下,仍能保持较好性能. 本文将要介绍的伸展树也是二叉查找树的变形,它对空间要求及编程难度的要求相对不高. 伸展树: 伸展树与二叉查找树一样,具有有序性.即伸展树的每一个结点x满足:该结点的左子树中的每个元素都小于x

查找——清晰图解伸展树SplayTree

伸展树 伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它由Daniel Sleator和Robert Tarjan创造,后者对其进行了改进. 假设想要对一个二叉查找树执行一系列的查找操作.为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置.于是想到设计一个简单方法,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方.splaytree应运而生.splaytree是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转

平衡二叉树之伸展树

伸展树(Splay Tree),或者叫自适应查找树,插入.查找和删除操作的时间都为O(logn). 伸展树的目的是使被查频率高的那些条目就应当经常处于靠近树根的位置.它的做法是在每次查找后,将被查找的节点splay到根节点. 使用伸展树需要符合90-10法则:在实际情况中,90%的访问发生在10%的数据上. 优点:不需要记录用于平衡树的冗余信息 伸展树节点结构: struct SplayTree { int value; int lchild; //左儿子在数组中的下标 int rchild;