旋转/非旋转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_堆 = ...
随机推荐
- iOS几个功能:1.摇一摇;2.震动;3.简单的摇动动画;4.生成二维码图片;5.发送短信;6.播放网络音频等
有一个开锁的功能,具体的需求就类似于微信的“摇一摇”功能:摇动手机,手机震动,手机上的锁的图片摇动一下,然后发送开锁指令.需求简单,但用到了许多方面的知识. 1.摇一摇 相对这是最简单的功能了. 在v ...
- h5 页面点击添加添加input框
h5 页面点击添加添加input框 前段时间有个需求,页面要能点击添加按钮控制input框的个数,当时感觉有点难,就没做,这两个又遇到了,没办法写了个简单的 效果图,加号增加,减号减少 直接上代码, ...
- Focal Loss 的前向与后向公式推导
把Focal Loss的前向和后向进行数学化描述.本文的公式可能数学公式比较多.本文尽量采用分解的方式一步一步的推倒.达到能易懂的目的. Focal Loss 前向计算 其中 是输入的数据 是输入的标 ...
- JDK7 AutoCloseable
干嘛的 直接看JDK7的流(运用了AutoCloseable)源码 public abstract class InputStream implements Closeable { //实现Close ...
- How to install Jenkins on CentOS 7
How to install Jenkins on CentOS 7 on March 3, 2018 by AmirLeave a comment Introduction Jenkins is a ...
- redis 数据持久化 aof方式
redis持久化-Append-only file(缩写aof)的方式 本质:把用户执行的每个 ”写“ 指令(增加.修改.删除)都备份到文件中,还原数据的时候就是执行具体写指令. 打开redis的运 ...
- 安装TD出现Unknown user name or bad password问题
在Server 2003 sp2上安装TD8.0 出现Unknown user name or bad password,是因为2003启用了DEP保护. 关闭系统的DEP保护就可以了. 方法如下 ...
- JS 开发者必须知道的十个 ES6 新特性
这篇文章会给你简单介绍一下ES6.如果你还不知道什么是ES6的话,它是JavaScript一个新的实现,如果你是一个忙碌的JavaScript开发者(但谁不是呢),那么继续读下去吧,看看当今最热门的语 ...
- C++11 并发(一道笔试题目)
题目:编写一个程序,开启3个线程,这3个线程的ID分别为A.B.C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示:如:ABCABC….依次递推. #include < ...
- poj 1222EXTENDED LIGHTS OUT
高斯消元的题本质思想一样. 学习网址:http://www.cnblogs.com/rainydays/archive/2011/08/31/2160748.html #include <ios ...