Treap是一种平衡二叉树,同时也是一个堆。它既具有二叉查找树的性质,也具有堆的性质。在对数据的查找、插入、删除、求第k大等操作上具有期望O(log2n)的复杂度。 
    Treap可以通过节点的旋转来实现其维持平衡的操作,详见旋转式Treap. 而旋转式Treap在对区间数据的操作上无能为力,这就需要非旋转式Treap来解决这些区间问题。

非旋转式Treap支持的操作

基本操作:

操作 说明 实现复杂度
Build 构造Treap O(n)
Merge 合并Treap O(log2n)
Split 拆分Treap O(log2n)
NewNode 新建节点 O(1)

可支持操作:

操作 说明(实现) 实现复杂度
Insert NewNode + Merge O(log2n)
Delete Split + Split + Merge O(log2n)
FindKth Split + Split O(log2n)
Query Split + Split O(log2n)
Cover Split + Split + Merge O(log2n)

基本操作

1. Build

类似于笛卡尔树的构造(详细见笛卡尔树),非旋转式Treap也可以通过与栈的集合来达到平摊O(n)的复杂度。具体为:

(1) 通过快排等排序手段对原始数据序列进行排序,这主要是为了使Treap满足二叉查找特性 
(2) 当前Treap中的从根开始的右子节点、右子节点的右子节点...链上的所有节点push到一个栈中,栈底为根节点 
(3) 对排序后的序列,从头开始,执行: 
(3.1) 生成一个新的Treap节点,节点中会有随机生成的priority值用来实现堆的结构 
(3.2) 从栈顶向栈底查找,同时栈顶元素出栈,直到栈顶元素的priority小于当前节点的priority,记录下当前栈顶节点为P 
(3.3) 将P之前出栈的那个节点置为待插入节点的左子结点,同时将待插入节点置为P的右子节点,再将带插入节点入栈

2. Merge

类似于左倾堆的Merge操作,可以在O(log2n)的时间复杂度内完成Merge操作。具体为:

(1) 如果一个Treap为空,则返回另外的Treap 
(2) 如果选择两个Treap中堆顶元素的priority值最小为的堆,“较小堆”的堆顶成为新堆的堆顶,然后递归调用 Merge,对较小堆的堆顶元素的右子节点和较大堆进行合并操作,并将返回的结果置为“较小堆”堆顶元素的右子节点 
(3) 对新堆的顶点进行维护,即维护堆顶节点的size等信息

3. Split

对于一个Treap,按照他的第k位进行拆分,则可以按照类似快排算法寻找第k大元素的步骤,返回结果为一个pair,即最大元素为全局第k大的子树根节点和最小元素为全局第k+1大的子树根节点构成的pair。具体为:

(1) 若当前节点x的左子树中的size大于等于k,则进入左子树进行拆分,返回结果y(为一个pair)。 
(1.1) 然后将第k+1大及其之后的节点构成的子树挂在到x的左子树上。即将x的左子结点置为y的second,然后更新x节点的size等信息。 
(1.2) 然后将y的second指向x节点,即最小元素为全局第k+1大的子树的根节点 
(2) 若当前节点x的左子树的size小于k,则进入右子树进行拆分,返回结果y 
(2.1) 然后将第k大及其之前的节点构成的子树挂在到x的右子树上。即将x的右子节点置为y的first,然后更新x节点的size信息。 
(2.2) 然后将y的first指向x节点,即最大元素为全局第k大的子树的根节点 
(3) 返回y

实现(c++)

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_NUM 100
struct TreapNode{
int key;
int priority;
int size;
TreapNode* left;
TreapNode* right;
TreapNode(int k){
key = k;
size = 1;
priority = rand();
left = right = NULL;
}
void Update(){
size = 1;
if (left)
size += left->size;
if (right)
size += right->size;
}
}; int GetSize(TreapNode* node){
if (node)
return node->size;
return 0;
}
void Build(int *arr, int n){
sort(arr, arr + n); //先进行排序
TreapNode* stack[MAX_NUM];
TreapNode* last;
int p = 0;
TreapNode* new_node;
for (int i = 0; i < n; i++){
last = NULL;
new_node = new TreapNode(arr[i]);
//直到栈为空,或者栈顶元素priority小于当前节点的priority
while (p && stack[p]->priority > new_node->priority){
last = stack[p--];
}
stack[p]->right = new_node; //新节点作为p的右子节点
new_node->left = last; //前一次出栈的节点作为 新节点的左子结点
stack[++p] = new_node; //新插入元素入栈
}
} TreapNode* Merge(TreapNode* treap1, TreapNode* treap2){
if (!treap1)
return treap2;
if (!treap2)
return treap1; if (treap1->priority < treap2->priority){
treap1->right = Merge(treap1->right, treap2);
treap1->Update();
return treap1;
}
else{
treap2->left = Merge(treap1, treap2->left);
treap2->Update();
return treap2;
}
} typedef pair<TreapNode*, TreapNode*> TreapPair;
TreapPair Split(TreapNode* treap, int k){
if (!treap){
return TreapPair(NULL, NULL);
}
TreapPair result;
if (treap->left->size >= k){
result = Split(treap->left, k);
treap->left = result.second;
treap->Update();
result.second = treap;
}
else{
result = Split(treap->right, k - treap->left->size - 1);
treap->right = result.first;
treap->Update();
result.first = treap;
}
return result;
} //注意为treapNode指针引用
int FindKth(TreapNode*& treap, int k){
TreapPair x = Split(treap, k - 1);
TreapPair y = Split(x.second, 1);
int result = y.first->key;
treap = Merge(x.first, y.first);
treap = Merge(treap, y.second);
} //询问一个数是第几大
int GetKth(TreapNode* treap, int v){
if (!treap)
return 0;
if (v < treap->key)
return GetKth(treap->left, v);
else
return GetKth(treap->right, v) + GetSize(treap->left) + 1;
} void Insert(TreapNode*& treap, int v){
int k = GetKth(treap, v);
TreapPair result = Split(treap, k);
TreapNode* new_treap = new TreapNode(v);
treap = Merge(result.first, new_treap);
treap = Merge(treap, result.second);
} void Delete(TreapNode*& treap, int k){
TreapPair x = Split(treap, k - 1);
TreapPair y = Split(x.second, 1);
treap = Merge(x.first, y.second);
}
参考

