\(Treap = Tree + Heap\)

树堆(Treap),在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。
----百度百科

要了解Treap,就先要看看什么是二叉搜索树

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
----百度百科

Treap既有BST的性质,也有堆的性质,Treap的每个结点额外附加一个随机值(优先级),让他们按照关键码构成BST的同时也满足堆的性质(父节点优先级高于或低于子节点优先级),因为优先级是随机的,这样在绝大多数情况下我们得到的树都是平衡的,不容易被卡成链, 单次操作期望时间复杂度为O(logn)。

操作:

1.定义

struct Treap {
int dat, val;
int cnt, size, l, r;
} e[N];

dat 随机优先级; val BST关键码; cnt 当前结点数量; size 子树结点数量; l,r 左右儿子。

2.更新

void update(int p) {
e[p].size = e[e[p].l].size + e[e[p].r].size + e[p].cnt;
}

更新结点size值。

3.新建结点

int newpoint(int val) {
e[++tot].val = val;
e[tot].dat = rand();
e[tot].cnt = e[tot].size = 1;
return tot;
}

4.建树

void build() {
newpoint(-inf); newpoint(inf);
root = 1; e[root].r = 2; update(root);
}

初始化两个结点,设置为无限大和无限小。

5.左右旋转

左右旋转是在保证BST性质前提下(中序遍历不变)将一个结点向上或者向下转,实质上是父节点变成了子节点,子节点变成了父节点。

左旋和右旋分别对应着将左儿子翻上来和将右儿子翻上来。



具体操作

void zig(int &p) {//左旋
int q = e[p].l;
e[p].l = e[q].r; e[q].r = p; p = q;
update(e[p].r); update(p);
}
void zag(int &p) {//右旋
int q = e[p].r;
e[p].r = e[q].l; e[q].l = p; p = q;
update(e[p].l); update(p);
}
//p引用的是旋转前的根结点

6.插入

插入一个值为val的结点,若存在关键码为val的结点,将此结点数量 + 1,若不存在,根据val大小和当前结点val大小向左右两边找,最后新建结点,返回时要维护堆性质。

void insert(int &p, int val) {
if(p == 0) {
p = newpoint(val); return ;
}
if(e[p].val == val) {
e[p].cnt++; update(p);
return ;
}
if(val < e[p].val) {
insert(e[p].l, val);
if(e[p].dat < e[e[p].l].dat) zig(p);
} else {
insert(e[p].r, val);
if(e[p].dat < e[e[p].r].dat) zag(p);
}
update(p);
}

7.删除结点

删除一个值为法val的结点,先在树中查找值为val的结点:

1.若结点cnt(数量) 大于1,则可以直接减1,返回;

2.若结点数量为1,将其向下旋转,转到叶结点时直接删除;

注意:在向下旋转时应维护堆的性质,若是大根堆,选取子节点中dat大的结点与其交换。

void delet(int &p, int val) {
if(p == 0) return ;
if(val == e[p].val) {
if(e[p].cnt > 1) {
e[p].cnt--; update(p); return ;
} else {
if(e[p].l || e[p].r) {//不为叶结点
if(e[e[p].l].dat > e[e[p].r].dat || !e[p].r)
zig(p), delet(e[p].r, val);
else
zag(p), delet(e[p].l, val);
update(p);
} else p = 0;
}//else
return ;
}
if(val < e[p].val) delet(e[p].l, val);
else delet(e[p].r, val);
update(p);
}

8.排名

包括根据值查排名和根据排名查值。

1.根据值查排名(rankk();)我们只需要从根节点往下找,如果当前结点值较大,递归查询左儿子;如果当前结点值较小,递归查询右儿子,返回时要加上左子树结点数和当前节点的数量。

2.根据排名查值(arcrank();),同样,如果当前结点值较大, 递归查询左儿子; 如果当前结点值较小,递归查询右儿子,注意查询右儿子时应将排名减去左子树和当前结点的数量。

int rankk(int p, int val) {
if(p == 0) return 0;
if(val == e[p].val) return e[e[p].l].size + 1;
if(val < e[p].val) return rankk(e[p].l, val);
return rankk(e[p].r, val) + e[e[p].l].size + e[p].cnt;
}
int arcrank(int p, int k) {
if(p == 0) return inf;
if(k <= e[e[p].l].size) return arcrank(e[p].l, k);
if(k <= e[p].cnt + e[e[p].l].size) return e[p].val;
return arcrank(e[p].r, k - e[p].cnt - e[e[p].l].size);
}

