treap 学习小记
介绍
treap是tree和heap的组合词,说明这种数据结构有树的特点又有堆的特点。本质是一颗二叉搜索树。
treap的结点除了key关键字外还有个priority关键字。treap除了要保证key满足二叉搜索树性质,还要保证当前priority大于等于两个子节点的priority(即堆的性质)。由于priority是随机产生的值,故treap的结构是期望平衡的。
treap分为有旋treap和无旋treap。有旋treap就是带旋转操作的treap;无旋treap就是利用merge和split两个操作来解决各种问题。
有旋treap旋转常数比splay树小且好写,但好像不支持区间操作(还不清楚为什么);无旋treap好写功能强大,但比较慢。
无旋treap
无旋treap有两个核心操作,merge和split。
两个操作的均摊复杂度是O(logn)
split操作
split操作接受根节点u和值key,把一个u指向的treap拆成两个treap,其中一个treap中每个结点的关键值小于等于key,另一个treap的关键值则大于key。
递归判断分裂,看代码会好理解。
PII split(int rt, int key) { //返回的o.first小于等于,o.second为大于。
if (!rt) {
return mp(0, 0);
}
if (key < val[rt]) {
PII o = split(lc[rt], key);
lc[rt] = o.second;
return mp(o.first, rt);
} else {
PII o = split(rc[rt], key);
rc[rt] = o.first;
return mp(rt, o.second);
}
}
merge操作
merge操作接受两个根节点u和v,把u指向的treap和v指向的treap合并成一个treap。必须要保证u中所有结点的关键值小于v中所有结点。merge就是split的逆操作。由于两个treap之间是有序的,所以根据priority来递归merge,需满足大根堆性质。
int merge(int lrt, int rrt) {
if (!lrt)
return rrt;
if (!rrt)
return lrt;
if (pos[lrt] > pos[rrt]) { //这里的pos就是priority,我换了个名字
rc[lrt] = merge(rc[lrt], rrt);
return lrt;
} else {
lc[rrt] = merge(lrt, lc[rrt]);
return rrt;
}
}
build
先判断插入的新结点是否已存在,如果不存在,就按新结点的关键值分裂成两个treap,然后再把结点夹进去merge。(split和merge真是太直观了)
建树好像有个O(n)的笛卡尔建树方法。
void insert(int& rt, int v) {
if (!find(rt, v)) {
si++;
pos[si] = rand();
val[si] = v;
PII o = split(rt, v);
rt = merge(merge(o.first, si), o.second);
}
}
erase
erase之前必须保证当前的值在treap中存在,然后分裂成3份,去掉待删除的部分再合并起来。insert的逆操作
void erase(int& rt, int v) {
if (!find(rt, v)) return ;
PII o = split(rt, v);
rt = merge(split(o.first, v - 1).first, o.second);
}
其它操作
查询操作就和普通二叉搜索树一样了。
代码
纯的无旋treap
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <string>
#include <stack>
#include <deque>
#include <cmath>
#include <iomanip>
#include <cctype>
#define endl '\n'
#define IOS \
std::ios::sync_with_stdio(0); \
cout.tie(0); \
cin.tie(0);
#define FILE freopen("..//data_generator//in.txt", "r", stdin), freopen("res.txt", "w", stdout)
#define FI freopen("..//data_generator//in.txt", "r", stdin)
#define FO freopen("res.txt", "w", stdout)
#define pb cpush_back
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
#define INF 0x3f3f3f3f
const int N = 1e5 + 10;
const int M = 1e9 + 7;
const double eps = 1e-8;
int pos[N];
int lc[N], rc[N];
int val[N];
int si;
typedef pair<int, int> PII;
void print(int rt) {
if(!rt) return ;
print(lc[rt]);
cout << val[rt] << " ";
print(rc[rt]);
}
int find(int rt, int v) {
if (!rt)
return rt;
if (v > val[rt]) {
return find(rc[rt], v);
} else if (v < val[rt]) {
return find(lc[rt], v);
} else
return rt;
}
PII split(int rt, int key) {
if (!rt) {
return mp(0, 0);
}
if (key < val[rt]) {
PII o = split(lc[rt], key);
lc[rt] = o.second;
return mp(o.first, rt);
} else {
PII o = split(rc[rt], key);
rc[rt] = o.first;
return mp(rt, o.second);
}
}
int merge(int lrt, int rrt) {
if (!lrt)
return rrt;
if (!rrt)
return lrt;
if (pos[lrt] > pos[rrt]) {
rc[lrt] = merge(rc[lrt], rrt);
return lrt;
} else {
lc[rrt] = merge(lrt, lc[rrt]);
return rrt;
}
}
void insert(int& rt, int v) {
if (find(rt, v)) return ;
si++;
pos[si] = rand();
val[si] = v;
PII o = split(rt, v);
rt = merge(merge(o.first, si), o.second);
}
void erase(int& rt, int v) {
if (!find(rt, v)) return ;
PII o = split(rt, v);
rt = merge(split(o.first, v - 1).first, o.second);
}
int main() {
IOS;
}
有旋treap
没有具体地去实现qwq,有机会再补吧。