平衡树
\(Write-by-尹以豪\)
本篇文章主要介绍 \(\sf Treap\) 以及 \(\Huge fhq-Treap\) 。
PS* :本篇文章只是堆 \(\sf Treap\) 做大概的讲解,着重分析 \(\Huge fhq-Treap\)
\(\sf Treap\) 的简介
顾名思义 \(\boxed{ Treap = Tree + Heap}\) ,也就是 \(\sf Treap\) 同时满足 \(Tree\) (二叉搜索树) 的优美性质,也满足 \(Heap\) (堆) 的性质
专业的,\(\sf Treap\) 是一种 弱平衡 的 二叉搜索树 。
二叉搜索树只需要了解其性质吧
二叉搜索树有着优美的性质,我们对二叉搜索树经行操作 (插入,删除,找最大,找最小) ,所需的时间复杂度是 \(O(h)\) ,也就是树的高度,但有时候树的形态是一条链的话 (就比如下面这一个)

这样的话时间复杂度就直接变成 \(\color{red}{O(n)}\) 了 \(\tiny TAT\) ,那我们再 \(q\) 次操作 \((1 \leq q \leq 10^5)\) ,你不就炸了吗,而且不难发现,假设这一条链只有 \(3\) 个节点,我们可以令 \(2\) 为这一颗树的根节点, \(1\) 为 \(2\) 的左儿子, \(3\) 为 \(2\) 的右儿子,如下。

