Treap入门(转自NOCOW)
Treap
Treap,就是有另一个随机数满足堆的性质的二叉搜索树,其结构相当于以随机顺序插入的二叉搜索树。其基本操作的期望复杂度为O(log
n)。
其特点是实现简单,效率高于伸展树并且支持大部分基本功能,性价比很高。
目录
|
前言
我们可以看到,如果一个二叉搜索树节点插入的顺序是随机的,这样我们得到的二叉搜索树大多数情况下是平衡的,即使存在一些极端情况,但是这种情况发生的概率很小,所以我们可以这样建立一棵二叉搜索树,而不必要像AVL那样旋转,可以证明随机顺序建立的二叉搜索树在期望高度是O(log
n),但是某些时候我们并不能得知所有的待插入节点,打乱以后再插入。所以我们需要一种规则来实现这种想法,并且不必要所有节点。也就是说节点是顺序输入的,我们实现这一点可以用Treap。
介绍
Treap=Tree+Heap。Treap本身是一棵二叉搜索树,它的左子树和右子树也分别是一个Treap,和一般的二叉搜索树不同的是,Treap记录一个额外的数据,就是优先级。Treap在以关键码构成二叉搜索树的同时,还按优先级来满足堆的性质(在这里我们假设节点的优先级小于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap可以并不一定是。(网友注:这些图来自算法导论)
操作
Treap维护堆性质的方法用到了旋转,这里先简单地介绍一下。Treap只需要两种旋转,这样编程复杂度比Splay等就要小一些,这正是Treap的特色之一。
旋转是这样的:
插入
给节点随机分配一个优先级,先和二叉搜索树的插入一样,先把要插入的点插入到一个叶子上,然后跟维护堆一样,如果当前节点的优先级比根小就旋转,如果当前节点是根的左儿子就右旋如果当前节点是根的右儿子就左旋。 即左旋能使根节点转移到左边,右旋能使根节点转移到右边。
我们如果把插入写成递归形式的话,只需要在递归调用完成后判断是否满足堆性质,如果不满足就旋转,实现非常容易。
由于旋转是O(1)的,最多进行h次(h是树的高度),插入的复杂度是O(h)的,在期望情况下h=O(log n),所以它的期望复杂度是O(log
n)。
"如果当前节点的优先级比根小就旋转,如果当前节点是根的左儿子就右旋如果当前节点是根的右儿子就左旋。"
删除
有了旋转的操作之后,Treap的删除比二叉搜索树还要简单。因为Treap满足堆性质,所以我们只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。具体的方法就是每次找到优先级最小的儿子,向与其相反的方向旋转,直到那个节点被旋转到了叶节点,然后直接删除。
删除最多进行O(h)次旋转,期望复杂度是O(log n)。。
查找
和一般的二叉搜索树一样,但是由于Treap的随机化结构,可以证明Treap中查找的期望复杂度是O(log n)。
分离
要把一个Treap按大小分成两个Treap,只要在需要分开的位置加一个虚拟节点,然后旋至根节点删除,左右两个子树就是得出的两个Treap了。根据二叉搜索树的性质,这时左子树的所有节点都小于右子树的节点。
时间相当于一次插入操作的复杂度,也就是O(log n)。
合并
合并是指把两个平衡树(或者其他的有序表)合并成一个平衡树(有序表),其中第一个树(表)的所有节点都必须小于或等于第二个树(表)中的所有节点,这也是上面的分离操作的结果所满足的条件。
Treap的合并操作的过程和分离相反,只要加一个虚拟的根,把两棵树分别作为左右子树,然后把根删除就可以了。
时间复杂度和删除一样,也是期望O(log n)。
参考程序(poj 2892)
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
int n,m,root=,d[],st,ed,sz;
struct Treap{int rnd,v,w,l,r;}tr[];
inline void lturn(int &k)
{
int t=tr[k].r;
tr[k].r=tr[t].l;
tr[t].l=k;
k=t;
}
inline void rturn(int &k)
{
int t=tr[k].l;
tr[k].l=tr[t].r;
tr[t].r=k;
k=t;
}
inline void insert(int &w,int x)
{
if(w==){
sz++;
w=sz;
tr[w].v=x;
tr[w].w=;
tr[w].rnd=rand();
return;
}
if(tr[w].v==x){
tr[w].w++;
return;
}
if(tr[w].v>x){
insert(tr[w].l,x);
if(tr[tr[w].l].rnd<tr[w].rnd)rturn(w);
}
else{
insert(tr[w].r,x);
if(tr[tr[w].r].rnd<tr[w].rnd)lturn(w);
}
}
inline void del(int &w,int x)
{
if(tr[w].v==x){
if(tr[w].w>){tr[w].w--;return;}
if(tr[w].l*tr[w].r==)w=tr[w].l+tr[w].r;
else{
if(tr[tr[w].l].rnd<tr[tr[w].r].rnd){
rturn(w);
del(w,x);}
else{
lturn(w);
del(w,x);}
}
return;
}
else if(tr[w].v<x)del(tr[w].r,x);
else del(tr[w].l,x);
}
inline void find(int &w,int x)
{
if(w==)return;
if(tr[w].v>=x&&tr[w].v<ed)ed=tr[w].v;
if(tr[w].v<=x&&tr[w].v>st)st=tr[w].v;
if(tr[w].v<x)find(tr[w].r,x);
else find(tr[w].l,x);
}
int main()
{
srand(time());
scanf("%d%d",&n,&m);
char od;
int a;
for(int i=,j=;i<=m;i++){
cin>>od;
if(od=='D'){
scanf("%d",&a);
insert(root,a);
j++;
d[j]=a;
}
if(od=='R'){
del(root,d[j]);
j--;
}
if(od=='Q'){
scanf("%d",&a);
st=,ed=n+;
find(root,a);
if(st==a&&ed==a)puts("");
else printf("%d\n",ed-st-);
}
}
return ;
}
算法分析
首先我们注意到二叉搜索树有一个特性,就是每个子树的形态在优先级唯一确定的情况下都是唯一的,不受其他因素影响,也就是说,左子树的形态与树中大于根节点的值无关,右子树亦然。
这是因为Treap满足堆的性质,Treap的根节点是优先级最小的那个节点,考虑它的左子树,树根也是子树里面最小的一点,右子树亦然。所以Treap相当于先把所有节点按照优先级排序,然后插入,实质上就相当于以随机顺序建立的二叉搜索树,只不过它并不需要一次读入所有数据,可以一个一个地插入。而当这个随机顺序确定的时候,这个树是唯一的。
因此在给定优先级的情况下,只要是用符合要求的操作,通过任何方式得出的Treap都是一样的,所以不改变优先级的情况下,特殊的操作不会造成Treap结构的退化。而改变优先级可能会使Treap变得不够随机以致退化。
证明随机建立二叉搜索树的E[h]=O(log n)大家可以参见CLRS P265 12.4 Randomly built binary search trees,这里略去。
如果有E[h]=O(log n)我们就证明了,Treap插入的期望复杂度是O(log n)。
Treap的其它操作的期望复杂度同样是O(log n)。
评价
与其他结构的比较
- AVL树(基于长度保持平衡的二叉查找树)
- 伸展树(Splay Tree,一种基于查找次序优化的二叉查找树,适用于查询操作比较集中的查询集合)
- 线段树(可以保存线段,往往用于统计、图形等题目)
- 红黑树(红黑树编程更理性一些,但编程复杂度更高)
- SBT (SBT比TREAP的速度更快,但编程复杂度更高)
Treap入门(转自NOCOW)的更多相关文章
- poj2761(treap入门)
给n个数,然后m个询问,询问任意区间的第k小的数,特别的,任意两个区间不存在包含关系, 也就是说,将所有的询问按L排序之后, 对于i<j , Li < Lj 且 Ri < Rj ...
- treap入门
这几天刚学了treap,听起来还行,就是调题调到恶心了…… 就以这道题作为板子吧(”你本来也就做了一道题!”) https://www.luogu.org/problemnew/show/P3369 ...
- 数据结构之Treap
1. 概述 同splay tree一样,treap也是一个平衡二叉树,不过Treap会记录一个额外的数据,即优先级.Treap在以关键码构成二叉搜索树的同时,还按优先级来满足堆的性质.因而,Treap ...
- 【bzoj3173-最长上升子序列-一题两解】
这道题不就是简单的DP吗,BZOJ在水我!不,你是错的. ·本题特点: 不断向不同位置插入数字(按数字1,2,3,4,5,6……),需要求出每一次插入后的最长上升子序列. ·分析 ...
- 入门平衡树: Treap
入门平衡树:\(treap\) 前言: 如有任何错误和其他问题,请联系我 微信/QQ同号:615863087 前置知识: 二叉树基础知识,即简单的图论知识. 初识\(BST\): \(BST\)是\( ...
- [转载]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)
转载自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182491.html 今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和t ...
- [您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)
今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我 ...
- 快速入门Treap(代码实现)
学习数据结构对我来说真的相当困难,网上讲\(Treap\)的我也看不太懂,前前后后花了大概六天才把\(Treap\)学会.为了避免再次忘记,这里我整理一下\(Treap\)的基础知识和模板. 阅读此文 ...
- 洛谷 2234 [HNOI2002]营业额统计——treap(入门)
题目:https://www.luogu.org/problemnew/show/P2234 学习了一下 treap 的写法. 学习材料:https://blog.csdn.net/litble/ar ...
随机推荐
- Ajax异步刷新地址栏url改变(利用Html5 history.pushState实现)
早些时候在博客园参阅了不少资料,然后决定入驻博客园分享自己的开发心得,最近准备转方向筹备着辞职交接工作,所以有点忙碌,搁置了一个月才匆匆写下这么一篇随笔,希望能给大家带来一点帮助吧,资料和学识有限,如 ...
- 学习笔记:腾讯云——服务器mysql操作
1.进入数据库 (注意:在linux系统下要进入mysql所在的文件夹下才能打开数据库) 操作1:进入到指定目录下 命令行:cd /opt/lampp/bin 操作2:进入到数据库 命令行:./mys ...
- C#复习⑨(附带C#参考答案仅限参考)
C#复习⑨ 2016年6月22日 14:28 C#考试题&参考答案:http://pan.baidu.com/s/1sld4K13 Main XML Comments & Pointe ...
- Spring为某个属性注入值或为某个方法的返回值
项目中用到需要初始化一些数据,Spring提供了filed的值注入和method的返回值注入. 一.Field值的注入 filed值注入需要使用org.springframework.beans.fa ...
- SVN导出/导入、SVN备份/还原 【小白版】
一.导出: 1.进入svn安装路径bin文件夹下,使用 cd 命令. 在windows下,win+R 键入 cmd 回车 打开命令窗口cmd,进入下列目录(svn服务器安装目录bin): " ...
- 2014.1.23 Discuz论坛迁移+VPS配置手记
虽说这也不是我第一次转移这个论坛了,但毕竟还是第一次自己配置VPS,写点东西记一下 一:关于VPS的配置 1.用TeamViewer连接服务器 这个VPS的IDC自己带有一个远程控制的页面,用浏览器打 ...
- 网站添加数据出错,原来是MS SQL Server2008日志文件占据空间过大导致的
最近发现公司上线的八爪鱼招标网有部分功能出现问题,主要表现为无法向数据库插入数据:远程登陆到数据库服务器时,发现原本的40G空间都被数据库吃完了,于是打开MS SQL Server 2008对数据库进 ...
- Tomcat:基于Apache+Tomcat的集群搭建
根据Tomcat的官方文档说明可以知道,使用Tomcat配置集群需要与其它Web Server配合使用才可以完成,典型的有Apache和IIS. 这里就使用Apache+Tomcat方式来完成基于To ...
- Caused by: java.lang.NoClassDefFoundError: org/objectweb/asm/Type
使用 proxy-target-class="true" 强制配置了 cglib 代理,于是包上面的错误,加入了 asm.jar 报也一样报错. 错误原因是,lib 中有两个cgl ...
- MySQL入门(三)
写了两篇<MySQL入门>以后我发现,写书的人还是都挺有本事的,起码人家知道怎么编排自己想讲的知识点,我实在是不知道该先说那里后说哪里,那我就想到什么讲什么吧. 一 写SQL 其实我是不想 ...