[学习笔记] fhq Treap 平衡树

fhqTreap:无旋平衡树与操作详解
本文介绍了fhqTreap,一种弱平衡的二叉搜索树,通过结合搜索树和堆的性质,避免了二叉搜索树退化为链。文章详细讲解了fhqTreap的定义、核心操作如分裂和合并,以及基本操作如插入、删除和查询。

fhq Treap 也叫无旋Treap (好像?我也不知道)

反正我带旋 Treap 是不会滴,其他的平衡树也不会(但是会平板电视)

fhq Treap 好写,码量小,缺点是常数比较大

定义

二叉搜索树

二叉搜索树是一种二叉树的树形数据结构,其定义如下:

  1. 空树是二叉搜索树。

  2. 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。

  3. 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。

  4. 二叉搜索树的左右子树均为二叉搜索树。

至于二叉搜索树怎么写我也不知道

但是由于可以构造数据使得二叉搜索树退化成一条链所以平衡树就应运而生了

平衡树是通过左旋和右旋各种奇怪的操作使左子树和右子树的高度最多相差 1 的二叉搜索树

Treap 就是一种弱平衡的平衡树

Treap 顾名思义就是 Tree + Heap 是加入了堆来防止二叉搜索树退化(说白了就是随机化

其中,二叉搜索树的性质是:

左子节点的值( val \textit{val} val)比父节点大
右子节点的值( val \textit{val} val)比父节点小(当然这也是可以反过来的)

堆的性质是:

子节点值( key \textit{key} key)比父节点大或小(取决于是小根堆还是大根堆)
不难看出,如果用的是同一个值,那这两种数据结构的性质是矛盾的,所以我们再在搜索树的基础上,引入一个给堆的值 key \textit{key} key。对于 val \textit{val} val 值,我们维护搜索树的性质,对于 key \textit{key} key 值,我们维护堆的性质。其中 key \textit{key} key 这个值是随机给出的。

搬个 OI-Wiki 的图片
在这里插入图片描述
Treap 的核心操作是左旋和右旋 而 fhq Treap 则是不带旋的 Treap,很多情况下会一些操作好写很多

fhq Treap

fhq Treap 的核心操作是分裂 ( s p l i t split split) 和 合并 ( m e r g e merge merge)

节点信息

一个节点中的信息应该很好想罢,值 v a l val val ,键值 k e y key key, 左儿子 l l l ,右儿子 r r r 以及子树大小 s i z siz siz

struct treap{
   
   
	int val,key,siz,l,r;
}fhq[N << 1];

建立新节点

建立一个新节点其实就是把一个节点初始化掉

int new_treap(int val){
   
   
	fhq[++cnt].val = val;
	fhq[cnt].key = rand();
	fhq[cnt].siz = 1;
	return cnt;
}

很好理解对吧

更新父节点信息

其实就和线段树的 p u s h _ u p push \_ up push_up操作是一样的

void push_up(int pos){
   
   
	fhq[pos].siz = fhq[fhq[pos].l].siz + fhq[fhq[pos].r].siz + 1;
}

分裂

分裂操作有两种,一种是按值分裂,把所有值小于等于 v a l val val 的分裂成一颗树,把值 大于 v a l val val分裂成一颗树;一种是按大小分裂,把小于等于给定大小的分裂成一棵树,大于给定大小的分裂成一颗树

一般把 fhq Treap 当正常平衡树使用的时候都是用按值分裂

直接看代码理解罢

void split(int pos, int val, int& x, int& y){
   
   //因为是一棵树分裂成两颗树,返回pair会比较麻烦,所以直接引用一下 
	if(!pos) {
   
   //到底了不能分裂 
		x = y = 0;
		return
### FHQ Treap 实现区间查询功能 FHQ Treap 是一种基于分裂和合并操作的平衡树结构,能够高效地处理动态序列上的各种操作。为了实现区间查询功能,可以利用其高效的子树分割与合并能力。 #### 节点定义 FHQ Treap 的基本节点除了存储键值外还需要额外的信息用于支持快速查询: ```cpp struct Node { int val, size; // 当前结点权值 和 子树小 long long sum; // 维护当前区间的总和或其他聚合属性 Node *ch[2]; // 左右孩子指针 Node(int v):val(v),size(1),sum(v){ ch[0]=ch[1]=nullptr; } }; ``` #### 关键函数:Split 和 Merge - **Split** 函数按照给定的关键字 `k` 将一棵树分为两部分; - **Merge** 函数则将两个有序的部分重新组合成完整的树形结构[^1]。 通过上述两种基础变换,可以在 O(log n) 时间复杂度内完成任意位置的数据访问以及修改。 对于具体的区间求和问题,则需进一步扩展节点信息并调整相应逻辑: 当执行 Split 或者 Rotate 操作时同步更新涉及路径上各节点所携带的辅助字段 (如累加和),从而保证每次查询都能获得最新状态下的正确结果。 下面给出一段简单的 C++ 代码片段展示如何构建这样的数据结构及其核心方法: ```cpp // 合并两棵 fhq-treap 成为新的 fhq-treap Node* merge(Node *a, Node *b) { if (!a || !b) return a ? a : b; if ((rand() % (a->size + b->size)) < a->size) { a->push_down(); // 更新懒标记(如果有的话) a->ch[1] = merge(a->ch[1], b); a->pull_up(); return a; } else { b->push_down(); b->ch[0] = merge(a, b->ch[0]); b->pull_up(); return b; } } pair<Node*, Node*> split(Node *rt, int k) { if (!rt) return make_pair(nullptr, nullptr); rt->push_down(); pair<Node *, Node *> res; if (get_size(rt->ch[0]) >= k) { auto tmp = split(rt->ch[0], k); rt->ch[0] = tmp.second; rt->pull_up(); res.first = tmp.first; res.second = rt; } else { auto tmp = split(rt->ch[1], k-get_size(rt->ch[0])-1); rt->ch[1] = tmp.first; rt->pull_up(); res.first = rt; res.second = tmp.second; } return res; } ``` 以上实现了最基础版本的支持区间查询特性的 FHQ Treap 。实际应用中可能还需考虑更多细节优化性能表现,比如引入惰性传播机制减少不必要的计算开销等[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bamboo_Day

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值