三大平衡树(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神犇的写法,在此鸣 ...
随机推荐
- python爬虫训练——爬poj题目
首先要解决的就是不同的题目在不同的页上,也就是要实现翻页功能,自动获取所要爬取的地址,通过分析可以得出不同的页面也就是volume=后面的数字不同 所以我们可以用re模块来替换即可: new_url ...
- JqGrid 列时间格式化
{name:'createTime',index:'createTime',label:"创建时间", editable:false,formatter:"date&qu ...
- vs2013 报错error C1083: 无法打开包括文件:“gl\glew.h”: No such file or directory\
vs报错诸如如无法打开“gl\xxx.h”时, 解决方法: 1.去http://glew.sourceforge.net/下载相关文件,2.在下载下来的文件里找到xxx.h,将其复制到vs的相关目录下 ...
- python中常用的模块一
一,常用的模块 模块就是我们将装有特定功能的代码进行归类,从代码编写的单位来看我们的程序,从小到大的顺序: 一条代码<语句块,<代码块(函数,类)<模块我们所写的所有py文件都是模块 ...
- App.Config自定义配置节点
配置文件: <?xml version="1.0" encoding="utf-8"?> <configuration> <con ...
- JavaSE习题 第四章 类与对象
问答题: 1.在声明类时,类名应该遵守哪些习惯? 1.与文件名相同2.首字母大写 2.类体内容中有那两类比较重要的成员? 1.成员变量2.方法 3.实例方法可以操作类变量吗?类方法可以操作实例变量吗? ...
- Android集成人脸识别demo分享
本应用来源于虹软人工智能开放平台,人脸识别技术工程如何使用? 1.下载代码 git clone https://github.com/andyxm/ArcFaceDemo.git 2.下载虹软人脸识别 ...
- CC初试啼声-----演讲与我
演讲与我 我非常讨厌演讲,因为我不会演讲,当我站在许多人面前讲话时,我会非常的紧张,我会血压升高,心跳加速,后背冒冷汗. 第一次演讲应该是在我初二的时候,期末考试结束,班级前五名的同学要做一个分享,我 ...
- AtCoder Grand Contest 027 C ABland Yard
ABland Yard 思路: 用了类似拓扑排序的方法来判环 代码: #pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optim ...
- selenium+Page Objects(第三话)
写好BasePage基类和页面元素定位后,就可以针对每个页面写业务逻辑了 1.编写每个页面page类,拿其中一个页面为例 fourth_page.py(名字我随便取的,实际中希望能取一些有意义的名字) ...