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. 2019寒假训练营寒假作业(三) MOOC的网络空间安全概论笔记部分

    目录 第五章 网络攻防技术 5.1:网络信息收集技术--网络踩点 信息收集的必要性及内容 网络信息收集技术 网络踩点(Footprinting) 网络踩点常用手段 5.2:网络信息收集技术 --网络扫 ...

  2. java—连连看-实现封装

    1.封装 Chess.java package Linkup; /** * 棋子封装类 * * @author laixl * */ public class Chess { // 图片的 状态 // ...

  3. osg::Vec2 Vec3 Vec4

    osg::Vec2可以用于保存2D纹理坐标. osg::Vec3是一个三维浮点数数组. osg::Vec4用于保存颜色数据.

  4. 生成以指定字符为开头的md5值(6位数字)

    以下脚本的功能是生成以指定字符为开头的md5值 #-*- coding:utf-8 -*- #脚本功能:生成以指定字符为开头的md5值(6位数字) import hashlib import rand ...

  5. 相机上的P,S,A,M分别是什么单词的缩写?

    程序曝光 Programmed Auto快门优先 Shutter Priority光圈优先 aperture-priority 全手动模式 Manual Mode

  6. 【Docker 命令】- ps命令

    docker ps : 列出容器 语法 docker ps [OPTIONS] OPTIONS说明: -a:显示所有的容器,包括未运行的. -f:根据条件过滤显示的内容. --format :指定返回 ...

  7. [socket编程] 一个服务器与多个客户端之间通信

    转自:http://blog.csdn.net/neicole/article/details/7539444 并加以改进 Server程序: // OneServerMain.cpp #includ ...

  8. Apache Tomcat Nginx的区别和联系

    一.定义 1. Apache Apache HTTP服务器是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上.其属于应用服务器.Apache支持支持模块多,性能稳定,Apache本身是静态 ...

  9. bzoj2676 Contra

    题意: 给定N,R,Q,S 有N个关卡,初始有Q条命,且任意时刻最多只能有Q条命 每通过一个关卡,会得到u分和1条命,其中u=min(最近一次连续通过的关数,R) 若没有通过这个关卡,将失去一条命,并 ...

  10. BZOJ4869 六省联考2017相逢是问候(线段树+欧拉函数)

    由扩展欧拉定理,a^(a^(a^(……^x)))%p中x作为指数的模数应该是φ(φ(φ(φ(……p)))),而p取log次φ就会变为1,也即每个位置一旦被修改一定次数后就会变为定值.线段树维护区间剩余 ...