平衡树-Splay
今天是写平衡树的一天,准备仅使用今天一天的时间了解一下这个模板,不至于比赛的时候出模板题也写不上来
平衡树是一个很巧妙的数据结构,各种旋转全都是智慧
Splay与Treap相比各有其巧妙的地方,今天使用题目测试了一下,速度不相上下,相对于我自己而言,更喜欢Treap,小白不求甚解,不知道两者之间有什么精妙的区别
Splay是通过每次插入之或者修改值的时候对树都进行一次操作,每次都使得树尽可能的均衡,来达到降低时间复杂度的
每次操作,都将现在查询的节点调到根节点上,就像是搜索引擎一样,对于访问次数多的点就会更快
左旋右旋依然不改变树的平衡性
附上自己的模板
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int Maxn = 1000006; struct Tree { int num , key , size , fa ,son[2]; ///num记录键值等于key的值有多少个 size记录的是子树的节点数目 fa记录的是父亲节点的标号 /* 不通过随机值来保持平衡树均衡 而是每次询问都进行双旋 像搜索引擎一样 询问次数多的就在上面 使得总的复杂度减小 双旋的过程中使得平衡树均衡 第一次询问的复杂度较高 平均下来就是log级别的 */ } T[Maxn]; int cnt , root; int Add[Maxn]; void init() { root = 0; cnt = 1; } void PushUp(int x) { T[x].size = T[T[x].son[0]].size + T[T[x].son[1]].size+T[x].num; } void PushDown(int x) { if(Add[x]) { if(T[x].son[0]) { T[T[x].son[0]].key += Add[x]; Add[T[x].son[0]] += Add[x]; } if(T[x].son[1]) { T[T[x].son[1]].key += Add[x]; Add[T[x].son[1]] += Add[x]; } Add[x] = 0; } } void Rotate(int x , int p) //0左旋 1右旋 { int y = T[x].fa; PushDown(y); PushDown(x); T[y].son[!p] = T[x].son[p]; T[T[x].son[p]].fa = y; T[x].fa = T[y].fa; if( T[x].fa ) T[T[x].fa].son[T[T[x].fa].son[1]==y]=x; T[x].son[p] = y; T[y].fa = x; PushUp(y); PushUp(x); } void Splay(int x , int to) ///将标号是x的节点移动到标号是to的子节点上 { while( T[x].fa!=to ) { if(T[T[x].fa].fa == to) { Rotate(x , T[T[x].fa].son[0]==x); } else { int y = T[x].fa; int z = T[y].fa; int p = (T[z].son[0]==y); if(T[y].son[p]==x) { Rotate(x , !p); Rotate(x , p); } //之字旋转 else { Rotate(y , p); Rotate(x , p); } //一字旋转 } } if(to == 0) root = x; } int Newnode(int key , int fa) ///新建一个节点 { ++cnt; T[cnt].fa = fa; T[cnt].key = key; T[cnt].num = T[cnt].size = 1; T[cnt].son[0] = T[cnt].son[1] = 0; return cnt; } void Insert(int key) ///插入键值为key的节点 { if( !root ) { root = Newnode(key , 0); } else { int now = root , y = 0; while( now ) { PushDown(now); y = now; if(T[now].key == key) { T[now].num++; T[now].size++; break; } T[now].size++; now = T[now].son[key>T[now].key]; } if(!now) { now = T[y].son[key>T[y].key] = Newnode(key,y); } Splay(now,0); //插入新节点旋转到根节点上来 } } int GetPth(int p , int to) ///找到第p小的节点的标号 若无返回0 并移动到to的子节点上来 { if(root==0 || p>T[root].size) return 0; int x = root; while(x) { PushDown(x); if( p>=T[T[x].son[0]].size+1 && p<=T[T[x].son[0]].size+T[x].num) break; if(p > T[T[x].son[0]].size+T[x].num) { p -= T[T[x].son[0]].size+T[x].num; x = T[x].son[1]; } else { x = T[x].son[0]; } } Splay(x , to); return x; } int Find(int key) ///返回键值是key的节点的标号 若无返回0 若有转移到根处 { if(root == 0) return 0; int x = root; while( x ) { PushDown(x); if(T[x].key == key) break; x = T[x].son[key>T[x].key]; } if(x) Splay(x , 0); return x; } void Del(int key) ///删除键值是key的节点1个 { int x =Find(key); if( !x ) return ; if( T[x].num > 1) { T[x].num--; PushUp(x); return; } int y = T[x].son[0]; while( T[y].son[1] ) y = T[y].son[1]; int z = T[x].son[1]; while( T[z].son[0] ) z = T[z].son[0]; if(!y && !z) { root = 0; return ; } if( !y ) { Splay(z , 0); T[z].son[0] = 0; PushUp(z); return ; } if( !z ) { Splay(y , 0); T[y].son[1] = 0; PushUp(y); return ; } Splay(y , 0); Splay(z , y); T[z].son[0] = 0; PushUp(z); PushUp(y); } int GetRank(int key) ///获得键值<=key的节点的个数 { if( !Find(key) ) { Insert(key); int tmp = T[T[root].son[0]].size; Del(key); return tmp; } else { return T[T[root].son[0]].size+T[root].num; } } int Prev() ///返回根节点的前驱(不重要) { if(!root || !T[root].son[0]) return 0; int x = T[root].son[0]; while( T[x].son[1] ) { PushDown(x); x = T[x].son[1]; } Splay(x , 0); return x; } int Succ() { if(!root || !T[root].son[1]) return 0; int x = T[x].son[1]; while( T[x].son[0] ) { PushDown(x); x = T[x].son[0]; } Splay(x , 0); return x; } void DelSeq(int l , int r) ///删除键值在[l,r]中的所有节点 l!=r { if( !Find(l) ) Insert(l); int p = Prev(); if( !Find(r) ) Insert(r); int q = Succ(); if(!p &&!q) { root = 0; return ; } if( !p ) { T[root].son[0] = 0; PushUp(root); return ; } if( !q ) { Splay(p , 0); T[root].son[1] = 0; PushUp(root); return ; } Splay(p , q); T[p].son[1] = 0; PushUp(p); PushUp(q); } int GetClose(int key) ///找到与键值key相差的最小值 返回-1代表树上无值 { if(root == 0) return -1; int x = root; int res = Inf; while(x) { res = min(res , abs(T[x].key-key)); x = T[x].son[key>T[x].key]; } return res; } void output(int x) ///输出所建的平衡树 { printf("%d..%d..%d..%d..%d..%d..\n" ,x , T[x].key , T[x].num , T[x].size , T[x].son[0] , T[x].son[1]); if( T[x].son[0] ) output(T[x].son[0]); if(T[x].son[1]) output(T[x].son[1]); } int main() { init(); ///初始化不能忘记 return 0; }
POJ1442
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int Maxn = 1000006; struct Tree { int num , key , size , fa ,son[2]; ///num记录键值等于key的值有多少个 size记录的是子树的节点数目 fa记录的是父亲节点的标号 /* 不通过随机值来保持平衡树均衡 而是每次询问都进行双旋 像搜索引擎一样 询问次数多的就在上面 使得总的复杂度减小 双旋的过程中使得平衡树均衡 第一次询问的复杂度较高 平均下来就是log级别的 */ } T[Maxn]; int cnt , root; int Add[Maxn]; void init() { root = 0; cnt = 1; } void PushUp(int x) { T[x].size = T[T[x].son[0]].size + T[T[x].son[1]].size+T[x].num; } void PushDown(int x) { if(Add[x]) { if(T[x].son[0]) { T[T[x].son[0]].key += Add[x]; Add[T[x].son[0]] += Add[x]; } if(T[x].son[1]) { T[T[x].son[1]].key += Add[x]; Add[T[x].son[1]] += Add[x]; } Add[x] = 0; } } void Rotate(int x , int p) //0左旋 1右旋 { int y = T[x].fa; PushDown(y); PushDown(x); T[y].son[!p] = T[x].son[p]; T[T[x].son[p]].fa = y; T[x].fa = T[y].fa; if( T[x].fa ) T[T[x].fa].son[T[T[x].fa].son[1]==y]=x; T[x].son[p] = y; T[y].fa = x; PushUp(y); PushUp(x); } void Splay(int x , int to) ///将标号是x的节点移动到标号是to的子节点上 { while( T[x].fa!=to ) { if(T[T[x].fa].fa == to) { Rotate(x , T[T[x].fa].son[0]==x); } else { int y = T[x].fa; int z = T[y].fa; int p = (T[z].son[0]==y); if(T[y].son[p]==x) { Rotate(x , !p); Rotate(x , p); } //之字旋转 else { Rotate(y , p); Rotate(x , p); } //一字旋转 } } if(to == 0) root = x; } int Newnode(int key , int fa) ///新建一个节点 { ++cnt; T[cnt].fa = fa; T[cnt].key = key; T[cnt].num = T[cnt].size = 1; T[cnt].son[0] = T[cnt].son[1] = 0; return cnt; } void Insert(int key) ///插入键值为key的节点 { if( !root ) { root = Newnode(key , 0); } else { int now = root , y = 0; while( now ) { PushDown(now); y = now; if(T[now].key == key) { T[now].num++; T[now].size++; break; } T[now].size++; now = T[now].son[key>T[now].key]; } if(!now) { now = T[y].son[key>T[y].key] = Newnode(key,y); } Splay(now,0); //插入新节点旋转到根节点上来 } } int GetPth(int p , int to) ///找到第p小的节点的标号 若无返回0 并移动到to的子节点上来 { if(root==0 || p>T[root].size) return 0; int x = root; while(x) { PushDown(x); if( p>=T[T[x].son[0]].size+1 && p<=T[T[x].son[0]].size+T[x].num) break; if(p > T[T[x].son[0]].size+T[x].num) { p -= T[T[x].son[0]].size+T[x].num; x = T[x].son[1]; } else { x = T[x].son[0]; } } Splay(x , to); return x; } void output(int x) { printf("%d..%d..%d..%d..%d..%d..\n" ,x , T[x].key , T[x].num , T[x].size , T[x].son[0] , T[x].son[1]); if( T[x].son[0] ) output(T[x].son[0]); if(T[x].son[1]) output(T[x].son[1]); } int a[Maxn]; int u[Maxn]; int main() { int n , m; u[0] = 0; while( scanf("%d%d" ,&n ,&m) != EOF ) { for(int i=1; i<=n; i++) scanf("%d" , &a[i]); for(int i=1; i<=m; i++) { scanf("%d" , &u[i]); for(int j=u[i-1]+1; j<=u[i]; j++) { Insert(a[j]); // output(root); // printf("\n"); } printf("%d\n" , T[GetPth(i,0)].key); } } return 0; }
POJ 2352
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int Maxn = 1000006; struct Tree { int num , key , size , fa ,son[2]; ///num记录键值等于key的值有多少个 size记录的是子树的节点数目 fa记录的是父亲节点的标号 /* 不通过随机值来保持平衡树均衡 而是每次询问都进行双旋 像搜索引擎一样 询问次数多的就在上面 使得总的复杂度减小 双旋的过程中使得平衡树均衡 第一次询问的复杂度较高 平均下来就是log级别的 */ } T[Maxn]; int cnt , root; int Add[Maxn]; void init() { root = 0; cnt = 1; } void PushUp(int x) { T[x].size = T[T[x].son[0]].size + T[T[x].son[1]].size+T[x].num; } void PushDown(int x) { if(Add[x]) { if(T[x].son[0]) { T[T[x].son[0]].key += Add[x]; Add[T[x].son[0]] += Add[x]; } if(T[x].son[1]) { T[T[x].son[1]].key += Add[x]; Add[T[x].son[1]] += Add[x]; } Add[x] = 0; } } void Rotate(int x , int p) //0左旋 1右旋 { int y = T[x].fa; PushDown(y); PushDown(x); T[y].son[!p] = T[x].son[p]; T[T[x].son[p]].fa = y; T[x].fa = T[y].fa; if( T[x].fa ) T[T[x].fa].son[T[T[x].fa].son[1]==y]=x; T[x].son[p] = y; T[y].fa = x; PushUp(y); PushUp(x); } void Splay(int x , int to) ///将标号是x的节点移动到标号是to的子节点上 { while( T[x].fa!=to ) { if(T[T[x].fa].fa == to) { Rotate(x , T[T[x].fa].son[0]==x); } else { int y = T[x].fa; int z = T[y].fa; int p = (T[z].son[0]==y); if(T[y].son[p]==x) { Rotate(x , !p); Rotate(x , p); } //之字旋转 else { Rotate(y , p); Rotate(x , p); } //一字旋转 } } if(to == 0) root = x; } int Newnode(int key , int fa) ///新建一个节点 { ++cnt; T[cnt].fa = fa; T[cnt].key = key; T[cnt].num = T[cnt].size = 1; T[cnt].son[0] = T[cnt].son[1] = 0; return cnt; } void Insert(int key) ///插入键值为key的节点 { if( !root ) { root = Newnode(key , 0); } else { int now = root , y = 0; while( now ) { PushDown(now); y = now; if(T[now].key == key) { T[now].num++; T[now].size++; break; } T[now].size++; now = T[now].son[key>T[now].key]; } if(!now) { now = T[y].son[key>T[y].key] = Newnode(key,y); } Splay(now,0); //插入新节点旋转到根节点上来 } } int Find(int key) ///返回键值是key的节点的标号 若无返回0 若有转移到根处 { if(root == 0) return 0; int x = root; while( x ) { PushDown(x); if(T[x].key == key) break; x = T[x].son[key>T[x].key]; } if(x) Splay(x , 0); return x; } void Del(int key) ///删除键值是key的节点1个 { int x =Find(key); if( !x ) return ; if( T[x].num > 1) { T[x].num--; PushUp(x); return; } int y = T[x].son[0]; while( T[y].son[1] ) y = T[y].son[1]; int z = T[x].son[1]; while( T[z].son[0] ) z = T[z].son[0]; if(!y && !z) { root = 0; return ; } if( !y ) { Splay(z , 0); T[z].son[0] = 0; PushUp(z); return ; } if( !z ) { Splay(y , 0); T[y].son[1] = 0; PushUp(y); return ; } Splay(y , 0); Splay(z , y); T[z].son[0] = 0; PushUp(z); PushUp(y); } int GetRank(int key) ///获得键值<=key的节点的个数 { if( !Find(key) ) { Insert(key); int tmp = T[T[root].son[0]].size; Del(key); return tmp; } else { return T[T[root].son[0]].size+T[root].num; } } int ans[Maxn]; int main() { int n , x , y; while( scanf("%d" ,&n) != EOF ) { memset(ans , 0 , sizeof(ans)); for(int i=1; i<=n; i++) { scanf("%d%d" , &x , &y); ans[GetRank(x)]++; Insert(x); } for(int i=0; i<n; i++) printf("%d\n" , ans[i]); } return 0; }
POJ 3481
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int Maxn = 1000006; struct Tree { int num , key , size , fa ,son[2]; int id; ///num记录键值等于key的值有多少个 size记录的是子树的节点数目 fa记录的是父亲节点的标号 /* 不通过随机值来保持平衡树均衡 而是每次询问都进行双旋 像搜索引擎一样 询问次数多的就在上面 使得总的复杂度减小 双旋的过程中使得平衡树均衡 第一次询问的复杂度较高 平均下来就是log级别的 */ } T[Maxn]; int cnt , root; int Add[Maxn]; void init() { root = 0; cnt = 1; } void PushUp(int x) { T[x].size = T[T[x].son[0]].size + T[T[x].son[1]].size+T[x].num; } void PushDown(int x) { if(Add[x]) { if(T[x].son[0]) { T[T[x].son[0]].key += Add[x]; Add[T[x].son[0]] += Add[x]; } if(T[x].son[1]) { T[T[x].son[1]].key += Add[x]; Add[T[x].son[1]] += Add[x]; } Add[x] = 0; } } void Rotate(int x , int p) //0左旋 1右旋 { int y = T[x].fa; PushDown(y); PushDown(x); T[y].son[!p] = T[x].son[p]; T[T[x].son[p]].fa = y; T[x].fa = T[y].fa; if( T[x].fa ) T[T[x].fa].son[T[T[x].fa].son[1]==y]=x; T[x].son[p] = y; T[y].fa = x; PushUp(y); PushUp(x); } void Splay(int x , int to) ///将标号是x的节点移动到标号是to的子节点上 { while( T[x].fa!=to ) { if(T[T[x].fa].fa == to) { Rotate(x , T[T[x].fa].son[0]==x); } else { int y = T[x].fa; int z = T[y].fa; int p = (T[z].son[0]==y); if(T[y].son[p]==x) { Rotate(x , !p); Rotate(x , p); } //之字旋转 else { Rotate(y , p); Rotate(x , p); } //一字旋转 } } if(to == 0) root = x; } int Newnode(int key , int fa , int id) ///新建一个节点 { ++cnt; T[cnt].fa = fa; T[cnt].key = key; T[cnt].num = T[cnt].size = 1; T[cnt].son[0] = T[cnt].son[1] = 0; T[cnt].id = id; return cnt; } void Insert(int key , int id) ///插入键值为key的节点 { if( !root ) { root = Newnode(key , 0 , id); } else { int now = root , y = 0; while( now ) { PushDown(now); y = now; if(T[now].key == key) { T[now].num++; T[now].size++; break; } T[now].size++; now = T[now].son[key>T[now].key]; } if(!now) { now = T[y].son[key>T[y].key] = Newnode(key,y,id); } Splay(now,0); //插入新节点旋转到根节点上来 } } int Find(int key) ///返回键值是key的节点的标号 若无返回0 若有转移到根处 { if(root == 0) return 0; int x = root; while( x ) { PushDown(x); if(T[x].key == key) break; x = T[x].son[key>T[x].key]; } if(x) Splay(x , 0); return x; } void Del(int key) ///删除键值是key的节点1个 { int x =Find(key); if( !x ) return ; if( T[x].num > 1) { T[x].num--; PushUp(x); return; } int y = T[x].son[0]; while( T[y].son[1] ) y = T[y].son[1]; int z = T[x].son[1]; while( T[z].son[0] ) z = T[z].son[0]; if(!y && !z) { root = 0; return ; } if( !y ) { Splay(z , 0); T[z].son[0] = 0; PushUp(z); return ; } if( !z ) { Splay(y , 0); T[y].son[1] = 0; PushUp(y); return ; } Splay(y , 0); Splay(z , y); T[z].son[0] = 0; PushUp(z); PushUp(y); } int Fin(int x , int p) { if(x == 0) return 0; while(T[x].son[p]) { x = T[x].son[p]; } return x; } void output(int x) { printf("%d..%d..%d..%d..%d..%d.\n" , x , T[x].key , T[x].id , T[x].son[0] , T[x].son[1] , T[x].num); if(T[x].son[0]) output(T[x].son[0]); if(T[x].son[1]) output(T[x].son[1]); } int main() { int flag , num , key; while( scanf("%d" , &flag) != EOF ) { if(flag == 0) break; if(flag == 1) { scanf("%d%d" , &num , &key); Insert(key , num); } else if(flag == 2) { int tmp = Fin(root , 1); if(tmp == 0) printf("0\n"); else { printf("%d\n" , T[tmp].id); Del(T[tmp].key); } } else if(flag == 3) { int tmp = Fin(root , 0); if(tmp == 0) printf("0\n"); else { printf("%d\n" , T[tmp].id); Del(T[tmp].key); } } } return 0; }
HNOI 2002
评测地点:http://www.lydsy.com/JudgeOnline/problem.php?id=1588
题意:给你一个序列,让你找到前面的与他差距最小的数字,将所有位的最小差值求和,就是平衡树上求离他最近的两个点与他的差值求最小值
这里代码中一个巧妙的地方,从很节点不停的求差值取最小,并且逼近与他相邻的那个值,这样不仅不需要保存最近的两个值,而且一定不会有遗漏
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int Maxn = 1000006; const int Inf = 0x3f3f3f3f; struct Tree { int num , key , size , fa ,son[2]; ///num记录键值等于key的值有多少个 size记录的是子树的节点数目 fa记录的是父亲节点的标号 /* 不通过随机值来保持平衡树均衡 而是每次询问都进行双旋 像搜索引擎一样 询问次数多的就在上面 使得总的复杂度减小 双旋的过程中使得平衡树均衡 第一次询问的复杂度较高 平均下来就是log级别的 */ } T[Maxn]; int cnt , root; int Add[Maxn]; void init() { root = 0; cnt = 1; } void PushUp(int x) { T[x].size = T[T[x].son[0]].size + T[T[x].son[1]].size+T[x].num; } void PushDown(int x) { if(Add[x]) { if(T[x].son[0]) { T[T[x].son[0]].key += Add[x]; Add[T[x].son[0]] += Add[x]; } if(T[x].son[1]) { T[T[x].son[1]].key += Add[x]; Add[T[x].son[1]] += Add[x]; } Add[x] = 0; } } void Rotate(int x , int p) //0左旋 1右旋 { int y = T[x].fa; PushDown(y); PushDown(x); T[y].son[!p] = T[x].son[p]; T[T[x].son[p]].fa = y; T[x].fa = T[y].fa; if( T[x].fa ) T[T[x].fa].son[T[T[x].fa].son[1]==y]=x; T[x].son[p] = y; T[y].fa = x; PushUp(y); PushUp(x); } void Splay(int x , int to) ///将标号是x的节点移动到标号是to的子节点上 { while( T[x].fa!=to ) { if(T[T[x].fa].fa == to) { Rotate(x , T[T[x].fa].son[0]==x); } else { int y = T[x].fa; int z = T[y].fa; int p = (T[z].son[0]==y); if(T[y].son[p]==x) { Rotate(x , !p); Rotate(x , p); } //之字旋转 else { Rotate(y , p); Rotate(x , p); } //一字旋转 } } if(to == 0) root = x; } int Newnode(int key , int fa) ///新建一个节点 { ++cnt; T[cnt].fa = fa; T[cnt].key = key; T[cnt].num = T[cnt].size = 1; T[cnt].son[0] = T[cnt].son[1] = 0; return cnt; } void Insert(int key) ///插入键值为key的节点 { if( !root ) { root = Newnode(key , 0); } else { int now = root , y = 0; while( now ) { PushDown(now); y = now; if(T[now].key == key) { T[now].num++; T[now].size++; break; } T[now].size++; now = T[now].son[key>T[now].key]; } if(!now) { now = T[y].son[key>T[y].key] = Newnode(key,y); } Splay(now,0); //插入新节点旋转到根节点上来 } } int GetClose(int key) { if(root == 0) return 0; int x = root; int res = Inf; while(x) { res = min(res , abs(T[x].key-key)); x = T[x].son[key>T[x].key]; } return res; } int main() { int n , num , ans; while( scanf("%d" , &n) != EOF ) { scanf("%d" , &num); Insert(num); ans = num; for(int i=2; i<=n; i++) { scanf("%d" , &num); ans += GetClose(num); Insert(num); } printf("%d\n" , ans); } return 0; }
推荐 NOI2004 是区间删除的操作 但是没找到地方提交