「模板」「讲解」Treap名次树
Treap实现名次树
前言
学平衡树的过程可以说是相当艰难。浏览Blog的过程中看到大量指针版平衡树,不擅长指针操作的我已经接近崩溃。于是,我想着一定要写一篇非指针实现的Treap的Blog。
具体如下。
简介
Treap(树堆,Tree+Heap)是一种强大的数据结构——每个节点除了本身键值(v)之外,附有一个随机优先级(p),其中v满足二叉搜索树性质,p满足堆性质(下文中为大根堆),通过旋转操作来维护性质,并使整棵树保持平衡。
名次树
顾名思义就是可以查找x的排名、查找第x名的值、查找前驱与后继的树。详见标题下方题目链接。
节点数据结构
struct node
{
int v,p,size,c[2];
};//键值,优先级,(包括自身在内的)子树大小,左右子节点。
操作
每一种操作都是从根开始。
插入
首先, 与插入二叉搜索树一样。
给待插入节点一个随机的p值,为了避免重复,对生成随机数做了一些特殊处理。
int Random(void)
{
int x;
while(a[x=rand()%MAXN]);//a是bool数组,记录当前数是否被生成过,如果是,就重新生成。
a[x]=1;
return x;
}
- 当前树为空,直接插入;
- 待插入的v大于当前点的v,递归将当前点插入右子树;
- 否则,递归将当前点插入左子树。
其次,待插入点的v到了合适的位置时,我们会发现它的p也许不符合堆性质。
这时,我们要通过旋转操作维护堆性质。
具体操作为,以当前点为根,进行如下图所示的旋转。图源网络。侵删。

旋转示例。图为右旋,自绘。
void Rotate(int &i,bool p)
{
int t=s[i].c[!p];
s[i].c[!p]=s[t].c[p],s[t].c[p]=i;
Update(i),Update(i=t);
}

