splay也是平衡树的一种,二叉搜索树的性质splay也都有
隶属于splay的特殊性质:
1.每次操作都要把被操作的点转到根上,以保证随机性
2.很难被卡
3.对于一棵树,你越splay,这棵树就越平衡
所以spaly应用范围很广
下面以文艺平衡树为例
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 #define MAXN 1000007 5 #define INF 1<<30 6 struct ziji{ 7 int f,sub_size,cnt,val,tag,son[2]; 8 //tag打反转标记 9 //s[i].sub_size代表以i为根的子树的大小 10 //f是每个节点的父亲 11 //cnt记录自己被重复统计了几次 12 //son[1]右孩子,son[0]左孩子 13 }s[MAXN]; 14 int ori[MAXN],root,wz,n,m,x,y; 15 //ori[i]是放置元素的储存值 16 inline bool which(int x){ 17 return x==s[s[x].f].son[1]; 18 //判断当前节点和爸爸关系 19 //右子树就return true else return false 20 } 21 inline void update(int x){ 22 if(x){ 23 s[x].sub_size=s[x].cnt; 24 if(s[x].son[0]) s[x].sub_size+=s[s[x].son[0]].sub_size; 25 if(s[x].son[1]) s[x].sub_size+=s[s[x].son[1]].sub_size; 26 } 27 //更新这个节点的sub_size值 28 //其实就是自己被提及的次数加上左右子树的大小 29 } 30 inline void pushdown(int x){ 31 //我们在反转时,选择了以打标记的形式 32 //所以在询问或者做某些改变父子关系时就要像线段树一样下放标记 33 //我么可以通过画图的形式得知,这棵树的中序遍历就是这个序列 34 //所以反转序列就相当于把左右两棵子树反转 35 if(x&&s[x].tag){ 36 //如果这个点存在且这个点有标记 37 s[s[x].son[1]].tag^=1; 38 s[s[x].son[0]].tag^=1; 39 //左右子树要反转,所以标记也要变 40 swap(s[x].son[1],s[x].son[0]); 41 s[x].tag=0;//这个点标记下放完了 42 } 43 } 44 inline void rotate(int x){//双旋 45 int fnow=s[x].f,ffnow=s[fnow].f; 46 //存一下爸爸和爷爷 47 pushdown(x),pushdown(fnow); 48 //父子关系变了,就要下放标记 49 bool w=which(x);//看父子间关系 50 s[fnow].son[w]=s[x].son[w^1]; 51 //0^1=1,1^1=0,0^0=1,所以w^1可以很好的转变子树关系 52 s[s[fnow].son[w]].f=fnow; 53 s[fnow].f=x;s[x].f=ffnow; 54 s[x].son[w^1]=fnow; 55 //具体的旋转操作(一次) 56 if(ffnow) s[ffnow].son[s[ffnow].son[1]==fnow]=x; 57 //如果有爷爷,并且爷爷的右孩子就是爸爸 58 //那么x成为爷爷的右孩子,否则就是左孩子 59 update(fnow);//更新,维护性质 60 } 61 inline void splay(int x,int goal){//伸展 62 for(register int qwq;(qwq=s[x].f)!=goal;rotate(x)){ 63 if(s[qwq].f!=goal)//如果x的爷爷不是我们想要的旋转目标 64 rotate(which(x)==which(qwq)?qwq:x); 65 //就开始向上旋转 66 //旋转规则:孩子和父亲在同一方向按孩子双旋 67 //否则就按父亲双旋 68 } 69 if(goal==0) root=x;//如果目标成为0了,即你已经转到整棵树的根了,你就停止 70 } 71 inline int build_tree(int l,int r,int fa){ 72 if(l>r) return 0;//平常的建树 73 int mid=l+r>>1,now=++wz; 74 s[now].f=fa;s[now].son[0]=s[now].son[1]=0; 75 s[now].cnt++;s[now].val=ori[mid];s[now].sub_size++; 76 s[now].son[1]=build_tree(mid+1,r,now); 77 s[now].son[0]=build_tree(l,mid-1,now); 78 update(now);return now; 79 } 80 inline int find(int x){//按照排名找数值 81 int now=root; 82 while(1){ 83 pushdown(now);//你要找的是现在的状态,所以要下放标记 84 if(x<=s[s[now].son[0]].sub_size) now=s[now].son[0]; 85 else{//如同替所有平衡树的寻找方法 86 x-=s[s[now].son[0]].sub_size+1; 87 if(!x) return now;now=s[now].son[1]; 88 //一直找,直到你找到,返回排名 89 } 90 } 91 } 92 inline void reverse(int x,int y){//真.反转区间 93 int l=x-1,r=y+1;l=find(l),r=find(r); 94 //要反转区间,你要反转的区间很可能不是恰好属于一个根的两个区间 95 //所以这时,你要想办法把这个区间变为一个根的两个子树,然后反转子树 96 //这时,你可以找到一条可以包含这个区间的最小的链,即[x-1,y+1] 97 splay(l,0);splay(r,l); 98 // 当l成为根,r成为根下面的点时,原本要翻的区间就是一棵子树 99 // 由于中序遍历是序列的性质,你可以发现,当你这么干时,他就会成为左右子树 100 int pos=s[root].son[1]; 101 pos=s[pos].son[0];s[pos].tag^=1; 102 //标记最初打在操作区间的根节点上 103 //但此时你并没有真的反转,由上面下放标记的函数可以知道 104 } 105 inline void dfs(int now){ 106 pushdown(now);//按照中序遍历输出序列 107 if(s[now].son[0]) dfs(s[now].son[0]); 108 if(s[now].val!=-INF&&s[now].val!=INF) printf("%d ",s[now].val); 109 if(s[now].son[1]) dfs(s[now].son[1]); 110 } 111 int main(){ 112 scanf("%d%d",&n,&m); 113 ori[1]=-INF,ori[n+2]=INF; 114 //-INF&&INF相当于两个标兵,其中ori[1] 就是不存在的,但是可以方便你旋转的超级根 115 //INF就是防止你在区间反转时 没有r+1 116 for(register int i=1;i<=n;i++) ori[i+1]=i;//序列的初始化 117 root=build_tree(1,n+2,0); 118 for(register int i=1;i<=m;i++) scanf("%d%d",&x,&y),reverse(x+1,y+1); 119 dfs(root); 120 }
然后就是普通平衡树为例
#include<iostream> #include<cstdio> using namespace std; const int MAXL=100005; const int INF=2147480000; //你要用到的数字 struct node{ int v,father; int ch[2];//判断是右孩子(1)还是左孩子(0) int sum;//自己和自己下面还有多少节点 int recy;//自己重复出现了几次(元素) }e[100001];//左大右 int n,points;//使用了多少节点,出现了几个元素 //节点每种元素只有一个,但是每种元素都可以有很多个 int root=e[0].ch[1];//根节点是超级根的右孩子 //全局变量 void update(int x){ e[x].sum=e[e[x].ch[0]].sum+e[e[x].ch[1]].sum+e[x].recy; } //维护树的性质 inline int identify(int x){ return e[e[x].father].ch[0]==x?0:1; }//判断自己是父节点的左孩子还是右孩子 //以上函数在下面的函数中都会用到 void connect(int x,int f,int son){ e[x].father=f; e[f].ch[son]=x; }//作用:将x连接在f的下方。连接方向由son的值决定。 //连接函数。用法:connect(son,father,左儿子(0)或右儿子(1)) void rotate(int x){ int y=e[x].father; int mroot=e[y].father; int mrootson=identify(y); int yson=identify(x); int B=e[x].ch[yson^1]; connect(B,y,yson);connect(y,x,(yson^1));connect(x,mroot,mrootson); update(y);update(x); }//旋转函数 void splay(int at,int to){ //伸展函数,splay精髓 to=e[to].father; //每次都好引用,连接时直接当原本to的父节点(感觉自己在说废话) while(e[at].father!=to){ //直到at到了原本to的位置才停 int up=e[at].father; if(e[up].father==to) rotate(at); //此时at再向上一下就到了 else if(identify(up)==identify(at)){//at,to是同一方向上的子树 rotate(up);rotate(at); } else{//at,to不是同一方向上的子树 rotate(at);rotate(at); } } } //connct和rotate隶属于splay int crepoint(int v,int father){//新加一个节点 n++;e[n].v=v; e[n].father=father; e[n].sum=e[n].recy=1; return n; } void destroy(int x){//删除节点 彻底地 e[x].v=e[x].ch[0]=e[x].ch[1]=e[x].sum=e[x].father=e[x].recy=0; if(x==n) n--; } //以上函数隶属于插入元素(注意是元素的插入)与删除元素->建树函数 //(注意以上两个函数是对节点的操作) int find(int v) { int now=root; while(true){//一直找 if(e[now].v==v){ splay(now,root); return now; } int next=v<e[now].v?0:1; if(!e[now].ch[next]) return 0; now=e[now].ch[next]; } } //找到特定值及其编号,每次找到后splay以保证树的随机性、 int build(int v){//内部调用的插入函数,没有splay points++;//元素多了一个 if(n==0){//特判无点状态 root=1;crepoint(v,0); } else{ int now=root; while(true){//向下找到一个空节点 e[now].sum++;//自己的下级肯定增加了一个节点 if(v==e[now].v){ e[now].recy++;//这个值又出现了一次,在原地有俩点了 return now;//不用再找了 } int next=v<e[now].v?0:1;//左孩子,右孩子 if(!e[now].ch[next]){ crepoint(v,now);e[now].ch[next]=n; return n; } now=e[now].ch[next]; } } return 0; } //建树函数隶属于插入节点(是节点)和删除节点(是节点) void push(int v){//增加节点 int add=build(v);splay(add,root); } void pop(int v){//删除节点 int deal=find(v);//出现过么 if(!deal) return; points--;//删去了一个元素 if(e[deal].recy>1){//这种元素有不止一个 e[deal].recy--;e[deal].sum--; return; } if(!e[deal].ch[0]){//要删去的没有左孩子 root=e[deal].ch[1];//右孩子当根 e[root].father=0;//爸爸没儿子了 } else{ //有左孩子,把左孩子中子树的最大当为左孩子的值 //把原本根的右孩子当为现在左孩子的右孩子,再让左孩子当根 int lef=e[deal].ch[0]; while(e[lef].ch[1]) lef=e[lef].ch[1]; splay(lef,e[deal].ch[0]); int rig=e[deal].ch[1]; connect(rig,lef,1);connect(lef,0,1); update(lef); } destroy(deal); } int rank(int v){//获取值为v的元素在这棵树里是第几小 int ans=0,now=root; while(true){ if(e[now].v==v) return ans+e[e[now].ch[0]].sum+1; if(now==0) return 0; if(v<e[now].v) now=e[now].ch[0]; else{ ans=ans+e[e[now].ch[0]].sum+e[now].recy; now=e[now].ch[1]; } } if(now) splay(now,root); return 0; } int atrank(int x){//获取第x小的元素的值 if(x>points) return -INF;//如果没有,就要返回无限小 int now=root; while(true){ int minused=e[now].sum-e[e[now].ch[1]].sum; if(x>e[e[now].ch[0]].sum&&x<=minused) break; //如果这个排名大于左子树可又到不了右子树,就直接返回e[now].v if(x<minused) now=e[now].ch[0];//反之,就去左子树找 else{ x=x-minused;//如果都不是 ,就去右子树找 now=e[now].ch[1]; } } splay(now,root); return e[now].v; } //无论是第K小的数,还是 k是第几小 都要在找完后splay int upper(int v){//寻找前驱 int now=root; int result=INF; while(now){ if(e[now].v>v&&e[now].v<result) result=e[now].v; if(v<e[now].v) now=e[now].ch[0]; else now=e[now].ch[1]; } return result; } int lower(int v){//寻找该值对应的一个最近的下界值 int now=root; int result=-INF; while(now){ if(e[now].v<v&&e[now].v>result) result=e[now].v; if(v>e[now].v) now=e[now].ch[1]; else now=e[now].ch[0]; } return result; }