9.查询前驱和后继

拿前驱来说,若查询v的前驱,分两种情况:

1.找到了值为v的结点,那么v的前驱就是v的左子树中值最大的点的值, 找到直接返回。

2.没有找到结点为v的值,需要不断比较,若当前结点值比v小且比找到的答案大,就更新答案。

后继与前驱类似。

int getper(int val) {
int p = root, ans = 1;//初始化为无限小
while(p) {
if(val == e[p].val) {
if(e[p].l) {
p = e[p].l;
while(e[p].r) p = e[p].r;
ans = p;
}
break;
}
if(e[p].val < val && e[ans].val < e[p].val) ans = p;//比较,更新答案
if(val < e[p].val) p = e[p].l;
else p = e[p].r;
}
return e[ans].val;
}
int getnext(int val) {
int p = root, ans = 2;//初始化为无限大
while(p) {
if(val == e[p].val) {
if(e[p].r) {
p = e[p].r;
while(e[p].l) p = e[p].l;
ans = p;
}
break;
}
if(e[p].val > val && e[ans].val > e[p].val) ans = p;
if(val < e[p].val) p = e[p].l;
else p = e[p].r;
}
return e[ans].val;
}

完整代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 100005;
const int inf = (1 << 31) - 1;
int n, root, tot = 0;
struct Treap {
int dat, val;
int cnt, size, l, r;
} e[N];
void update(int p) {
e[p].size = e[e[p].l].size + e[e[p].r].size + e[p].cnt;
}
int newpoint(int val) {
e[++tot].val = val;
e[tot].dat = rand();
e[tot].cnt = e[tot].size = 1;
return tot;
}
void build() {
newpoint(-inf); newpoint(inf);
root = 1; e[root].r = 2; update(root);
}
void zig(int &p) {
int q = e[p].l;
e[p].l = e[q].r; e[q].r = p; p = q;
update(e[p].r); update(p);
}
void zag(int &p) {
int q = e[p].r;
e[p].r = e[q].l; e[q].l = p; p = q;
update(e[p].l); update(p);
}
void insert(int &p, int val) {
if(p == 0) {
p = newpoint(val); return ;
}
if(e[p].val == val) {
e[p].cnt++; update(p);
return ;
}
if(val < e[p].val) {
insert(e[p].l, val);
if(e[p].dat < e[e[p].l].dat) zig(p);
} else {
insert(e[p].r, val);
if(e[p].dat < e[e[p].r].dat) zag(p);
}
update(p);
}
void delet(int &p, int val) {
if(p == 0) return ;
if(val == e[p].val) {
if(e[p].cnt > 1) {
e[p].cnt--; update(p); return ;
} else {
if(e[p].l || e[p].r) {
if(e[e[p].l].dat > e[e[p].r].dat || !e[p].r)
zig(p), delet(e[p].r, val);
else
zag(p), delet(e[p].l, val);
update(p);
} else p = 0;
}
return ;
}
if(val < e[p].val) delet(e[p].l, val);
else delet(e[p].r, val);
update(p);
}
int rankk(int p, int val) {
if(p == 0) return 0;
if(val == e[p].val) return e[e[p].l].size + 1;
if(val < e[p].val) return rankk(e[p].l, val);
return rankk(e[p].r, val) + e[e[p].l].size + e[p].cnt;
}
int arcrank(int p, int k) {
if(p == 0) return inf;
if(k <= e[e[p].l].size) return arcrank(e[p].l, k);
else if(k <= e[p].cnt + e[e[p].l].size) return e[p].val;
return arcrank(e[p].r, k - e[p].cnt - e[e[p].l].size);
}
int getper(int val) {
int p = root, ans = 1;
while(p) {
if(val == e[p].val) {
if(e[p].l) {
p = e[p].l;
while(e[p].r) p = e[p].r;
ans = p;
}
break;
}
if(e[p].val < val && e[ans].val < e[p].val) ans = p;
if(val < e[p].val) p = e[p].l;
else p = e[p].r;
}
return e[ans].val;
}
int getnext(int val) {
int p = root, ans = 2;
while(p) {
if(val == e[p].val) {
if(e[p].r) {
p = e[p].r;
while(e[p].l) p = e[p].l;
ans = p;
}
break;
}
if(e[p].val > val && e[ans].val > e[p].val) ans = p;
if(val < e[p].val) p = e[p].l;
else p = e[p].r;
}
return e[ans].val;
}
int main() {
// freopen("data.in", "r", stdin);
scanf("%d", &n); build();
while(n--) {
int opt, x; scanf("%d%d", &opt, &x);
switch(opt) {
case 1:
insert(root, x);
break;
case 2:
delet(root, x);
break;
case 3:
printf("%d\n", rankk(root, x) - 1);
break;
case 4:
printf("%d\n", arcrank(root, x + 1));
break;
case 5:
printf("%d\n", getper(x));
break;
case 6:
printf("%d\n", getnext(x));
break;
}
}
return 0;
}

