[数据结构]FHQ-Treap
前言(个人评价FHQ-Treap)
这是一个巨佬(名叫范浩强)在冬令营交流的时候提出的数据结构(FHQ:\(\text{你干嘛非要旋转呢?Think Functional!}\))(可以看出FHQ大佬英语可能不太过关)
其实就是非旋Treap. 由于不用复杂的旋转, 所以支持可持久化, 且代码简单易懂(只要您熟悉函数式编程的思想).
由于本人思维怠惰所以觉得这种数据结构很符合我的胃口, 唯一的不足之处在于维护LCT的时候, 要多一个\(\log\)(由于蒟蒻我没有学LCT所以不确定是否正确).
核心思想
FHQ-Treap依然借鉴了Treap的依靠随机权值维护树高的方法, 但在操作时, 从不\(\text{rotate}\), 而是依靠两个核心操作\(\text{split}\)和\(\text{merge}\).
在写代码的时候要时刻记得, 一个根就是一棵树.
基本函数
1.\(\text{split}\)
将一棵平衡树按照权值或排名"分裂"成两棵树, 保证一棵树的权值小于另一棵. 代码中, 以\(\text{x}\)为根的树是权值较小的那一棵.
根据Treap的性质, 每次能直接把一半的节点直接扔进一个分裂出来的树中, 所以时间复杂度\(O(\log{n})\)
void split(int cur, int k, int &x, int &y)
{
if(!cur) x = y = 0;
else
{
if(tr[cur].val <= k) x = cur, split(tr[cur].rs, k, tr[cur].rs, y); //若这个节点的权值<=k, 那么这个节点的左子树都<=k, 直接递归右子树即可.
else y = cur, split(tr[cur].ls, k, x, tr[cur].ls); //否则右子树都>k, 递归左子树即可.
update(cur);
}
}
2.\(\text{merge}\)
有分裂就要有合并. 同样的, 在合并两棵平衡树的时候, 保证一颗树的权值小于另一棵. 代码中, 以\(\text{x}\)为根的树是权值较小的那一棵.
合并就要显然一些了. 只需要考虑是\(\text{x}\)合到\(\text{y}\)的左子树还是\(\text{y}\)合到\(\text{x}\)的右子树. 这个通过随机权值来决定, 递归下去就好了.
inline int merge(int x, int y)
{
if(!x || !y) return x + y;
int ret;
if(tr[x].pri < tr[y].pri) ret = x, tr[x].rs = merge(tr[x].rs, y);
else ret = y, tr[y].ls = merge(x, tr[y].ls);
update(ret);
return ret;
}
没错, 基本函数就这两个, 简单吧! 下面我们来看靠这两个函数可以实现什么.
功能实现
在下面的代码中, 我们定义\(\text{x}\), \(\text{y}\), \(\text{z}\)是一棵分裂出来的子树.
1.\(\text{insert}\)(插入一个数, 权值为\(v\))
极其简单的代码实现. 比Splay简单多了.
从代码明显看到, 插入一个数的思路是, 按照\(v\)先\(\text{split}\), 再\(\text{merge}\)回去.
split(rt, v, x, y);
rt = merge(merge(x, new_Node(v)), y);
2.\(\text{delete}\)(删除一个权值为\(\text{v}\)的数)
极其简单的代码实现. 比Splay简单多了.
\(\text{delete}\)操作的方法是, 先按照\(v\)把原树分裂成\(\text{y}\)和\(\text{z}\), 再按照\(v-1\)把\(\text{y}\)分裂成\(\text{x}\)和\(\text{y}\), 这样\(\text{y}\)中就只剩下了权值为\(v\)的节点.
若删除一个, 那么就合并\(\text{y}\)的左儿子和右儿子, 再并回去, 若删除全部, 则直接合并\(\text{x}\)和\(\text{z}\)即可.
split(rt, v, x, z);
split(x, v - 1, x, y);
rt = merge(merge(x, merge(tr[y].ls, tr[y].rs)), z);
3.\(\text{rank}\)(求一个数的排名)
显然把原树按照\(v\)分裂成\(\text{x}\)和\(\text{y}\)之后, 答案就是\(\text{x}\)的节点数.
别忘了最后\(\text{merge}\)回去.
split(rt, v - 1, x, y);
printf("%d\n", tr[x].sz + 1);
rt = merge(x, y);
4.\(k\text{th}\)(求第\(k\)大的数)
类似所有二叉平衡树的方法, 不多讲了( Q:为什么就这个操作写成一个函数呢? A:之后求前驱后继还需要用...
代码中\(\text{x}\)是这棵树的根.
inline int kth(int x, int k)
{
if(k > tr[x].sz) k = tr[x].sz;
while(1)
{
if(k <= tr[tr[x].ls].sz) x = tr[x].ls;
else if(k == tr[tr[x].ls].sz + 1) return x;
else k -= (tr[tr[x].ls].sz + 1), x = tr[x].rs;
}
}
主程序中:
printf("%d\n", tr[kth(rt, v)].val);
5.\(\text{precursor}\)(求一个数的前驱(小于\(v\)的最大数))
显然按照\(v-1\)分裂\(\text{x}\)和\(\text{y}\)后, 答案就是\(\text{x}\)中最大的那个数.
最大的那个怎么求呢? 那就是跟\(\text{x}\)的节点个数一样大呗..
split(rt, v - 1, x, y);
printf("%d\n", tr[kth(x, tr[x].sz)].val);
rt = merge(x, y);
6.\(\text{succesor}\)(求一个数的后继(大于\(v\)的最小数))
类比\(\text{precursor}\)的做法, 按照\(v\)分裂\(\text{x}\)和\(\text{y}\)后, 答案就是\(\text{y}\)中的最小值.
split(rt, v, x, y);
printf("%d\n", tr[kth(y, 1)].val);
rt = merge(x, y);
以下是洛谷P3369 【模板】普通平衡树的完整AC代码:
#include <ctime>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define N 100010
#define init(a, b) memset(a, b, sizeof(a))
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read() // notice : 1. long long ? 2. negative ?
{
int x = 0; char ch = getchar(); bool ne = 0;
while(ch < '0' || ch > '9') ne |= (ch == '-'), ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return ne ? -x : x;
}
struct Node
{
int sz, ls, rs, val, pri;
Node(){}
Node(int _v){sz = 1, ls = rs = 0, val = _v, pri = rand();}
}tr[N];
int n, rt, tot;
inline int new_Node(int v)
{
tr[++tot] = Node(v);
return tot;
}
inline void update(int x){tr[x].sz = tr[tr[x].ls].sz + tr[tr[x].rs].sz + 1;}
inline int merge(int x, int y)
{
if(!x || !y) return x + y;
int ret;
if(tr[x].pri < tr[y].pri) ret = x, tr[x].rs = merge(tr[x].rs, y);
else ret = y, tr[y].ls = merge(x, tr[y].ls);
update(ret);
return ret;
}
inline void split(int cur, int k, int &x, int &y)
{
if(!cur) x = y = 0;
else
{
if(tr[cur].val <= k) x = cur, split(tr[cur].rs, k, tr[cur].rs, y);
else y = cur, split(tr[cur].ls, k, x, tr[cur].ls);
update(cur);
}
}
inline int kth(int x, int k)
{
if(k > tr[x].sz) k = tr[x].sz;
while(1)
{
if(k <= tr[tr[x].ls].sz) x = tr[x].ls;
else if(k == tr[tr[x].ls].sz + 1) return x;
else k -= (tr[tr[x].ls].sz + 1), x = tr[x].rs;
}
}
int main()
{
freopen("treap.in", "r", stdin);
freopen("treap.out", "w", stdout);
srand(time(NULL));
for(int q = read(); q; --q)
{
int op = read(), v = read(), x, y, z;
if(op == 1) //insert
{
split(rt, v, x, y);
rt = merge(merge(x, new_Node(v)), y);
}
else if(op == 2) //delete
{
split(rt, v, x, z);
split(x, v - 1, x, y);
rt = merge(merge(x, merge(tr[y].ls, tr[y].rs)), z);
}
else if(op == 3) //rank
{
split(rt, v - 1, x, y);
printf("%d\n", tr[x].sz + 1);
rt = merge(x, y);
}
else if(op == 4) //kth
printf("%d\n", tr[kth(rt, v)].val);
else if(op == 5) //precursor
{
split(rt, v - 1, x, y);
printf("%d\n", tr[kth(x, tr[x].sz)].val);
rt = merge(x, y);
}
else //succesor
{
split(rt, v, x, y);
printf("%d\n", tr[kth(y, 1)].val);
rt = merge(x, y);
}
}
return 0;
}
后记
虽然本人怠惰, 但是Splay 不能不学, 这就去学Splay.
[数据结构]FHQ-Treap的更多相关文章
- 【数据结构】FHQ Treap详解
FHQ Treap是什么? FHQ Treap,又名无旋Treap,是一种不需要旋转的平衡树,是范浩强基于Treap发明的.FHQ Treap具有代码短,易理解,速度快的优点.(当然跟红黑树比一下就是 ...
- FHQ Treap小结(神级数据结构!)
首先说一下, 这个东西可以搞一切bst,treap,splay所能搞的东西 pre 今天心血来潮, 想搞一搞平衡树, 先百度了一下平衡树,发现正宗的平衡树写法应该是在二叉查找树的基础上加什么左左左右右 ...
- fhq treap——简单又好写的数据结构
今天上午学了一下fhq treap感觉真的很好用啊qwq 变量名解释: \(size[i]\)表示以该节点为根的子树大小 \(fix[i]\)表示随机权值 \(val[i]\)表示该节点的值 \(ch ...
- 【数据结构】平衡树splay和fhq—treap
1.BST二叉搜索树 顾名思义,它是一棵二叉树. 它满足一个性质:每一个节点的权值大于它的左儿子,小于它的右儿子. 当然不只上面那两种树的结构. 那么根据性质,可以得到该节点左子树里的所有值都比它小, ...
- 【POJ2761】【fhq treap】A Simple Problem with Integers
Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...
- 在平衡树的海洋中畅游(四)——FHQ Treap
Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...
- 可持久化treap(FHQ treap)
FHQ treap 的整理 treap = tree + heap,即同时满足二叉搜索树和堆的性质. 为了使树尽可能的保证两边的大小平衡,所以有一个key值,使他满足堆得性质,来维护树的平衡,key值 ...
- 并不对劲的fhq treap
听说很对劲的太刀流不止会splay一种平衡树,并不对劲的片手流为了反驳他,并与之针锋相对,决定学学高端操作. 很对劲的太刀流-> 据说splay常数极大,但是由于只知道splay一种平衡树能对序 ...
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
- 简析平衡树(四)——FHQ Treap
前言 好久没码过平衡树了! 这次在闪指导的指导下学会了\(FHQ\ Treap\),一方面是因为听说它可以可持久化,另一方面则是因为听说它是真的好写. 简介 \(FHQ\ Treap\),又称作非旋\ ...
随机推荐
- LINUX 系统性能检测工具vmstat
vmstat 有2个参数,第一个是采样时间间隔(单位是s),第二个参数是采样个数. #表示 2s采样一次,一共采样2次 vmstat 2 2 也可以只写第一个参数,让系统一直采样直到停止(ctrl + ...
- 深入理解java动态代理机制
动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会继承Proxy类,并实现所有你指定的接口(您在参数中传入的接口数组 ...
- 基于注解的方式搭建mybatis开发框架
1.创建工程 <groupId>com.hope</groupId> <artifactId>day01_eesy_01mybatis</artifa ...
- ssm+mysql+jsp打造在线考试系统WeKnow-学生端
一.登陆模块 前台提交账号和密码传到后台处理控制层 1.1 首先是控制器 @RequestMapping(value="/studentLogin", method=Request ...
- 【C/C++】n皇后问题/全排列/递归/回溯/算法笔记4.3
按常规,先说一下我自己的理解. 递归中的return常用来作为递归终止的条件,但是对于返回数值的情况,要搞明白它是怎么返回的.递归的方式就是自己调用自己,而在有返回值的函数中,上一层的函数还没执行完就 ...
- uniapp实现钉钉扫码登录
由于uniapp暂无钉钉授权登录所以本文将钉钉扫码登录作为网页嵌入uniapp,最终实现钉钉扫码登录app 1. 用H5调起钉钉扫码登录 钉钉在网页端的扫码登录可参考钉钉文档:扫码登录第三方网站 - ...
- CF721B Passwords 题解
Content 有一天,小 V 突然忘记了他在 Codehorses 的网站上的密码.但是他有所有网站上的 \(n\) 个密码 \(\{s_i\}_{i=1}^n\),所以他开始一个一个试.他会先从长 ...
- linux安装软件系列之npm安装
什么是rpm 百度说它是 Red-hat Package Manager (红帽包管理器) 其实它是:RPM Package Manager (RPM包管理器,来源于:https://rpm.org) ...
- 10分钟快速上车短视频风口:基于uniapp框架创建自己的仿抖音短视APP
在今年也就是第48次发布的<中国互联网络发展状况统计报告>有这样一个数据,21年的上半年以来,我国我国网民规模达10.11亿,其中短视频用户达8.88亿.碎片化的生活场景下,短视频成为人们 ...
- Spring工具类 非spring管理环境中获取bean及环境配置
SpringUtils.java import org.springframework.beans.BeansException; import org.springframework.beans.f ...