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名次树的更多相关文章

  1. UVa 1479 (Treap 名次树) Graph and Queries

    这题写起来真累.. 名次树就是多了一个附加信息记录以该节点为根的树的总结点的个数,由于BST的性质再根据这个附加信息,我们可以很容易找到这棵树中第k大的值是多少. 所以在这道题中用一棵名次树来维护一个 ...

  2. LA 5031 Graph and Queries —— Treap名次树

    离线做法,逆序执行操作,那么原本的删除边的操作变为加入边的操作,用名次树维护每一个连通分量的名次,加边操作即是连通分量合并操作,每次将结点数小的子树向结点数大的子树合并,那么单次合并复杂度O(n1lo ...

  3. [模板] 平衡树: Splay, 非旋Treap, 替罪羊树

    简介 二叉搜索树, 可以维护一个集合/序列, 同时维护节点的 \(size\), 因此可以支持 insert(v), delete(v), kth(p,k), rank(v)等操作. 另外, prev ...

  4. POJ-1442 Black Box,treap名次树!

                                                      Black Box 唉,一天几乎就只做了这道题,成就感颇低啊! 题意:有一系列插入查找操作,插入每次 ...

  5. 「模板」 FHQ_Treap 区间翻转

    「模板」 FHQ_Treap 区间翻转 没有旋转的 Treap 实现区间操作的功能,很好理解,也很好写,只是速度不算太快. 对于要翻转的区间,把整棵 Treap(存有区间 \([1,n]\) 的信息) ...

  6. 「模板」 FHQ_Treap

    「模板」 FHQ_Treap 我也是偶然发现我还没发过FHQ_Treap的板子. 那就发一波吧. 这个速度实在不算快,但是不用旋转,并且好写. 更重要的是,Splay 可以做的事情它都可以做!比如区间 ...

  7. 「模板」 线段树——区间乘 && 区间加 && 区间求和

    「模板」 线段树--区间乘 && 区间加 && 区间求和 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long l ...

  8. 「模板」 树链剖分 HLD

    「模板」 树链剖分 HLD 不懂OOP的OIer乱用OOP出人命了. 谨此纪念人生第一次类套类. 以及第一次OI相关代码打过200行. #include <algorithm> #incl ...

  9. SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1-Spr ...

随机推荐

  1. ide的tomcat的部署和配置

    关于intellij ide的tomcat的部署和配置   1.下载zip版的Tomcat 7,并解压.下载地址 2.在IDEA中配置Tomcat 7 在idea中的Settings(Ctrl+Alt ...

  2. python正则表达式函数match()和search()的区别详解

    match()和search()都是python中的正则匹配函数,那这两个函数有何区别呢? match()函数只检测RE是不是在string的开始位置匹配, search()会扫描整个string查找 ...

  3. Ubuntu下FileZilla的安装

    FileZilla是一个免费而且开源的FTP客户端软件,共有两种版本:客户端版本.服务器版本.FileZilla有条理的界面和管理多站点的简化方式使得FileZilla Client成为一个方便高效的 ...

  4. lintcode-185-矩阵的之字型遍历

    185-矩阵的之字型遍历 给你一个包含 m x n 个元素的矩阵 (m 行, n 列), 求该矩阵的之字型遍历. 样例 对于如下矩阵: [ [1, 2, 3, 4], [5, 6, 7, 8], [9 ...

  5. iOS- AVSpeechSynthesizer——iOS7语音合成器

    语音合成器的技术是iOS7推出的,可以实现无网络语音功能,支持多种语言 1. 定义一个成员变量,记录语音合成器 AVSpeechSynthesizer #import <AVFoundation ...

  6. ASP.NET 使用MVC4的EF5 Code First 入门(一):创建数据库

    一.基本流程 建立模型→建立控制器→EF框架自动生成视图的数据库 二.基本理论 1.约定优于配置(Convention Over Configuration) 设计不好的框架通常需要多个配置文件,每一 ...

  7. php缓存技术——memcache常用函数详解

    php缓存技术——memcache常用函数详解 2016-04-07 aileen PHP编程 Memcache函数库是在PECL(PHP Extension Community Library)中, ...

  8. Java notify的使用

    半路出家学习java, 花了几分钟简单看了.在早上那个例子上稍微改了下, notify 对象上必须使用 synchronized 我的理解是在java synchronized只是个线程同步标志,但是 ...

  9. 【BZOJ4892】DNA(后缀数组)

    [BZOJ4892]DNA(后缀数组) 题面 BZOJ 洛谷 题解 看到这道题目,我第一反应是\(FFT\)??? 然后大力码出了一个\(FFT\) 就像这样 #include<iostream ...

  10. Semphore信号量的使用

    前言:在多线程环境的同步中,我们为了让每个线程具有同步的作用,经常采用synchronize.reetrantlock等同步手段进行上锁,以便在同一时间只能有一个线程具有访问变量和读写变量的权力.然而 ...