Treap总结的更多相关文章

  1. fhq treap最终模板

    新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...

  2. BZOJ 1691: [Usaco2007 Dec]挑剔的美食家 [treap 贪心]

    1691: [Usaco2007 Dec]挑剔的美食家 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 786  Solved: 391[Submit][S ...

  3. BZOJ 1862: [Zjoi2006]GameZ游戏排名系统 [treap hash]

    1862: [Zjoi2006]GameZ游戏排名系统 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1318  Solved: 498[Submit][ ...

  4. 非旋treap模板

    bzoj3580 非旋转treap 在大神教导下发现split一段区间时先split右边再split左边比较好写 #include <cstdio> #include <cstdli ...

  5. POJ2985 The k-th Largest Group[树状数组求第k大值+并查集||treap+并查集]

    The k-th Largest Group Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 8807   Accepted ...

  6. [普通平衡树treap]【学习笔记】

    3224: Tyvj 1728 普通平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 9046  Solved: 3840[Submit][Sta ...

  7. UVALive5031 Graph and Queries(Treap)

    反向操作,先求出最终状态,再反向操作. 然后就是Treap 的合并,求第K大值. #include<cstdio> #include<iostream> #include< ...

  8. 【Treap】bzoj1588-HNOI2002营业额统计

    一.题目 Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司 ...

  9. hdu 4585 Shaolin treap

    Shaolin Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Problem ...

  10. treap 模版

    struct Treap { struct node { node *son[]; int key,siz,wei,cnt; node(int _key,node *f) { son[]=son[]= ...

随机推荐

  1. NOde.js的安装和简介

    1.nodejs的安装 1.1 检测nodejs的版本 node -v (version:版本) 1.2 path配置nodejs 的环境变量(当前版本都是自动安装配置环境变量)指令: path 1. ...

  2. 每天一道Java题[9]

    题目 native关键字的作用是什么? 解答 首先,需了解JNI(Java Native Interface),它是连接Java平台与本地C代码的一个API. 其次,用native关键字声明的方法,是 ...

  3. 这个菜鸟花几个小时写的 DEMO 被码云推荐上首页 ?

    写在最前     没有接触过 AntV 的诸位看客可通过这篇不成文的文章稍作了解.最近 病毒猖獗,遂抽空做了一个相关小 DEMO.数据可视化方面的使用的是 AntV F2,前端框架使用 Vue 快速成 ...

  4. jQuery下载所有版本

    下载地址:http://www.jq22.com/jquery-info122

  5. Hibernate(六)

    ================================缓存============================定义:介于应用程序和永久性数据存储源之间,可以复制数据存储源中的数据. 工作 ...

  6. Request和Session的生命周期

    request的生命周期是request请求域,一个请求结束,则request结束 session的生命周期是session会话域,打开一个浏览器请求一个网站的页面后,Session开始,当sessi ...

  7. 解决dotnet错误 System.InvalidOperationException Message=Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found.

    开始=>设置=>manage user certificats  (管理用户证书),里面所有的.net core的全部删除 然后控制台执行: dotnet dev-certs https ...

  8. Java1变量数据类型和运算符

    day02_变量数据类型和运算符   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class jh_01_数据类型说明 {     public  ...

  9. 通过open上网并设置开机自启与自动连接

    注意:这里所说的上网并不是FQ.只是简单的通过iptables让客户端可以访问外网.因为有些时候有这样的需求 上一篇文章为大家介绍了OpenVPN.以及它的部署工作.而这一篇主要就是讲解如何让客户端可 ...

  10. 小程序websocket心跳库——websocket-heartbeat-miniprogram

    前言 在16年的时候因为项目接触到websocket,而后对心跳重连做了一次总结,写了篇博客,而后18年对之前github上的demo代码进行了再次开发和开源,最终封装成库.如下: 博客:https: ...