三大平衡树(Treap + Splay + SBT)总结+模板[转]
Treap树
核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn)
Treap模板:
#include <cstdio> #include <cstring> #include <ctime> #include <iostream> #include <algorithm> #include <cstdlib> #include <cmath> #include <utility> #include <vector> #include <queue> #include <map> #include <set> #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) #define INF 0x3f3f3f3f #define MAXN 100005 using namespace std; ,rt=; //节点编号从1开始 struct Tree { ]; //保证父亲的pri大于儿子的pri void set(int x, int y, int z) { key=x; pri=y; size=z; son[]=son[]=; } }T[MAXN]; void rotate(int p, int &x) { int y=T[x].son[!p]; T[x].size=T[x].size-T[y].size+T[T[y].son[p]].size; T[x].son[!p]=T[y].son[p]; T[y].size=T[y].size-T[T[y].son[p]].size+T[x].size; T[y].son[p]=x; x=y; } void ins(int key, int &x) { ) T[x = cnt++].); else { T[x].size++; int p=key < T[x].key; ins(key, T[x].son[!p]); if(T[x].pri < T[T[x].son[!p]].pri) rotate(p, x); } } void del(int key, int &x) //删除值为key的节点 { if(T[x].key == key) { ] && T[x].son[]) { ]].pri > T[T[x].son[]].pri; rotate(p, x); del(key, T[x].son[p]); } else { ]) x=T[x].son[]; else x=T[x].son[]; } } else { T[x].size--; int p=T[x].key > key; del(key, T[x].son[!p]); } } int find(int p, int &x) //找出第p小的节点的编号 { ]].size+) return x; ]].size+) find(p-T[T[x].son[]].size-, T[x].son[]); else find(p, T[x].son[]); } int find_NoLarger(int key, int &x) //找出值小于等于key的节点个数 { ) ; if(T[x].key <= key) ]].size++find_NoLarger(key, T[x].son[]); else ]); }
相关题解:
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为维护对象保证单调)
常用版:(支持相同值)
Struct Tree{
int key, size, fa, son[2];
}
void PushUp(int x);
void Rotate(int x, int p); //0左旋 1右旋
void Splay(int x, int To) //将x节点插入到To的子节点中
int find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处
int prev() //返回比根值小的最大值 若无返回0 若有将其转移到根处
int succ() //返回比根值大的最小值 若无返回0 若有将其转移到根处
void Insert(int key) //插入key 并且将该节点转移到根处
void Delete(int key) //删除值为key的节点 若有重点只删其中一个 x的前驱移动到根处
int GetPth(int p) //获得第p小的节点 并将其转移到根处
int GetRank(int key) //获得值<=key的节点个数 并将其转移到根处 若<key只需将<=换为<
, rt=; struct Tree { ]; void set(int _key, int _size, int _fa) { key=_key; size=_size; fa=_fa; son[]=son[]=; } }T[MAXN]; inline void PushUp(int x) { T[x].size=T[T[x].son[]].size+T[T[x].son[]].size+; } inline void Rotate(int x, int p) //0左旋 1右旋 { int y=T[x].fa; 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[] == 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[] == x); else { int y=T[x].fa, z=T[y].fa; ] == y); if(T[y].son[p] == x) Rotate(x, !p), Rotate(x, p); //之字旋 else Rotate(y, p), Rotate(x, p); //一字旋 } } ) rt=x; } int find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处 { int x=rt; while(x && T[x].key != key) x=T[x].son[key > T[x].key]; ); return x; } int prev() //返回比根值小的最大值 若无返回0 若有将其转移到根处 { ]; ; ]) x=T[x].son[]; Splay(x, ); return x; } int succ() //返回比根值大的最小值 若无返回0 若有将其转移到根处 { ]; ; ]) x=T[x].son[]; Splay(x, ); return x; } void Insert(int key) //插入key 并且将该节点转移到根处 { if(!rt) T[rt = cnt++]., ); else { ; while(x) { y=x; x=T[x].son[key > T[x].key]; } T[x = cnt++]., y); T[y].son[key > T[y].key]=x; Splay(x, ); } } void Delete(int key) //删除值为key的节点 若有重点只删其中一个 x的前驱移动到根处 { int x=find(key); if(!x) return; ]; ]) y=T[y].son[]; ]; ]) z=T[z].son[]; if(!y && !z) { rt=; return; } if(!y) { Splay(z, ); T[z].son[]=; PushUp(z); return; } if(!z) { Splay(y, ); T[y].son[]=; PushUp(y); return; } Splay(y, ); Splay(z, y); T[z].son[]=; PushUp(z); PushUp(y); } int GetPth(int p) //获得第p小的节点 并将其转移到根处 { ; ; while(x) { ]].size+) break; ]].size+) { p-=T[T[x].son[]].size+; x=T[x].son[]; } else x=T[x].son[]; } Splay(x, ); return x; } int GetRank(int key) //获得值<=key的节点个数 并将其转移到根处 若<key只需将<=换为< { ; , y; while(x) { y=x; if(T[x].key <= key) { ret+=T[T[x].son[]].size+; x=T[x].son[]; } else x=T[x].son[]; } Splay(y, ); return ret; }
完全版:(支持相同值,支持区间删除,支持懒惰标记)
Struct Tree{
int key, num, size, fa, son[2];
}
void PushUp(int x);
void PushDown(int x);
int Newnode(int key, int fa); //新建一个节点并返回
void Rotate(int x, int p); //0左旋 1右旋
void Splay(int x, int To); //将x节点移动到To的子节点中
int GetPth(int p, int To); //返回第p小的节点 并移动到To的子节点中
int Find(int key); //返回值为key的节点 若无返回0 若有将其转移到根处
int Prev(); //返回根节点的前驱
int Succ(); //返回根结点的后继
void Insert(int key); //插入key值
void Delete(int key); //删除值为key的节点
int GetRank(int key); //获得值<=key的节点个数
void Delete(int l, int r); //删除值在[l, r]中的节点
int cnt, rt; int Add[MAXN]; struct Tree{ ]; }T[MAXN]; inline void PushUp(int x) { T[x].size=T[T[x].son[]].size+T[T[x].son[]].size+T[x].num; } inline void PushDown(int x) { if(Add[x]) { ]) { T[T[x].son[]].key+=Add[x]; Add[T[x].son[]]+=Add[x]; } ]) { T[T[x].son[]].key+=Add[x]; Add[T[x].son[]]+=Add[x]; } Add[x]=; } } inline int Newnode(int key, int fa) //新建一个节点并返回 { ++cnt; T[cnt].key=key; T[cnt].num=T[cnt].size=; T[cnt].fa=fa; T[cnt].son[]=T[cnt].son[]=; return cnt; } inline 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[] == 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[] == x); else { int y=T[x].fa, z=T[y].fa; ] == y); if(T[y].son[p] == x) Rotate(x, !p), Rotate(x, p); //之字旋 else Rotate(y, p), Rotate(x, p); //一字旋 } } ) rt=x; } int GetPth(int p, int To) //返回第p小的节点 并移动到To的子节点中 { ; int x=rt; while(x) { PushDown(x); ]].size+ && p <= T[T[x].son[]].size+T[x].num) break; ]].size+T[x].num) { p-=T[T[x].son[]].size+T[x].num; x=T[x].son[]; } else x=T[x].son[]; } Splay(x, ); return x; } int Find(int key) //返回值为key的节点 若无返回0 若有将其转移到根处 { ; int x=rt; while(x) { PushDown(x); if(T[x].key == key) break; x=T[x].son[key > T[x].key]; } ); return x; } int Prev() //返回根节点的前驱 非重点 { ]) ; ]; ]) { PushDown(x); x=T[x].son[]; } Splay(x, ); return x; } int Succ() //返回根结点的后继 非重点 { ]) ; ]; ]) { PushDown(x); x=T[x].son[]; } Splay(x, ); return x; } void Insert(int key) //插入key值 { if(!rt) rt=Newnode(key, ); else { ; while(x) { PushDown(x); y=x; if(T[x].key == key) { T[x].num++; T[x].size++; break; } T[x].size++; x=T[x].son[key > T[x].key]; } if(!x) x=T[y].son[key > T[y].key]=Newnode(key, y); Splay(x, ); } } void Delete(int key) //删除值为key的节点1个 { int x=Find(key); if(!x) return; ) { T[x].num--; PushUp(x); return; } ]; ]) y=T[y].son[]; ]; ]) z=T[z].son[]; if(!y && !z) { rt=; return; } if(!y) { Splay(z, ); T[z].son[]=; PushUp(z); return; } if(!z) { Splay(y, ); T[y].son[]=; PushUp(y); return; } Splay(y, ); Splay(z, y); T[z].son[]=; PushUp(z); PushUp(y); } int GetRank(int key) //获得值<=key的节点个数 { if(!Find(key)) { Insert(key); ]].size; Delete(key); return tmp; } else ]].size+T[rt].num; } void Delete(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) { rt=; return; } if(!p) { T[rt].son[]=; PushUp(rt); return; } if(!q) { Splay(p, ); T[rt].son[]=; PushUp(rt); return; } Splay(p, q); T[p].son[]=; PushUp(p); PushUp(q); }
(经测NOI2004郁闷的出纳员 POJ3481 POJ2352 POJ1442)
速度相对来说都还不错,POJ这些都3~500ms,郁闷的出纳员900多ms
相关题解:
II 用于维护序列:(以序列下标为对象维护,相当于对区间操作)(能够完成线段树的操作及其不能完成的操作)
Struct Tree{
int key, sum, size, fa, son[2];
}
支持操作:
void PushUp(int x);
void PushDown(int x);
int MakeTree(int l, int r, int a[]); //新建一个子树返回根节点
void Rotate(int x, int p); //0左旋 1右旋
void Splay(int x, int To); //将x节点移动到To的子节点中
int Select(int p, int To); //将第p个数移动到To的子节点中 并返回该节点
int Find(int key); //返回值为key的节点 若无返回0 若有将其转移到根处
int Prev(); //返回根节点的前驱
int Succ(); //返回根结点的后继
void Insert(int p, int l, int r, int a[]) //将a[l .. r]的数插入到下标为p后面
void Delete(int l, int r); //删除区间[l, r]中的节点
int Query(int l, int r); //返回[l, r]的和
待补充。。
Size Balance Tree
和上述两种二叉树比起来,SBT可能是最像真正平衡二叉树吧。
SBT能够保证树的高度在lgn,这样对于插入,删除操作都能够准确保证时间复杂度在O(lgn)
Maintain操作事实上理解起来也是挺简单的,至于证明参见CQF神牛的《SBT》
int cnt, rt; struct Tree { ]; }T[MAXN]; inline void PushUp(int x) { T[x].size=T[T[x].son[]].size+T[T[x].son[]].size+; } inline int Newnode(int key) { ++cnt; T[cnt].key=key; T[cnt].size=; T[cnt].son[]=T[cnt].son[]=; return cnt; } void Rotate(int p, int &x) { int y=T[x].son[!p]; T[x].son[!p]=T[y].son[p]; T[y].son[p]=x; PushUp(x); PushUp(y); x=y; } void Maintain(int &x, int p) //维护SBT的!p子树 { if(T[T[T[x].son[p]].son[p]].size > T[T[x].son[!p]].size) Rotate(!p, x); else if(T[T[T[x].son[p]].son[!p]].size > T[T[x].son[!p]].size) Rotate(p, T[x].son[p]), Rotate(!p, x); else return; Maintain(T[x].son[], ); Maintain(T[x].son[], ); Maintain(x, ); Maintain(x, ); } inline int Prev() //返回比根值小的最大值 若无返回0 { ]; ; ]) x=T[x].son[]; return x; } inline int Succ() //返回比根值大的最小值 若无返回0 { ]; ; ]) x=T[x].son[]; return x; } void Insert(int key, int &x) { if(!x) x=Newnode(key); else { T[x].size++; Insert(key, T[x].son[key > T[x].key]); Maintain(x, key > T[x].key); } } bool Delete(int key, int &x) //删除值为key的节点 key可以不存在 { ; if(T[x].key == key) { ]) { x=T[x].son[]; ; } ]) { x=T[x].son[]; ; } int y=Prev(); T[x].size--; ]); } else if(Delete(key, T[x].son[key > T[x].key])) { T[x].size--; ; } } int GetPth(int p, int &x) //返回第p小的节点 { ; ]].size+) return x; ]].size+) ]].size-, T[x].son[]); else ]); } int GetRank(int key, int &x) //找出值<=key的节点个数 { ; if(T[x].key <= key) ]].size++GetRank(key, T[x].son[]); else ]); }
相关题解:
上述题均为用于测试平衡树基本操作的题目。
提高题:(暂时未写)
三大平衡树(Treap + Splay + SBT)总结+模板[转]的更多相关文章
- 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】
平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...
- 三大平衡树(Treap + Splay + SBT)总结+模板[转]
Treap树 核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn) Treap模板: #include <cstdio> #include <cstring> #i ...
- 三大平衡树(Treap + Splay + SBT)总结+模板
Treap树 核心是 利用随机数的二叉排序树的各种操作复杂度平均为O(lgn) Treap模板: #include <cstdio> #include <cstring> #i ...
- BZOJ 3224 - 普通平衡树 - [Treap][Splay]
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3224 Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中 ...
- [洛谷P3369] 普通平衡树 Treap & Splay
这个就是存一下板子...... 题目传送门 Treap的实现应该是比较正经的. 插入删除前驱后继排名什么的都是平衡树的基本操作. #include<cstdio> #include< ...
- luoguP3369[模板]普通平衡树(Treap/SBT) 题解
链接一下题目:luoguP3369[模板]普通平衡树(Treap/SBT) 平衡树解析 #include<iostream> #include<cstdlib> #includ ...
- 平衡树Treap模板与原理
这次我们来讲一讲Treap(splay以后再更) 平衡树是一种排序二叉树(或二叉搜索树),所以排序二叉树可以迅速地判断两个值的大小,当然操作肯定不止那么多(不然我们还学什么). 而平衡树在排序二叉树的 ...
- P3369 【模板】普通平衡树(splay)
P3369 [模板]普通平衡树 就是不用treap splay板子,好好背吧TAT #include<iostream> #include<cstdio> #include&l ...
- 算法模板——平衡树Treap 2
实现功能:同平衡树Treap 1(BZOJ3224 / tyvj1728) 这次的模板有了不少的改进,显然更加美观了,几乎每个部分都有了不少简化,尤其是删除部分,这个参照了hzwer神犇的写法,在此鸣 ...
随机推荐
- SAP本地化-银企直连
SAP本地化-银企直连 http://blog.sina.com.cn/s/blog_a0de8cc80101dee1.html 一.发展历史 2011年,在SAP ECC6 Ehp5中,通过功能增强 ...
- bash:command not found解决方法
先用:echo $PATH 查看path是否含有:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin如果没有先用临时环境变量(重启 ...
- android studio 的基本使用和建立一个小项目
https://github.com/allenxieyusheng/Android-Studio
- 【三十四】thinkphp之curd操作
1.数据创建(create) 接受提交过来的数据,比如表单提交的 POST(默认)数据.接受到数据后,还可以对数据进行有效的验证.完成.生成等工作 // 这里 create()方法就是数据创建,数据的 ...
- C# 使用 protobuf 进行对象序列化与反序列化
protobuf 是 google的一个开源项目,可用于以下两种用途: (1)数据的存储(序列化和反序列化),类似于xml.json等: (2)制作网络通信协议. 源代码下载地址:https://gi ...
- str_replace 批量查找替换字符串
<?php $str = 'I Love You!'; $str = str_replace('o','O',$str,$count); echo $str.PHP_EOL; // I LOve ...
- 力扣(LeetCode)226. 翻转二叉树
翻转一棵二叉树. 示例: 思想 递归 java版 /** * Definition for a binary tree node. * public class TreeNode { * int va ...
- js,java时间处理
1.JS获取时间格式为“yyyy-MM-dd HH:mm:ss”的字符串 function getTimeStr(){ var myDate = new Date(); var year = myDa ...
- CentOS/redhat使用光盘镜像源
1,首先进行光盘的挂载,注意光盘挂载时不会自动建立目录的, 所以需要自己建立目录. mkdir /mnt/cdrom mount /dev/cdrom /mnt/cdrom #de ...
- Python Selenium 文件下载
Python Selenium 进UI自动化测试时都会遇到文件上传和下载的操作,下面介绍一下文件下载的操作 这里介绍使用FireFox浏览器进行文件下载的操作. 1.设置文件默认下载地址 如下图,fi ...