非旋转Treap

非旋转Treap的更多相关文章

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

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

  2. 关于非旋转treap的学习

    非旋转treap的操作基于split和merge操作,其余操作和普通平衡树一样,复杂度保证方式与旋转treap差不多,都是基于一个随机的参数,这样构出的树树高为\(logn\) split 作用:将原 ...

  3. [Codeforces702F]T-Shirts——非旋转treap+贪心

    题目链接: Codeforces702F 题目大意:有$n$种T恤,每种有一个价格$c_{i}$和品质$q_{i}$且每种数量无限.现在有$m$个人,第$i$个人有$v_{i}$元,每人每次会买他能买 ...

  4. BZOJ5063旅游——非旋转treap

    题目描述 小奇成功打开了大科学家的电脑. 大科学家打算前往n处景点旅游,他用一个序列来维护它们之间的顺序.初 始时,序列为1,2,...,n. 接着,大科学家进行m次操作来打乱顺序.每次操作有6步: ...

  5. BZOJ3223文艺平衡树——非旋转treap

    此为平衡树系列第二道:文艺平衡树您需要写一种数据结构,来维护一个有序数列,其中需要提供以下操作: 翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 ...

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

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

  7. [NOIP]2017列队——旋转treap/非旋转treap

    Sylvia 是一个热爱学习的女孩子.  前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia所在的方阵中有n × m名学生,方阵的行数为 n,列数为m.  为了便 ...

  8. BZOJ3729Gty的游戏——阶梯博弈+巴什博弈+非旋转treap(平衡树动态维护dfs序)

    题目描述 某一天gty在与他的妹子玩游戏.妹子提出一个游戏,给定一棵有根树,每个节点有一些石子,每次可以将不多于L的石子移动到父节点,询问将某个节点的子树中的石子移动到这个节点先手是否有必胜策略.gt ...

  9. BZOJ1552[Cerc2007]robotic sort&BZOJ3506[Cqoi2014]排序机械臂——非旋转treap

    题目描述 输入 输入共两行,第一行为一个整数N,N表示物品的个数,1<=N<=100000. 第二行为N个用空格隔开的正整数,表示N个物品最初排列的编号. 输出 输出共一行,N个用空格隔开 ...

  10. BZOJ1251序列终结者——非旋转treap

    题目描述 网上有许多题,就是给定一个序列,要你支持几种操作:A.B.C.D.一看另一道题,又是一个序列 要支持几种操作:D.C.B.A.尤其是我们这里的某人,出模拟试题,居然还出了一道这样的,真是没技 ...

随机推荐

  1. thrift学习之二----学习资料积累

    自己没有仔细安装,从网上搜的安装技术文章,在此做个备份,以防后面用到: http://blog.csdn.net/hshxf/article/details/5567019 http://blog.c ...

  2. PHP 之前后端交互

    今天想实现一个很简单的功能,前端上面一个按键去控制 板子里面的一个LED灯,就这么一个简单的问题,困扰了一天. 以前搞底层的时候,没有服务端和客户端的深刻概念,网页不同,分在客户端运行的 html 代 ...

  3. u-boot源码分析

    Uboot源码分析 源码以u-boot-1.3.4为基准,主芯片采用at91sam9260,主要介绍uboot执行流程. uboot官网:http://www.denx.de/wiki/U-Boot/ ...

  4. FFmpeg API变化

    可以查看doc目录下的APIchanges和根目录下的Changelog 去掉了ffserver程序   'avcodec_register_all' is deprecated 还有av_regis ...

  5. npm常用命令汇总

    npm是一个node包管理和分发工具,已经成为了非官方的发布node模块(包)的标准. 有了npm,可以很快的找到特定服务要使用的包,进行下载.安装以及管理已经安装的包. 1.npm install ...

  6. 第三百零一节,python操作redis缓存-管道、发布订阅

    python操作redis缓存-管道.发布订阅 一.管道 redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pi ...

  7. static、extern分析

    1.extern extern在变量声明中有这样一个作用:你要在demo2.cpp中引用demo1.cpp中的一个全局变量,就要在demo2.h中用extern来声明这个全局变量(或者在demo1.h ...

  8. erlang -- ios apns provider -- erlang 实现

    os apns-apple notification server 与第三方provider的通信原理网上已有很多介绍,这里不再介绍,有想了解的大家可以去IOS官网https://developer. ...

  9. R read.table函数的check.names参数

    今天用cummeRbund 对cuffdiff的结果进行可视化, 一直报错,之前跑的好好的,找了半天原因, 原来出现在read.table这个函数上: read.table有一个参数check.nam ...

  10. linux用户态定时器的使用---19

    原创博文,转载请标明出处--周学伟http://www.cnblogs.com/zxouxuewei/ linux操作系统为每一个进程提供了3个内部计时器. ITIMER_REAL;ITIMER_VI ...