这样的时间复杂度就会缩小,如果有 \(10^5\) 个节点呢?我们也可以对其经行转化,单次处理的时间复杂度就由 \(O(n)\) 变为 \(O(\log n)\) 了!怎么转化呢,这就要我们的 \(\sf Treap\) 了。
\(\sf Treap\) 的各种操作
首先先讲讲 \(\sf Treap\) 是怎么维护他的「平衡」,其实特别抽象巧妙,就是随机数说出来可能不信 ,我第一次了解到甚至觉得是玄学。
言归正传,\(\sf Treap\) 每一个节点不仅有一个权值 (\(\small val\))也通过给每一个节点随机分配了一个优先级(\(\small priority\))。
其中,权值 (\(\small val\))用来维护二叉搜索树的性质,也就是如下:
- 对于节点 \(x\) ,左子树全部节点的权值(\(\small val\))均比 \(x\) 的权值(\(\small val\))小。
- 对于节点 \(x\) ,右子树全部节点的权值(\(\small val\))均比 \(x\) 的权值(\(\small val\))大。
优先级(\(\small priority\))维护的是堆的性质:
- 比父亲节点大或者小 (具体看是大根堆还是小根堆)
那我们为什么需要大费周章的去让这个数据结构符合树和堆的性质,并且随机给出堆的值呢?
要理解这个,首先需要理解朴素二叉搜索树的问题。在给朴素搜索树插入一个新节点时,我们需要从这个搜索树的根节点开始递归,如果新节点比当前节点小,那就向左递归,反之亦然。
最后当发现当前节点没有子节点时,就根据新节点的值的大小,让新节点成为当前节点的左或右子节点。
如果插入结点的权值是随机的(换言之,是随机插入的),那这个朴素搜索树的高度较小(接近 \(\log n\) ,其中 \(n\) 为结点数),而每一层的节点数较多,即它的形状会非常的「平衡」。\(\sf Treap\) 就是一个例子。因此此时的任意操作复杂度都将会是 \(O(\log n)\) 左右。
\(\sf Treap\) 解决了退化成链、达到一个较为「平衡」的状态,通过维护随机的优先级满足堆性质,「打乱」了节点的插入顺序,从而让二叉搜索树达到了理想的复杂度,避免了退化成链的问题。
旋转 ,就是以 \(O(1)\) 的时间复杂度将一棵子树的根节点转换为根节点的儿子节点,来维护「平衡」,不做讲解。
还有许多的操作,但这里不做详细的讲解
很难理解对吧,那不如看看fhq-Treap,很好理解的awa
\(\Huge fhq-Treap\)
\(fhq-Treap\) 是平衡树的一种,它不用 旋转 等操作来维护树的平衡,而且只有两个主要操作,分别是 分裂(split) 与 合并(merge)
无旋 treap 的操作方式使得它天生支持维护序列、可持久化等特性。
分裂(split)操作
分裂操作接受两个参数:根指针 \(\small root\) 、关键值 \(\small key\) 。分裂操作结果是将以 \(\small root\) 为根节点的子树分裂成两个 \(\sf treap\) ,第一个 \(\sf treap\) 的所有节点的值都 \(\leq \small key\) ,第二个 \(\sf treap\) 的所有节点的值 \(> \small key\) 。
我觉得是十分好理解的,那就直接上代码吧。
void split(int now,int val, int &x, int &y)
{
if(!now) x = y = 0;
else
{
if(fhq[now].val <= val) // 当前正在考虑的节点(根) <= val ,now 以及他的左子树全部都属于分裂后的第一个子树
{
x = now;
split(fhq[now].r, val, fhq[now].r, y);
}
else // 反之,则 now 及他的右子树全部都属于分裂后的第二个子树
{
y = now;
split(fhq[now].l, val, x, fhq[now].l);
}
update(now); // 相当于线段树中的 pushup
}
}
补充一下,查询一个数的排名可以这样写:
void getrank(int val)
{
split(root, val - 1, x, y);
printf("%d\n", fhq[x].size + 1);
root = merge(x, y);
}
void getnum(int rank)
{
int now = root;
while(now)
{
if(fhq[fhq[now].l].size + 1 == rank)
break;
else if(fhq[fhq[now].l].size >= rank)
now = fhq[now].l;
else
{
rank -= fhq[fhq[now].l].size + 1;
now = fhq[now].r;
}
}
printf("%d\n", fhq[now].val);
}
合并操作(merge)
合并过程接受两个参数:左 \(\sf treap\) 的根指针 \(\textit{u}\) 、右 \(\sf treap\) 的根指针 \(\textit{v}\) 。必须满足 \(\textit{u}\) 中所有结点的值小于等于 \(\textit{v}\) 中所有结点的值。一般来说,我们合并的两个 \(\sf treap\) 都是原来从一个 \(\sf treap\) 中分裂出去的,所以不难满足 \(\textit{u}\) 中所有节点的值都小于 \(\textit{v}\) 。
前面的 \(\sf Treap\) ,是通过 旋转操作 来维护 \(\small priority\) 的堆性质,同时还能维护二叉搜索树本身的性质,在 $ fhq-Treap$ 中,我们通过 合并操作 来达成同样的效果。
因为我们要合并的两个 \(\sf treap\) 都已经有序了,所以只需要根据 \(\small proirity\) 的数据来维护堆的性质,其他的具体见代码。
\(\scriptscriptstyle 打的是真累啊,不如直接把代码甩给你们qwq,干巴巴地陈述是真难搞!>A<\)
// 维护大根堆的 treap
int merge(int x, int y) // 返回的是一个值 (当前这个子树那个是根节点)
{
if(!x or !y) return x + y;
if(fhq[x].key > fhq[y].key) // x 的值比 y 的大,所以以 x 为根
{
fhq[x].r = merge(fhq[x].r,y);
update(x); // 相当于 pushup
return x;
}
else // 反之则就以 y 为根
{
fhq[y].l = merge(x, fhq[y].l);
update(y); // 相当于 pushup
return y;
}
}
插入操作(insert)
看了 wiki 感觉好困难,我就给个代码附上注释。
void insert(int val)
{
split(root, val, x, y);
root = merge(merge(x, newnode(val)), y);
}
例题 P3369【模板】普通平衡树
我就给出完整代码了,以上没有补充的可以在下面补充了\(\scriptscriptstyle 吧\) 。
#include<bits/stdc++.h>
#include<random>
#define LL long long
const int maxn = 1e5 + 5;
struct Node
{
int l, r;
int val, key;
int size;
}fhq[maxn];
int cnt, root;
std::mt19937 rnd(233);
int newnode(int val)
{
fhq[++cnt].val = val;
fhq[cnt].key = rnd();
fhq[cnt].size = 1;
return cnt;
}
void update(int now)
{
fhq[now].size = fhq[fhq[now].l].size + fhq[fhq[now].r].size + 1;
}
void split(int now,int val, int &x, int &y)
{
if(!now) x = y = 0;
else
{
if(fhq[now].val <= val)
{
x = now;
split(fhq[now].r, val, fhq[now].r, y);
}
else
{
y = now;
split(fhq[now].l, val, x, fhq[now].l);
}
update(now);
}
}
int merge(int x,int y)
{
if(!x or !y) return x + y;
if(fhq[x].key > fhq[y].key) // > >= < <=
{
fhq[x].r = merge(fhq[x].r,y);
update(x);
return x;
}
else
{
fhq[y].l = merge(x, fhq[y].l);
update(y);
return y;
}
}
int x, y, z;
void ins(int val)
{
split(root, val, x, y);
root = merge(merge(x, newnode(val)), y);
}
void del(int val)
{
split(root, val, x, z);
split(x, val - 1, x, y);
y = merge(fhq[y].l, fhq[y].r);
root = merge(merge(x, y), z);
}
void getrank(int val)
{
split(root, val - 1, x, y);
printf("%d\n", fhq[x].size+1);
root = merge(x, y);
}
void getnum(int rank)
{
int now = root;
while(now)
{
if(fhq[fhq[now].l].size + 1 == rank)
break;
else if(fhq[fhq[now].l].size >= rank)
now = fhq[now].l;
else
{
rank -= fhq[fhq[now].l].size + 1;
now = fhq[now].r;
}
}
printf("%d\n", fhq[now].val);
}
void pre(int val)
{
split(root, val - 1, x, y);
int now = x;
while(fhq[now].r)
now = fhq[now].r;
printf("%d\n", fhq[now].val);
root=merge(x,y);
}
void nxt(int val)
{
split(root,val,x,y);
int now = y;
while(fhq[now].l)
now = fhq[now].l;
printf("%d\n", fhq[now].val);
root = merge(x, y);
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
int opt, x;
scanf("%d%d", &opt, &x);
switch(opt)
{
case 1:
ins(x);
break;
case 2:
del(x);
break;
case 3:
getrank(x);
break;
case 4:
getnum(x);
break;
case 5:
pre(x);
break;
case 6:
nxt(x);
break;
}
}
return 0;
}
练习题
点击查看代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n, m, root;
int tot, ans, s[100005], ls;
struct st
{
int ria, rib;
bool operator < (const st b) const
{
if(ria == b.ria) return rib < b.rib;
else return ria > b.ria;
}
bool operator <= (const st b) const
{
if(ria == b.ria) return rib <= b.rib;
else return ria >= b.ria;
}
}a[100005];
struct ss
{
int lson, rson, rnd, sz, cnt;
st val;
}e[200005];
void push_up(int x)
{
e[x].sz = e[e[x].lson].sz + e[e[x].rson].sz + e[x].cnt;
}
int merge(int x,int y)
{
if(!x || !y) return x | y;
if(e[x].rnd < e[y].rnd)
{
e[x].rson = merge(e[x].rson, y);
push_up(x);
return x;
}
else
{
e[y].lson = merge(x,e[y].lson);
push_up(y);
return y;
}
}
void split(st k, int now, int &x, int &y)
{
if(!now)
{
x = y = 0;
}
else
{
if(e[now].val <= k)
{
x = now;
split(k, e[now].rson, e[now].rson, y);
}
else
{
y = now;
split(k, e[now].lson, x, e[now].lson);
}
}
push_up(now);
}
int new_node(st k)
{
int now;
if(ls) now = s[ls--];
else now = ++tot;
e[now].sz = e[now].cnt = 1;
e[now].val = k;
e[now].rnd = rand();
e[now].lson = e[now].rson = 0;
return now;
}
void insert(st x)
{
int l, r;
split(x, root, l, r);
l = merge(l, new_node(x));
root = merge(l, r);
}
void del(st x)
{
int l, a, r;
st y = x;
y.rib--;
split(x, root, l, r);
split(y, l, l, a);
s[++ls] = a;
a=merge(e[a].lson, e[a].rson);
root = merge(merge(l, a), r);
}
int ask_val(st x)
{
int l, r;
x.rib--;
split(x, root, l, r);
int re = e[l].sz + 1;
root = merge(l, r);
return re - 1;
}
typedef unsigned int ui;
ui randNum(ui& seed, ui last, const ui m) {
seed = seed * 17 + last;
return seed % m + 1;
}
ui seed, last = 7;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while(t--)
{
srand(19260817);
cin >> m >> n >> seed;
root = ls = tot = 0;
for(int i = 1; i <= m; i++)
a[i].ria = a[i].rib = 0, insert(a[i]);
int u, v;
for(int i = 1; i <= n; i++)
{
u = randNum(seed, last, m);
v = randNum(seed, last, m);
del(a[u]);
a[u].ria++;
a[u].rib += v;
insert(a[u]);
last = ask_val(a[u]);
cout << last << '\n';
}
}
return 0;
}
点击查看代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 8e4 + 5;
int n, m, id[maxn], a[maxn], root, r1, r2, r3, r4, cnt = 0;
struct treap{
int ch[2], fa, size, rd, val;
}t[maxn];
int newnode(int val)
{
t[++cnt].val = val;
t[cnt].rd = rand();
t[cnt].size = 1;
id[val] = cnt;
return cnt;
}
void pushup(int x)
{
t[x].size = t[t[x].ch[0]].size + t[t[x].ch[1]].size + 1;
}
void split(int x, int k, int &a, int &b, int faa = 0, int fab = 0)
{
if(x == 0)
{
a = b = 0;
return;
}
if(k <= t[t[x].ch[0]].size)
{
t[x].fa = fab;
b = x;
split(t[x].ch[0], k, a, t[x].ch[0], faa, x);
}
else
{
t[x].fa = faa;
a = x;
split(t[x].ch[1], k - t[t[x].ch[0]].size - 1, t[x].ch[1], b, x, fab);
}
pushup(x);
}
int merge(int x, int y)
{
if(x == 0 || y == 0) return x + y;
if(t[x].rd < t[y].rd)
{
t[x].ch[1] = merge(t[x].ch[1], y);
t[t[x].ch[1]].fa = x;
pushup(x);
return x;
}
else
{
t[y].ch[0] = merge(x, t[y].ch[0]);
t[t[y].ch[0]].fa = y;
pushup(y);
return y;
}
}
void insert(int pos, int val)
{
split(root, pos, r1, r2);
root = merge(r1, merge(newnode(val), r2));
}
bool get(int x)
{
return t[t[x].fa].ch[1] == x;
}
int find(int cnt)
{
int node = cnt, res = t[t[cnt].ch[0]].size + 1;
while(node != root and cnt)
{
if(get(cnt)) res += t[t[t[cnt].fa].ch[0]].size + 1;
cnt = t[cnt].fa;
}
return res;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
char opt[10];
int x, y, k;
cin >> n >> m; srand(19260817);
for(int i = 1; i <= n; i++)
cin >> a[i], insert(i - 1, a[i]);
for(int i = 1; i <= m; i++)
{
cin >> opt >> x;
if(opt[0] == 'T')
{
k = find(id[x]);
split(root, k, r1, r3);
split(r1, k - 1, r1, r2);
root = merge(r2, merge(r1, r3));
}
if(opt[0] == 'B')
{
k = find(id[x]);
split(root, k, r1, r3, 0);
split(r1, k - 1, r1, r2, 0);
root = merge(r1, merge(r3, r2));
}
if(opt[0] == 'I')
{
cin >> y;
k = find(id[x]);
if(y)
{
if(y > 0)
{
split(root, k + 1, r3, r4);
split(r3, k, r2, r3);
split(r2, k - 1, r1, r2);
root = merge(r1, merge(r3, merge(r2, r4)));
}
else
{
split(root, k, r3, r4);
split(r3, k - 1, r2, r3);
split(r2, k - 2, r1, r2);
root = merge(r1, merge(r3, merge(r2, r4)));
}
}
}
if(opt[0] == 'A')
{
k = find(id[x]);
cout << k - 1 << '\n';
}
if(opt[0] == 'Q')
{
split(root, x, r1, r2);
int node = r1;
while(t[node].ch[1]) node = t[node].ch[1];
cout << t[node].val << '\n';
root = merge(r1, r2);
}
}
return 0;
}
点击查看代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
struct Node
{
Node* ch[2];
int val, prio;
int cnt;
int siz;
bool to_rev = false;
Node(int _val) : val(_val), cnt(1), siz(1)
{
ch[0] = ch[1] = nullptr;
prio = rand();
}
int upd_siz()
{
siz = cnt;
if(ch[0] != nullptr) siz += ch[0] -> siz;
if(ch[1] != nullptr) siz += ch[1] -> siz;
return siz;
}
void pushdown()
{
swap(ch[0], ch[1]);
if(ch[0] != nullptr) ch[0] -> to_rev ^= 1;
if(ch[1] != nullptr) ch[1] -> to_rev ^= 1;
to_rev = false;
}
void check_tag()
{
if(to_rev)
pushdown();
}
};
struct Seg_treap
{
Node* root;
#define siz(_) (_ == nullptr ? 0 : _ -> siz)
pair<Node*, Node*> split(Node* cur, int sz)
{
if(cur == nullptr) return {nullptr, nullptr};
cur -> check_tag();
if(sz <= siz(cur -> ch[0]))
{
auto temp = split(cur -> ch[0], sz);
cur -> ch[0] = temp.second;
cur -> upd_siz();
return {temp.first, cur};
}
else
{
auto temp = split(cur -> ch[1], sz - siz(cur -> ch[0]) - 1);
cur -> ch[1] = temp.first;
cur -> upd_siz();
return {cur, temp.second};
}
}
Node* merge(Node* sm, Node* bg)
{
if(sm == nullptr and bg == nullptr) return nullptr;
if(sm != nullptr and bg == nullptr) return sm;
if(sm == nullptr and bg != nullptr) return bg;
sm -> check_tag(), bg -> check_tag();
if(sm -> prio < bg -> prio)
{
sm -> ch[1] = merge(sm -> ch[1], bg);
sm -> upd_siz();
return sm;
}
else
{
bg -> ch[0] = merge(sm, bg -> ch[0]);
bg -> upd_siz();
return bg;
}
}
void insert(int val)
{
auto temp = split(root, val);
auto l_tr = split(temp.first, val - 1);
Node* new_node;
if(l_tr.second == nullptr) new_node = new Node(val);
Node* l_tr_combined = merge(l_tr.first, l_tr.second == nullptr ? new_node : l_tr.second);
root = merge(l_tr_combined, temp.second);
}
void seg_rev(int l, int r)
{
pair<Node*, Node*> less = split(root, l - 1);
pair<Node*, Node*> more = split(less.second, r - l + 1);
more.first -> to_rev = true;
root = merge(less.first, merge(more.first, more.second));
}
void print(Node* cur)
{
if(cur == nullptr) return;
cur -> check_tag();
print(cur -> ch[0]);
cout << cur -> val << ' ';
print(cur -> ch[1]);
}
};
Seg_treap tr;
int main()
{
srand(time(nullptr));
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
tr.insert(i);
while(m--)
{
int l, r;
cin >> l >> r;
tr.seg_rev(l, r);
}
tr.print(tr.root);
return 0;
}
Ending
最上面的两幅图是我在机房用 Python 的第三方库 Manim 画的 (骄傲),代码如下 (图2).
点击查看代码
from manim import *
class TreeNode:
def __init__(self, value, x=0, y=0, radius=0.5):
self.value = value
self.left = None
self.right = None
self.x = x
self.y = y
self.radius = radius
self.circle = Circle(radius=radius, fill_opacity=1, color=BLUE_C).move_to([x, y, 0])
self.text = Text(str(value)).move_to([x, y, 0])
self.group = VGroup(self.circle, self.text)
class LinkedTree(Scene):
def construct(self):
root = TreeNode(2, x=0, y=1)
self.play(Create(root.group))
self.wait(0.5)
node1 = TreeNode(1, x=-2, y=-1)
self.add_edge(root, node1)
self.play(Create(node1.group))
self.wait(0.5)
node3 = TreeNode(3, x=2, y=-1)
self.add_edge(root, node3)
self.play(Create(node3.group))
self.wait(0.5)
def add_edge(self, parent, child):
edge = Line(
start=[parent.x, parent.y - parent.radius, 0],
end=[child.x, child.y + child.radius, 0],
color=WHITE
)
self.play(Create(edge))
setattr(child, 'edge', edge)

浙公网安备 33010602011771号