旋转后更新子树大小。
void Update(int i)
{
s[i].size=s[s[i].c[0]].size+s[s[i].c[1]].size+1;
}
插入代码。
void Insert(int &i,int x)
{
if(!i)
{
s[i=++cnt].v=x,s[i].p=Random(),s[i].size=1;
return;
}
++s[i].size;
bool t=x>s[i].v;
Insert(s[i].c[t],x);
if(s[s[i].c[t]].p>s[i].p)
Rotate(i,!t);
}
删除
其实就是完全把插入的操作反过来。
- 待删除的v等于当前点的v;
- 两个子节点都不为空,则比较两边子节点,将较大的旋转上来,再删除;
- 否则,当前点指向左右子节点中的非空节点(如果有),然后直接返回。
- 待删除的v大于当前点的v时,递归右子树删除当前点;
- 否则,递归左子树删除当前点。
操作完毕后更新当前点的子树大小。
void Erase(int &i,int x)
{
if(x==s[i].v)
if(s[i].c[0] && s[i].c[1])
{
bool t=s[s[i].c[0]].p>s[s[i].c[1]].p;
Rotate(i,t),Erase(s[i].c[t],x);
}
else
{
i=s[i].c[0]|s[i].c[1];
return;
}
else
Erase(s[i].c[x>s[i].v],x);
Update(i);
}
查找排名
- 当前点为空,直接返回1;
- 待查找的值大于当前点的值,递归在右子树中查找;
- 否则,递归在左子树查找。
int Rank(int i,int x)
{
return i ? (x>s[i].v ? Rank(s[i].c[1],x)+s[s[i].c[0]].size+1 : Rank(s[i].c[0],x)) : 1;
}
查找排名为x的数
- 待查找的排名小于t=(当前点的左子树大小+1),递归查找左子树中排名为x的数;
- 如果待查找的排名大于t,递归查找右子树中排名为x-t的数;
- 否则,返回当前点的v。
int Xth(int i,int x)
{
int t=s[s[i].c[0]].size+1;
if(x<t)
return Xth(s[i].c[0],x);
else if(x>t)
return Xth(s[i].c[1],x-t);
else
return s[i].v;
}
查找前驱
查找x的前驱,即查找整个Treap中比x小的最大数。
- 当前点为空,返回-INF;
- 待查找的值大于当前点的v,说明待查找点一定在当前点之后,递归右子树,看是否可以找到比x小的更大数;
- 否则,递归左子树,直到当前点在待查找点之前。
int Pre(int i,int x)
{
return i ? (x>s[i].v ? max(Pre(s[i].c[1],x),s[i].v) : Pre(s[i].c[0],x)) : -INF;
}
查找后继
和查找前驱完全相反。
整体代码
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
using namespace std;
const int MAXN=100010,INF=10000010;
int n;
class Treap
{
public:
int rt;
Treap(void)
{
rt=cnt=0;
memset(a,0,sizeof a);
memset(s,0,sizeof s);
}
void Insert(int &i,int x)
{
if(!i)
{
s[i=++cnt].v=x,s[i].p=Random(),s[i].size=1;
return;
}
++s[i].size;
bool t=x>s[i].v;
Insert(s[i].c[t],x);
if(s[s[i].c[t]].p>s[i].p)
Rotate(i,!t);
}
void Erase(int &i,int x)
{
if(x==s[i].v)
if(s[i].c[0] && s[i].c[1])
{
bool t=s[s[i].c[0]].p>s[s[i].c[1]].p;
Rotate(i,t),Erase(s[i].c[t],x);
}
else
{
i=s[i].c[0]|s[i].c[1];
return;
}
else
Erase(s[i].c[x>s[i].v],x);
Update(i);
}
int Rank(int i,int x)
{
return i ? (x>s[i].v ? Rank(s[i].c[1],x)+s[s[i].c[0]].size+1 : Rank(s[i].c[0],x)) : 1;
}
int Xth(int i,int x)
{
int t=s[s[i].c[0]].size+1;
if(x<t)
return Xth(s[i].c[0],x);
else if(x>t)
return Xth(s[i].c[1],x-t);
else
return s[i].v;
}
int Pre(int i,int x)
{
return i ? (x>s[i].v ? max(Pre(s[i].c[1],x),s[i].v) : Pre(s[i].c[0],x)) : -INF;
}
int Next(int i,int x)
{
return i ? (x<s[i].v ? min(Next(s[i].c[0],x),s[i].v) : Next(s[i].c[1],x)) : INF;
}
private:
bool a[MAXN];
int cnt;
struct node
{
int v,p,size,c[2];
}s[MAXN];
int Random(void)
{
int x;
while(a[x=rand()%MAXN]);
a[x]=1;
return x;
}
void Update(int i)
{
s[i].size=s[s[i].c[0]].size+s[s[i].c[1]].size+1;
}
void Rotate(int &i,bool p)
{
int t=s[i].c[!p];
s[i].c[!p]=s[t].c[p],s[t].c[p]=i;
Update(i),Update(i=t);
}
}T;
int main(int argc,char *argv[])
{
scanf("%d",&n);
srand((unsigned)time(NULL));
for(int i=1,&rt=T.rt,opt,x;i<=n;++i)
{
scanf("%d %d",&opt,&x);
switch(opt)
{
case 1:
T.Insert(rt,x);
break;
case 2:
T.Erase(rt,x);
break;
case 3:
printf("%d\n",T.Rank(rt,x));
break;
case 4:
printf("%d\n",T.Xth(rt,x));
break;
case 5:
printf("%d\n",T.Pre(rt,x));
break;
case 6:
printf("%d\n",T.Next(rt,x));
break;
}
}
return 0;
}
结束语
模板这种东西,尤其是代码量大的,及时复习很重要。
希望我的讲解可以帮助到大家吧。
谢谢阅读。
「模板」「讲解」Treap名次树的更多相关文章
- UVa 1479 (Treap 名次树) Graph and Queries
这题写起来真累.. 名次树就是多了一个附加信息记录以该节点为根的树的总结点的个数,由于BST的性质再根据这个附加信息,我们可以很容易找到这棵树中第k大的值是多少. 所以在这道题中用一棵名次树来维护一个 ...
- LA 5031 Graph and Queries —— Treap名次树
离线做法,逆序执行操作,那么原本的删除边的操作变为加入边的操作,用名次树维护每一个连通分量的名次,加边操作即是连通分量合并操作,每次将结点数小的子树向结点数大的子树合并,那么单次合并复杂度O(n1lo ...
- [模板] 平衡树: Splay, 非旋Treap, 替罪羊树
简介 二叉搜索树, 可以维护一个集合/序列, 同时维护节点的 \(size\), 因此可以支持 insert(v), delete(v), kth(p,k), rank(v)等操作. 另外, prev ...
- POJ-1442 Black Box,treap名次树!
Black Box 唉,一天几乎就只做了这道题,成就感颇低啊! 题意:有一系列插入查找操作,插入每次 ...
- 「模板」 FHQ_Treap 区间翻转
「模板」 FHQ_Treap 区间翻转 没有旋转的 Treap 实现区间操作的功能,很好理解,也很好写,只是速度不算太快. 对于要翻转的区间,把整棵 Treap(存有区间 \([1,n]\) 的信息) ...
- 「模板」 FHQ_Treap
「模板」 FHQ_Treap 我也是偶然发现我还没发过FHQ_Treap的板子. 那就发一波吧. 这个速度实在不算快,但是不用旋转,并且好写. 更重要的是,Splay 可以做的事情它都可以做!比如区间 ...
- 「模板」 线段树——区间乘 && 区间加 && 区间求和
「模板」 线段树--区间乘 && 区间加 && 区间求和 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long l ...
- 「模板」 树链剖分 HLD
「模板」 树链剖分 HLD 不懂OOP的OIer乱用OOP出人命了. 谨此纪念人生第一次类套类. 以及第一次OI相关代码打过200行. #include <algorithm> #incl ...
- SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」
有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1-Spr ...
随机推荐
- “Hello world!”团队—选题展示
本次选题展示内容: 一.视频展示 链接:http://v.youku.com/v_show/id_XMzA5Mzk5NjYwOA==.html?sharefrom=iphone 视频截图链接:http ...
- lintcode-196-寻找缺失的数
196-寻找缺失的数 给出一个包含 0 .. N 中 N 个数的序列,找出0 .. N 中没有出现在序列中的那个数. 样例 N = 4 且序列为 [0, 1, 3] 时,缺失的数为2. 挑战 在数组上 ...
- lintcode-39-恢复旋转排序数组
39-恢复旋转排序数组 给定一个旋转排序数组,在原地恢复其排序. 说明 什么是旋转数组? 比如,原始数组为[1,2,3,4], 则其旋转数组可以是[1,2,3,4], [2,3,4,1], [3,4, ...
- 这些JavaScript编程黑科技,装逼指南,高逼格代码,让你惊叹不已
Javascript是一门很吊的语言,我可能学了假的JavaScript,哈哈,大家还有什么推荐的,补充送那啥邀请码. 本文秉承着:你看不懂是你SB,我写的代码就要牛逼. 1.单行写一个评级组件 &q ...
- 【PHP】- PHPStorm+XDebug进行调试图文教程
转载:https://www.cnblogs.com/LWMLWM/p/8251905.html 这篇文章主要为大家详细介绍了PHPStorm+XDebug进行调试图文教程,内容很丰富,具有一定的 ...
- 不同品牌交换机设置telnet方法
H3C交换机:1.设置telnet system-view super password level 3 cipher ******telnet server enable user-interfac ...
- Flink中的数据传输与背压
一图道尽心酸: 大的原理,上游的task产生数据后,会写在本地的缓存中,然后通知JM自己的数据已经好了,JM通知下游的Task去拉取数据,下游的Task然后去上游的Task拉取数据,形成链条. 但是在 ...
- [STL] 如何将一个vector赋给另一个vector
vector 有个函数assign, 可以帮助执行赋值操作. assign会清空你的容器. assign函数: 函数原型: void assign(const_iterator first,const ...
- 【python】windows7下怎样安装whl
windows7 python2.7 1.用管理员方式打开cmd 2.首先通过pip命令安装wheel 如果提示’pip’不是内部或外部命令,也不是可运行的程序或批处理文件 ①将python安装目录下 ...
- BZOJ4770 图样(概率期望+动态规划)
考虑求出所有MST的权值和再除以方案数,方案数显然是2mn. 按位考虑,显然应该让MST里的边高位尽量为0.那么根据最高位是0还是1将点集划分成两部分,整张图的MST就是由两部分各自的MST之间连一条 ...