旋转/非旋转treap的简单操作
treap(树堆)
是在二叉搜索树的基础上,通过维护随机附加域,使其满足堆性质,从而使树相对平衡的二叉树;
为什么可以这样呢?
因为在维护堆的时候可以同时保证搜索树的性质;
(比如当一棵树的一个域满足堆的性质时,只要不断的互换左右,她的另一个域总会满足搜索树的性质)
(当一棵树的一个域满足搜索树的性质时,只要不断的上下旋转,她的另一个域总会满足二叉堆的性质)
于是树堆有两种实现:
旋转treap:
她的操作基于旋转;
注意:当存在相同val时,对于旋转treap,是存在一个节点中的;
roll(旋转操作)
交换点x与她的x.ch(i):
fa(x).ch(i)=x.ch(i);
x.ch(i)=x.ch(i).ch(i^1);
x.ch(i).ch(i^1)=x;
以上操作是一个改变上下位置却不影响搜索树性质的方法,与splay类似;
void roll(int &now){
int wh=data[data[now].ch[]].key<data[data[now].ch[]].key?:;
int son=data[now].ch[wh];
data[now].ch[wh]=data[son].ch[wh^];
data[son].ch[wh^]=now;
up(now);
now=son;
}
insert(插入操作)
普通的搜索树的插入操作,是插入一个叶子;
普通的堆也是插入一个叶子,然后再把她旋转到相应的位置上;
这样的话,只要按搜索树的法则插入一个节点,然后再以不影响搜索树性质的方式把她旋转到符合堆性质的位置即可;
void insert(int &now){
if(now==){
now=make_data(x);
return;
}
if(data[now].value==x){
data[now].cnt++;
data[now].size++;
}
else{
int wh=x < data[now].value ? : ;
insert(data[now].ch[wh]);
if(data[now].key>=data[data[now].ch[wh]].key)
roll(now);
}
up(now);
}
del(删除操作)
与堆类似的;
把她通过旋转操作下沉到叶子节点,然后再断开即可;
void del(int &now){
if(data[now].value==x){
if(data[now].cnt==){
if(data[now].ch[]*data[now].ch[]==){
now=data[now].ch[]+data[now].ch[];
return ;
}
roll(now);
int wh=data[data[now].ch[]].value==x?:;
del(data[now].ch[wh]);
}
else{
data[now].size--; data[now].cnt--;
}
}
else{
int wh=data[now].value>x?:;
del(data[now].ch[wh]);
}
up(now);
}
这两个操作是与堆流程类似的操作(虽然作为c++党我不写堆);
还有寻找Kth number,寻找rank,寻找last、next等等与平衡树相关的操作,不在此处赘述;
局限性:不能完成区间操作;
有关代码:
#include<cstdio>
#include<cstdlib>
using namespace std;
#define INF 2147483647
int n;
struct poo
{
int size,value,key,cnt;
int ch[];
}data[];
int tot,root,x;
int make_data(int );
void insert(int&);
void roll(int&);
int find( );
int rank( );
void del(int&);
int las(int );
int nex(int );
void up(int );
int main()
{
int i,j;
data[].value=INF;data[].key=INF;
scanf("%d",&n);
for(i=;i<=n;i++){
scanf("%d%d",&j,&x);
switch(j){
case :insert(root);break;
case : del(root);break;
case : printf("%d\n",rank( ));break;
case : printf("%d\n",find( ));break;
case : printf("%d\n", las(root));break;
case : printf("%d\n", nex(root));break;
}
}
return ;
}
int make_data(int value){
tot++;
data[tot].cnt++;
data[tot].key=(rand()/+rand()/);
data[tot].size=;
data[tot].value=value;
return tot;
}
void insert(int &now){
if(now==){
now=make_data(x);
return;
}
if(data[now].value==x){
data[now].cnt++;
data[now].size++;
}
else{
int wh=x < data[now].value ? : ;
insert(data[now].ch[wh]);
if(data[now].key>=data[data[now].ch[wh]].key)
roll(now);
}
up(now);
}
void roll(int &now){
int wh=data[data[now].ch[]].key<data[data[now].ch[]].key?:;
int son=data[now].ch[wh];
data[now].ch[wh]=data[son].ch[wh^];
data[son].ch[wh^]=now;
up(now);
now=son;
}
int find(){
int now=root;
int ls,rs;
ls=data[now].ch[];rs=data[now].ch[];
while(x<=data[ls].size||x>data[now].size-data[rs].size){
if(data[ls].size>=x)
now=ls;
else{
x=x+data[rs].size-data[now].size;
now=rs;
}
ls=data[now].ch[];rs=data[now].ch[];
}
return data[now].value;
}
int rank(){
int now=root,ans=;
int ls=data[now].ch[],rs=data[now].ch[];
while(x!=data[now].value&&x!=)
{
if(x<data[now].value)
now=ls;
else{
ans+=data[now].size-data[rs].size;
now=rs;
}
ls=data[now].ch[];rs=data[now].ch[];
}
return ans+data[ls].size+;
}
void del(int &now){
if(data[now].value==x){
if(data[now].cnt==){
if(data[now].ch[]*data[now].ch[]==){
now=data[now].ch[]+data[now].ch[];
return ;
}
roll(now);
int wh=data[data[now].ch[]].value==x?:;
del(data[now].ch[wh]);
}
else{
data[now].size--; data[now].cnt--;
}
}
else{
int wh=data[now].value>x?:;
del(data[now].ch[wh]);
}
up(now);
}
int las(int now){
int ans=,an=;
if(!now)return ;
if(data[now].value<x){
ans=data[now].value;
an=las(data[now].ch[]);
ans=an!=?an:ans;
}
else{
ans=las(data[now].ch[]);
}
return ans;
}
int nex(int now){
int ans=,an=;
if(!now)return ;
if(data[now].value>x){
ans=data[now].value;
an=nex(data[now].ch[]);
ans=an!=?an:ans;
}
else{
ans=nex(data[now].ch[]);
}
return ans;
}
void up(int now){
data[now].size=data[data[now].ch[]].size+data[data[now].ch[]].size+data[now].cnt;
}
//treap on the 2017.1.21
//10
//1 5
//4 1
//1 6
//1 7
//1 10
//1 3
//1 4
//6 2
//1 8
//5 9
//
//14
//1 5
//1 6
//1 7
//1 10
//1 3
//1 4
//1 8
//3 3
//3 4
//3 5
//3 6
//4 5
//4 6
//4 7
非旋转treap
同样是树堆,旋转treap通过与堆类似的旋转操作维护,但非旋转treap,通过与平衡树类似的拆分|合并操作完成;
注意:当存在相同val时,对于非旋转treap,是存在不同节点中的;
split(拆分操作)
这里介绍按value拆分(还有按排名拆分不讲)
首先预留两个变量名作为两棵树的树根名;
然后从原树根开始,按查找value为k的点的方法往下走,对于每一个走到的点,她大于value则属于右树,否则属于左树,
因为是从上往下走的,所以后进入树的点是先入者的子节点(符合堆性质)
因为当x.val>value时下一步走x.ls,之后的点全比x小,所以把x接到右树后,下一个接入右树的点——不管是谁——应当变成x的左儿子;
对x.val≤value,也是相似的;
void split(int now,int<r,int&rtr,int value){
if(!now){
ltr=rtr=;
return;
}
if(data[now].val<=value)
ltr=now,split(data[now].ch[],data[ltr].ch[],rtr,value);
else
rtr=now,split(data[now].ch[],ltr,data[rtr].ch[],value);
up(now);
}
merge(合并操作)
这里介绍当treapA的所有节点val小于treapB时的操作(即通常使用的操作)
可以看出只要把A的右链和B的左链,拆开,按符合堆上下顺序排好,再连上符合搜索树的父子关系即可;
因为AB分别满足堆性质,所以所谓的“按符合堆上下顺序排好”,只要互相参差插入即可,不改变A,B各自的相对上下顺序;
连父子关系时,属于A树的点需要连一个rs(因为排在她下方的点都比她大)属于B树的点需要连一个ls(因为排在她下方的点都比她小)
实现是递归的
merge( &now , a , b )-->( a.key < b.key ? ( now=a.rs , merge( &now.rs , a.rs , b ) ) : ( now=b.ls , merge( &now.ls , a , b.ls ) ) );
void merge(int&now,int s,int b){
if(!s||!b){
now=s+b;return;
}
if(data[s].key<data[b].key)
now=s,merge(data[now].ch[],data[s].ch[],b);
else
now=b,merge(data[now].ch[],s,data[b].ch[]);
up(now);
}
insert(value):把树split成A树≤value,B树>value,然后建一个value的点x,然后merge(root,A,x),merge(root,root,B);
del(value):把树拆成三部分,中间是等于value的点——把这部分的点减少一个,然后再合并树的三部分;
非旋转treap是一种非常好的平衡树,她有treap的优良性质——常数比Splay小,而且还支持区间操作和可持久化,
重点是代码短
唯一的缺点的是不好理解
有关代码:
#include<cstdio>
#include<cstdlib>
using namespace std;
const int INF=;
struct Treap{
int val,key,size;
int ch[];
}data[];
int root,tot;
void make_data(int&,int );
void up(int );
void merge(int&,int,int);
void split(int ,int&,int&,int );
void insert(int );
void del(int );
void rank(int );
void find(int ,int );
int las(int );
int nex(int );
int main()
{
srand(2.17);
int i,j,k,m;
root=;data[].key=INF;data[].val=INF;data[].size=;
scanf("%d",&m);
for(i=;i<=m;i++){
scanf("%d%d",&j,&k);
if(j==)insert(k);
if(j==)del(k);
if(j==)rank(k);
if(j==)find(root,k);
if(j==)las(k);
if(j==)nex(k);
}
return ;
}
void make_data(int&now,int value){
data[++tot].val=value;data[tot].key=rand();
data[tot].size=;
data[tot].ch[]=data[tot].ch[]=;
now=tot;
}
void up(int now){
data[now].size=data[data[now].ch[]].size+data[data[now].ch[]].size+;
}
void merge(int&now,int s,int b){
if(!s||!b){
now=s+b;return;
}
if(data[s].key<data[b].key)
now=s,merge(data[now].ch[],data[s].ch[],b);
else
now=b,merge(data[now].ch[],s,data[b].ch[]);
up(now);
}
void split(int now,int<r,int&rtr,int value){
if(!now){
ltr=rtr=;
return;
}
if(data[now].val<=value)
ltr=now,split(data[now].ch[],data[ltr].ch[],rtr,value);
else
rtr=now,split(data[now].ch[],ltr,data[rtr].ch[],value);
up(now);
}
void insert(int value){
int x=,y=,z=;
make_data(z,value);
split(root,x,y,value);
merge(x,x,z);
merge(root,x,y);
}
void del(int value){
int x=,y=,z=;
split(root,x,y,value);
split(x,x,z,value-);
merge(z,data[z].ch[],data[z].ch[]);
merge(x,x,z);merge(root,x,y);
}
void rank(int value){
int x=,y=;
split(root,x,y,value-);
printf("%d\n",data[x].size+);
merge(root,x,y);
}
void find(int now,int x){
while(data[data[now].ch[]].size+!=x){
if(data[data[now].ch[]].size>=x)
now=data[now].ch[];
else
x-=(data[data[now].ch[]].size+),now=data[now].ch[];
}
printf("%d\n",data[now].val);
}
int las(int value){
int x=,y=;
split(root,x,y,value-);
find(x,data[x].size);
merge(root,x,y);
}
int nex(int value){
int x=,y=;
split(root,x,y,value);
find(y,);
merge(root,x,y);
}
旋转/非旋转treap的简单操作的更多相关文章
- 非旋转Treap
Treap是一种平衡二叉树,同时也是一个堆.它既具有二叉查找树的性质,也具有堆的性质.在对数据的查找.插入.删除.求第k大等操作上具有期望O(log2n)的复杂度. Treap可以通过节点的旋 ...
- [bzoj3173]最长上升子序列_非旋转Treap
最长上升子序列 bzoj-3173 题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度. 注释:1<=n<=10,000,开始序列为 ...
- BZOJ3224普通平衡树——非旋转treap
题目: 此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数, ...
- 平衡树及笛卡尔树讲解(旋转treap,非旋转treap,splay,替罪羊树及可持久化)
在刷了许多道平衡树的题之后,对平衡树有了较为深入的理解,在这里和大家分享一下,希望对大家学习平衡树能有帮助. 平衡树有好多种,比如treap,splay,红黑树,STL中的set.在这里只介绍几种常用 ...
- 4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap
国际惯例的题面:这种维护排序序列,严格大于的进行操作的题都很套路......我们按照[0,k],(k,2k],(2k,inf)分类讨论一下就好.显然第一个区间的不会变化,第二个区间的会被平移进第一个区 ...
- 非旋转Treap:用运行时间换调试时间的有效手段
非旋转Treap:用运行时间换调试时间的有效手段 Hello大家好,我们今天来聊一聊非旋转Treap. 相信各位或多或少都做过些序列上的问题.如果水题我们考虑暴力:不强制在线我们可能用过莫队和待修 ...
- [bzoj3196][Tyvj1730]二逼平衡树_树套树_位置线段树套非旋转Treap/树状数组套主席树/权值线段树套位置线段树
二逼平衡树 bzoj-3196 Tyvj-1730 题目大意:请写出一个维护序列的数据结构支持:查询给定权值排名:查询区间k小值:单点修改:查询区间内定值前驱:查询区间内定值后继. 注释:$1\le ...
- [bzoj4864][BeiJing2017Wc]神秘物质_非旋转Treap
神秘物质 bzoj-4864 BeiJing-2017-Wc 题目大意:给定一个长度为n的序列,支持插入,将相邻两个元素合并并在该位置生成一个指定权值的元素:查询:区间内的任意一段子区间的最大值减最小 ...
- 关于非旋转Treap
刚刚跟着EM-LGH大佬学了非旋转Treap 非常庆幸不用再写万恶的rotate了(来自高级数据结构的恶意) 来记一下 Treap 概念 简单来说,\(Tree_{二叉搜索树} * Heap_堆 = ...
随机推荐
- graphviz画图与中文乱码等问题总结
最近想写一些文档,画一些程序的逻辑图,用了vision,markdown等软件感觉不怎么好用,于是找到graphviz,这款强大的软件.下面介绍一些入门,还有自己在用的过程中遇到的问题 1.中文乱码的 ...
- Ubuntu18.04配制阿里巴巴的源
配制阿里巴巴的源步骤 使用阿里巴巴的开源镜像:https://opsx.alibaba.com/mirror 然后选择ubuntu的帮助选项,复制ubuntu18.04镜像源 设置root账户密码: ...
- WebFrom局部刷新
因为页面用的是服务器的控件 结果每次触发后导致页面刷新 把刚填完的数据给整没了 很烦 在网上找了蛮久才找到的一个方法 战略插眼 以后还有用到的时候 <asp:ScriptManager runa ...
- 线段树+单调栈+前缀和--2019icpc南昌网络赛I
线段树+单调栈+前缀和--2019icpc南昌网络赛I Alice has a magic array. She suggests that the value of a interval is eq ...
- java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可!
java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可! 通过 云寻觅 javacallcsharp 生成器 自动生成java jni类库, ...
- mongo 授权访问
1.授权远程也可以访问 - 首先修改mongodb的配置文件 让其监听所有外网ip 编辑文件:/etc/mongodb.conf 修改后的内容如下: bind_ip = 0.0.0.0 port = ...
- hadoop-2.6.0.tar.gz的集群搭建(3节点)(不含zookeeper集群安装)
前言 本人呕心沥血所写,经过好一段时间反复锤炼和整理修改.感谢所参考的博友们!同时,欢迎前来查阅赏脸的博友们收藏和转载,附上本人的链接http://www.cnblogs.com/zlslch/p/5 ...
- Linux shell ${}简单用法
转自:Linux shell ${}简单用法 为了完整起见,我这里再用一些例子加以说明 ${ } 的一些特异功能:假设我们定义了一个变量为:file=/dir1/dir2/dir3/my.file.t ...
- ThreadPoolExecutor 异常
通过execute提交的任务,能将该任务抛出的异常交给未捕获异常处理器处理,而通过submit提交的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分.如果一个由submit ...
- 通配符证书导致 Outlook Anywhere 的客户端连接问题
通配符证书导致 Outlook Anywhere 的客户端连接问题 本主题介绍当您使用 Outlook Anywhere 连接到 Microsoft Exchange 及在组织中跨 Exchange ...