FHQ Treap基本原理
一、普通
= +
在平衡树的每个节点存放两个信息:
-
值
值满足二叉搜索树()的性质 -
随机修复值
修复值满足堆的性质 -
二叉搜索树的性质
当前结点左子树的值都比当前结点值小,反之则一定在右子树上。 -
二叉堆的性质(大根堆)
父结点的优先级总是大于或等于任何一个子节点的优先级。
这里提到的优先级,就是上面提到的索引。
总结
在保留二叉搜索树的中序遍历不变的前提下,采用一定的策略(二叉堆+随机数),使树尽量的均衡,就是平衡树的本质,中庸之道也~
二、为什么可以平衡?
我们发现,会遇到不平衡的原因是因为有序的数据会使查找的路径退化成链
而随机的数据使退化的概率是非常小的
在中,修正值的引入恰恰是使树的结构不仅仅取决于节点的值,还取决于修正值的值
然而修正值的值是随机生成的,出现有序的随机序列是小概率事件,
所以的结构是趋向于随机平衡的。
三、
优点:码量小而好写,核心操作的代码都是复读机
好理解、支持的操作多
缺点:常数略大(很大~)
平衡树双子星,很牛B的样子。
四、奇怪的操作
普通用来维护树平衡的 奇怪的操作是什么呢?
树旋转
的奇怪操作并且是核心操作只有两个:
,
只需掌握好这两个基本操作,那么你基本上就已经掌握了 了。
五、存储的信息
结点信息(个):
左右子树编号 这个所有平衡树都一样
值
修复值(随机)
子树大小
六、分裂()
分裂有两种:按值分裂和按大小分裂
按值分裂:把树拆成两棵树,拆出来的一棵树的值全部小于等于给定的值,另外一部分全都大于给定的值。
按大小分裂:把树拆成两棵树,拆出来的一棵树的值全部小于给定的大小,另外一部分的值全部大于等于给定的大小。

七、合并()

