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等等与平衡树相关的操作,不在此处赘述;

局限性:不能完成区间操作;

有关代码:

洛谷P3369 【模板】普通平衡树

 #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&ltr,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小,而且还支持区间操作和可持久化,

重点是代码短

唯一的缺点的是不好理解

有关代码:

洛谷P3369 【模板】普通平衡树

 #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&ltr,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的简单操作的更多相关文章

  1. 非旋转Treap

    Treap是一种平衡二叉树,同时也是一个堆.它既具有二叉查找树的性质,也具有堆的性质.在对数据的查找.插入.删除.求第k大等操作上具有期望O(log2n)的复杂度.     Treap可以通过节点的旋 ...

  2. [bzoj3173]最长上升子序列_非旋转Treap

    最长上升子序列 bzoj-3173 题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度. 注释:1<=n<=10,000,开始序列为 ...

  3. BZOJ3224普通平衡树——非旋转treap

    题目: 此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数, ...

  4. 平衡树及笛卡尔树讲解(旋转treap,非旋转treap,splay,替罪羊树及可持久化)

    在刷了许多道平衡树的题之后,对平衡树有了较为深入的理解,在这里和大家分享一下,希望对大家学习平衡树能有帮助. 平衡树有好多种,比如treap,splay,红黑树,STL中的set.在这里只介绍几种常用 ...

  5. 4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap

    国际惯例的题面:这种维护排序序列,严格大于的进行操作的题都很套路......我们按照[0,k],(k,2k],(2k,inf)分类讨论一下就好.显然第一个区间的不会变化,第二个区间的会被平移进第一个区 ...

  6. 非旋转Treap:用运行时间换调试时间的有效手段

    非旋转Treap:用运行时间换调试时间的有效手段   Hello大家好,我们今天来聊一聊非旋转Treap. 相信各位或多或少都做过些序列上的问题.如果水题我们考虑暴力:不强制在线我们可能用过莫队和待修 ...

  7. [bzoj3196][Tyvj1730]二逼平衡树_树套树_位置线段树套非旋转Treap/树状数组套主席树/权值线段树套位置线段树

    二逼平衡树 bzoj-3196 Tyvj-1730 题目大意:请写出一个维护序列的数据结构支持:查询给定权值排名:查询区间k小值:单点修改:查询区间内定值前驱:查询区间内定值后继. 注释:$1\le ...

  8. [bzoj4864][BeiJing2017Wc]神秘物质_非旋转Treap

    神秘物质 bzoj-4864 BeiJing-2017-Wc 题目大意:给定一个长度为n的序列,支持插入,将相邻两个元素合并并在该位置生成一个指定权值的元素:查询:区间内的任意一段子区间的最大值减最小 ...

  9. 关于非旋转Treap

    刚刚跟着EM-LGH大佬学了非旋转Treap 非常庆幸不用再写万恶的rotate了(来自高级数据结构的恶意) 来记一下 Treap 概念 简单来说,\(Tree_{二叉搜索树} * Heap_堆 = ...

随机推荐

  1. Spring aop+自定义注解统一记录用户行为日志

    写在前面 本文不涉及过多的Spring aop基本概念以及基本用法介绍,以实际场景使用为主. 场景 我们通常有这样一个需求:打印后台接口请求的具体参数,打印接口请求的最终响应结果,以及记录哪个用户在什 ...

  2. Memcached安装教程及使用

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载 Table of contents 安装 使用 在spring中使用 安装 下载下来m ...

  3. MySQL之查看数据库编码

    MySQL之查看数据库编码

  4. vue进行路由拼图的使用案例

    实现思路,利用路由进行实现多个组件拼图: Detail.vue <template> <div> <h1>详细展示</h1> <div>鞍山 ...

  5. 图解http 笔记

    一,了解web以及网络基础 1,使用http协议访问web web页面是由web浏览器根据地址栏中指定的url从web服务器获取文件资源等信息然后显示的页面. 像这种通过发送请求获取服务器资源的web ...

  6. Mac下快速新建txt文件

    1.打开终端,定位到桌面 cd desktop 2.输入 vi test.txt 此时,一个txt文件就会建立在桌面上,操作vi时的提示:按[i]为输入内容,编辑好之后按[esc]键,然后输入[:wq ...

  7. HTML5 五大特性

    一.正则表达式: 相信大家都会非常喜欢这个特性,无须服务器端的检测,使用浏览器的本地功能就可以帮助你判断电子邮件的格式,URL,或者是电话格式,防止用户输入错误的信息,通过使用HTML5的patter ...

  8. 浅谈js中的垃圾两种回收机制

    一.标记清除 标记清除的主要思想是先建立各个对象的关联,然后从根节点出发,使用广度优先搜索依次标记所有对象,那些不能被标记的对象就应该作为垃圾回收. 这种方式的主要缺点就是如果某些对象被清理后,内存是 ...

  9. css3记事

    1.文字超出省略 text-overflow: ellipsis white-space: nowrap; overflow: hidden; text-overflow: ellipsis; *父元 ...

  10. 《Algorithms算法》笔记:元素排序(4)——凸包问题

    <Algorithms算法>笔记:元素排序(4)——凸包问题 Algorithms算法笔记元素排序4凸包问题 凸包问题 凸包问题的应用 凸包的几何性质 Graham 扫描算法 代码 凸包问 ...