关于平衡树的一些学习
来源:互联网 发布:正装皮鞋推荐 知乎 编辑:程序博客网 时间:2024/06/06 12:35
听说平衡树种类很多,什么RBT,AVL,BST,Splay,跳表什么的,简单谈一谈。
(1) 红黑树 又叫RBT ,实现复杂 性能强悍,在OI中较少出现QAQ
(2)Splay 第一个接触的平衡树,一开始还以为Splay是唯一的平衡树(我是SB),splay的原理是:每插入一个点,就把它旋转到根节点,Splay不能保证每次旋转操作的复杂度的是O(log(n)),但是它的均摊复杂度确实是log(n),证明我也不会,听说splay会被卡,等到被卡了再说吧QAQ,编程复杂度也小,Splay的核心在于splay操作即是Splay(x,f)把x旋转到f的儿子上,特别的如果要旋转到根,那么f=0 Splay的旋转操作分为zig,zig-zig,zig-zag。
zig操作:当x的父亲已经是f的儿子时,直接旋转x。
zig-zig操作:当x与x的父亲位于同一侧时,即x是x的父亲的左儿子,x的父亲是x的父亲的父亲的左儿子,反之也成立。
zig-zag操作:当x与x的父亲位于不同侧时,直接旋转两次x。这里的旋转,就是指,如果x是它父亲的左儿子,那么就把他右旋,否则左旋。如图
Splay Tree可以方便的解决一些区间问题,根据不同形状二叉树先序遍历结果不变的特性,可以将区间按顺序建二叉查找树。
每次自下而上的一套splay都可以将x移动到根节点的位置,利用这个特性,可以方便的利用Lazy的思想进行区间操作。
对于每个节点记录size,代表子树中节点的数目,这样就可以很方便地查找区间中的第k小或第k大元素。
对于一段要处理的区间[x, y],首先splay x-1到root,再splay y+1到root的右孩子,这时root的右孩子的左孩子对应子树就是整个区间。————————从别的地方抄来的
贴上代码:
int father[N],key[N],ch[N][2],root,tot1; //分别表示父结点,键值,左右孩子(0为左孩子,1为右孩子),根结点,结点数量 void Rotate(int x,int kind) { int y=father[x]; //把其中一个分支先给父节点 ch[y][!kind]=ch[x][kind]; father[ch[x][kind]]=y; //如果父节点不是根结点,则要和父节点的父节点连接起来 if(father[y]) ch[father[y]][ch[father[y]][1]==y]=x; father[x]=father[y]; ch[x][kind]=y; father[y]=x; } //Splay调整,将根为r的子树调整为goal void Splay(int r,int goal) { while(father[r]!=goal) { //父节点即是目标位置,goal为0表示,父节点就是根结点 if(father[father[r]]==goal) Rotate(r,ch[father[r]][0]==r); else { int y=father[r]; int kind=ch[father[y]][0]==y; //两个方向不同,则先左旋再右旋 if(ch[y][kind]==r) { Rotate(r,!kind); Rotate(r,kind); } //两个方向相同,相同方向连续两次 else { Rotate(y,kind); Rotate(r,kind); } } } //更新根结点 if(goal==0) root=r; }二.Treap
我来填坑了,先给一道模板题 BZOJ3224 普通平衡树可以用Treap做
一些定义如下
#include<bits/stdc++.h>using namespace std;struct data { int l , r , v , rnd , size , w ; }; data tr[100001] ; int n , ans , size , root ; void update(int k) { tr[k].size = tr[tr[k].l].size + tr[tr[k].r].size + tr[k].w ; } void lturn(int &k) { int t = tr[k].r ; tr[k].r = tr[t].l ; tr[t].l = k ; tr[t].size = tr[k].size ; update(k) ; k = t ; } void rturn(int &k) { int t = tr[k].l ; tr[k].l = tr[t].r ; tr[t].r = k ; tr[t].size = tr[k].size ; update(k) ; k = t ; }
左旋和右旋看着图理解一下吧,实在不行记住代码也行QAQ
插入代码:
void insert(int &k , int x) { if(k == 0) { size ++ ; k = size ; tr[k].size = tr[k].w = 1 ; tr[k].v = x ; tr[k].rnd = rand() ; return ; } tr[k].size ++ ; if(tr[k].v == x) tr[k].w ++ ; else if(x > tr[k].v) { insert(tr[k].r , x) ; if(tr[tr[k].r].rnd < tr[k].rnd) lturn(k) ; }else { insert(tr[k].l , x) ; if(tr[tr[k].l].rnd < tr[k].rnd) rturn(k) ; } }
1.先讨论没有节点的情况,那就新加入一个节点
2.若当前节点的权值与插入的值相同,那么给当前节点w+1
3.与当前节点权值作比较,如果插入值大于当前节点权值则插入到右边,反之插入到左边,同时维护小根堆的特点
删除操作:
void del(int &k , int x) { if(k == 0) return ; if(tr[k].v == x) { if(tr[k].w > 1) { tr[k].w -- , tr[k].size -- ; return ; } if(tr[k].l * tr[k].r == 0) k = tr[k].l + tr[k].r ; else if(tr[tr[k].l].rnd < tr[tr[k].r].rnd) { rturn(k) ; del(k , x) ; }else { lturn(k) ; del(k , x) ; } }else if(x > tr[k].v) { tr[k].size -- ; del(tr[k].r , x) ; }else { tr[k].size -- ; del(tr[k].l , x) ; } }1.没有节点不删除
2.删除值与当前节点的值相同且当前节点的值的个数大于1则减一
3.若k处只有一个子节点,则把子节点提上来
4.如果左边rnd小于右边rnd,则右旋,把这个根节点旋到叶节点处删掉
4.如果右边rnd小于左边rnd,则左旋,把这个根节点旋到叶节点处删掉
5.如果比k处的值大,则将k的size--然后分到右子树操作
5.如果比k处的值小,则将k的size--然后分到左子树操作
查询排名操作:
int query_rank(int k , int x) { if(k == 0) return 0 ; if(tr[k].v == x) return tr[tr[k].l].size + 1 ; else if(x > tr[k].v) { return tr[tr[k].l].size + tr[k].w + query_rank(tr[k].r , x) ; }else return query_rank(tr[k].l , x) ; }1.讨论没有节点的情况
2.若当前节点的值与查询值相同,则返回左子树的节点数+1;
3.若当前节点的值小于被查询的值,则返回左子树的节点数+当前节点的数目+查询到右子树的返回值
4.若当前节点的值大于被查询的值,则返回查询到左子树的返回值
int query_num(int k , int x) { if(k == 0) return 0 ; if(x <= tr[tr[k].l].size) { return query_num(tr[k].l , x) ; }else if(x > tr[tr[k].l].size + tr[k].w) { return query_num(tr[k].r , x - tr[tr[k].l].size - tr[k].w) ; }else { return tr[k].v ; } }1.讨论没有节点的情况
2.如果x小于等于左子树的节点数则递归询问
3.如果x比左子树的节点数加k处节点数还大,则返回对右子树的x-左子树节点数-k处节点数的递归询问。
4.除了2,3情况就一定是k处,返回k处的值就好了
求前驱:
前驱定义:该节点的前一个节点
void query_pro(int k , int x) { if(k == 0) return ; if(tr[k].v < x) { ans = k ; query_pro(tr[k].r , x) ; }else query_pro(tr[k].l , x) ; }1.讨论没有节点的情况
2.如果x比k处的值大,则用ans记录k,然后递归到右子树
3.如果k处的值大于等于x,则递归到左子树
求后继:
后继定义:该节点的后一个节点
void query_sub(int k , int x) { if(k == 0) return ; if(tr[k].v > x) { ans = k ; query_sub(tr[k].l , x) ; }else query_sub(tr[k].r , x) ; }1.讨论没有节点的情况
2.如果x比k处的值小,则用ans记录k,然后递归到左子树
3.如果k处的值大于等于x,则递归到右子树
基本操作已经叙述完了,下面贴上主函数,大家就可以去把上面那到题水掉了QAQ
贴上全代码:
#include<bits/stdc++.h>using namespace std;struct data { int l , r , v , rnd , size , w ; }; data tr[100001] ; int n , ans , size , root ; void update(int k) { tr[k].size = tr[tr[k].l].size + tr[tr[k].r].size + tr[k].w ; } void lturn(int &k) { int t = tr[k].r ; tr[k].r = tr[t].l ; tr[t].l = k ; tr[t].size = tr[k].size ; update(k) ; k = t ; } void rturn(int &k) { int t = tr[k].l ; tr[k].l = tr[t].r ; tr[t].r = k ; tr[t].size = tr[k].size ; update(k) ; k = t ; }void insert(int &k , int x) { if(k == 0) { size ++ ; k = size ; tr[k].size = tr[k].w = 1 ; tr[k].v = x ; tr[k].rnd = rand() ; return ; } tr[k].size ++ ; if(tr[k].v == x) tr[k].w ++ ; else if(x > tr[k].v) { insert(tr[k].r , x) ; if(tr[tr[k].r].rnd < tr[k].rnd) lturn(k) ; }else { insert(tr[k].l , x) ; if(tr[tr[k].l].rnd < tr[k].rnd) rturn(k) ; } } void del(int &k , int x) { if(k == 0) return ; if(tr[k].v == x) { if(tr[k].w > 1) { tr[k].w -- , tr[k].size -- ; return ; } if(tr[k].l * tr[k].r == 0) k = tr[k].l + tr[k].r ; else if(tr[tr[k].l].rnd < tr[tr[k].r].rnd) { rturn(k) ; del(k , x) ; }else { lturn(k) ; del(k , x) ; } }else if(x > tr[k].v) { tr[k].size -- ; del(tr[k].r , x) ; }else { tr[k].size -- ; del(tr[k].l , x) ; } } int query_rank(int k , int x) { if(k == 0) return 0 ; if(tr[k].v == x) return tr[tr[k].l].size + 1 ; else if(x > tr[k].v) { return tr[tr[k].l].size + tr[k].w + query_rank(tr[k].r , x) ; }else return query_rank(tr[k].l , x) ; } int query_num(int k , int x) { if(k == 0) return 0 ; if(x <= tr[tr[k].l].size) { return query_num(tr[k].l , x) ; }else if(x > tr[tr[k].l].size + tr[k].w) { return query_num(tr[k].r , x - tr[tr[k].l].size - tr[k].w) ; }else { return tr[k].v ; } }void query_pro(int k , int x) { if(k == 0) return ; if(tr[k].v < x) { ans = k ; query_pro(tr[k].r , x) ; }else query_pro(tr[k].l , x) ; }void query_sub(int k , int x) { if(k == 0) return ; if(tr[k].v > x) { ans = k ; query_sub(tr[k].l , x) ; }else query_sub(tr[k].r , x) ; } int main() { scanf("%d" , &n) ; int opt , x ; for(int i = 1 ; i <= n ; i++) { scanf("%d%d" , &opt , &x) ; switch(opt) { case 1 :insert(root , x) ;break ; case 2 :del(root , x) ;break ; case 3 :printf("%d\n" , query_rank(root , x)) ;break ; case 4 :printf("%d\n" , query_num(root , x)) ;break ; case 5 :ans = 0;query_pro(root , x);printf("%d\n" , tr[ans].v);break ; case 6 :ans = 0;query_sub(root , x);printf("%d\n" , tr[ans].v);break ; } } }
三.SBT
挖坑待填
四.替罪羊树
什么都不会,挖坑待填QAQ
阅读全文
0 0
- 关于平衡树的一些学习
- 关于平衡树(Splay)的一些总结
- 关于平衡二叉树
- 关于学习的一些建议
- 关于Attribute的一些学习
- 关于学习的一些思考
- 关于TextView的一些学习
- 关于学习的一些想法
- 关于学习的一些思考
- 关于浮动的一些学习
- 关于 explicit 的一些学习
- 关于学习的一些思考
- 关于学习的一些思考
- 关于DialogFragment的一些学习
- 关于zookeeper的一些学习
- 关于Notification的一些学习
- 关于UML的一些学习
- 关于学习的一些思考
- java环境变量配置的意义
- libvirt Java API用法连载之KVM/QEMU区别与libvirt简介(一)
- n个Tomcat间Session共享-nginx配置
- ASP.NET MVC5验证系列之Remote Validation
- Quartus II 中IP Core LPM_DIVIDE的使用
- 关于平衡树的一些学习
- 软件工程(C编码实践篇)学习总结
- SVN服务器搭建
- Java中的反射机制
- 算法作业_28(2017.6.8第十六周)
- vector整理
- AJAX:ajax的五种状态---readyState
- weex采坑之旅(三)Android SDK环境搭建
- 两数之和