八、代码模板
P3369 【模板】普通平衡树
AcWing 253 普通平衡树
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
mt19937 rnd(233); //高性能随机数生成器 随机范围大概在(maxint,+maxint),233为种子,19937指该随机数循环节为2^19937
//重定义ls,rs
#define ls tr[p].l
#define rs tr[p].r
struct Node {
int l, r; //左右儿子的节点号
int rnd; //在堆中编号,随机值
int size; //以当前节点为根的子树中节点的个数
int val; //值
//这里没有记录当前节点值的个数,是不是有点奇怪?
} tr[N];
int root, idx;
//向父节点更新统计信息
void pushup(int p) {
tr[p].size = tr[ls].size + tr[rs].size + 1;
}
//创建一个新节点
int newnode(int val) {
tr[++idx].rnd = rand();
tr[idx].size = 1;
tr[idx].val = val;
return idx;
}
//将以p为根的平衡树进行分裂,小于等于x的都放到以pl为根的子树中,大于x都放到以pr为根的子树中
//因为生成的两个子树最终需要返回两个根,所以这里用了引用的方式
void split(int p, int val, int &x, int &y) {
if (!p) { //当前节点为空,还分裂个屁~
x = y = 0;
return;
}
if (tr[p].val <= val) //当前点归左边,如果当前点<=x,那么它的左子必然也<=x
x = p, split(rs, val, rs, y); //把当前点p接在pl上,继续分它的右子,它的右子可能还有>x的
else
y = p, split(ls, val, x, ls); //把当前点p接在pr上,继续分它的左子,它的左子可能还有<=x的
//推送统计信息
pushup(p);
}
//将以pl,pr为根的两个子树合并成一棵树
//要求:两个子树的值域不能重叠,没有交叉
int merge(int x, int y) {
if (!x || !y) return x + y; //如果pl或者pr有一个是空了,那么返回另一个即可,此处比较取巧,采用了+
int p; //根
if (tr[x].rnd < tr[y].rnd) { //两个都不空,谁来当根呢?需要使用小根堆性质,rk小的当根
p = x; // x当根
tr[x].r = merge(tr[x].r, y); //将y接到x的右子上
} else {
p = y; // y当根
tr[y].l = merge(x, tr[y].l); //将x接到y的左子上
}
//更新统计信息
pushup(p);
return p;
}
//插入操作,插入一个数字val
void insert(int val) {
int x, y, z;
split(root, val, x, z); // 1、小于等于val的划到x子树中,大于val的划到y子树中
y = newnode(val); // 2、创建一个新节点y
root = merge(x, merge(y, z)); // 3、合并三者,因为这三者是需要满足BST有序的,所以顺序不能乱,更新根节点
}
//移除值val
void remove(int val) {
int x, y, z;
split(root, val, x, z); // 1、小于等于v的放x里,大于v的放z里
split(x, val - 1, x, y); // 2、继续分裂x,小于等于v-1的放x里,大于v-1的放y里,即y里全部都是等于val的!
y = merge(tr[y].l, tr[y].r); // 3、y的左子树和右子树合并,相当于把根节点不要了
root = merge(x, merge(y, z)); // 4、合并三者,更新根节点
}
//查询某个值的排名
int get_rank_by_key(int val) {
int x, y;
split(root, val - 1, x, y); // 1、按val-1分裂,此时x中就是所有<=val-1的数
int res = tr[x].size + 1; // 2、统计x为根的树中数字个数+1就是最终排名
root = merge(x, y); // 3、还原,这也太暴力了吧~
return res; // 4、返回排名
}
//查询排名的值
int get_key_by_rank(int k) {
int p = root;
while (p) { // 1、如果当前节点不为空
if (tr[ls].size + 1 == k) // 2、说明当前节点就是要查找的第k个数
return tr[p].val;
if (tr[ls].size >= k) // 3、如果左子树的数字数量大于等于k,在左子树中查找
p = tr[p].l;
else
k -= tr[ls].size + 1, p = rs; // 4、换算下在右子树中查找
}
return -1; // 5、没有找到返回-1
}
//寻找v的前驱
int get_prev(int val) {
int x, y;
split(root, val - 1, x, y); // 1、按v-1分裂,x里的最大的数就是val的前驱
int p = x; // 2、目标肯定在左子中
while (rs) p = rs; // 3、左子的最右边就是答案
int res = tr[p].val; // 4、记录答案
root = merge(x, y); // 5、还原回去
return res;
}
//寻找v的后继
int get_next(int val) {
int x, y;
split(root, val, x, y); // 1、按v分裂,y里的最小的就是val的后继
int p = y; // 2、答案肯定在右子中
while (ls) p = ls; // 3、右子的最左边就是答案
int res = tr[p].val; // 4、记录答案
root = merge(x, y); // 5、还原回去
return res;
}
int main() {
// fhq可以有重复的点
//为了防止找前驱后继时要找的数比FHQ中的数都小/大(比如我们要找FHQ最小的数的前驱)
//我们可以一开始就加入−∞,∞两个哨兵节点。
insert(-INF), insert(INF);
int q;
scanf("%d", &q);
while (q--) {
int op, x;
scanf("%d%d", &op, &x);
if (op == 1)
insert(x);
else if (op == 2)
remove(x);
else if (op == 3)
printf("%d\n", get_rank_by_key(x) - 1);
else if (op == 4)
printf("%d\n", get_key_by_rank(x + 1));
else if (op == 5)
printf("%d\n", get_prev(x));
else if (op == 6)
printf("%d\n", get_next(x));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2018-05-10 今天需要完成的开发任务
2013-05-10 使用PowerDesigner生成数据库测试数据
2013-05-10 开发流程与各层软件选型
2013-05-10 MySQL分区和分布性能测试[转]
2013-05-10 Innodb共享表空间VS独立表空间
2013-05-10 MySQL MyISAM与Innodb优化方案比较