前言(个人评价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的更多相关文章

  1. 【数据结构】FHQ Treap详解

    FHQ Treap是什么? FHQ Treap,又名无旋Treap,是一种不需要旋转的平衡树,是范浩强基于Treap发明的.FHQ Treap具有代码短,易理解,速度快的优点.(当然跟红黑树比一下就是 ...

  2. FHQ Treap小结(神级数据结构!)

    首先说一下, 这个东西可以搞一切bst,treap,splay所能搞的东西 pre 今天心血来潮, 想搞一搞平衡树, 先百度了一下平衡树,发现正宗的平衡树写法应该是在二叉查找树的基础上加什么左左左右右 ...

  3. fhq treap——简单又好写的数据结构

    今天上午学了一下fhq treap感觉真的很好用啊qwq 变量名解释: \(size[i]\)表示以该节点为根的子树大小 \(fix[i]\)表示随机权值 \(val[i]\)表示该节点的值 \(ch ...

  4. 【数据结构】平衡树splay和fhq—treap

    1.BST二叉搜索树 顾名思义,它是一棵二叉树. 它满足一个性质:每一个节点的权值大于它的左儿子,小于它的右儿子. 当然不只上面那两种树的结构. 那么根据性质,可以得到该节点左子树里的所有值都比它小, ...

  5. 【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 ...

  6. 在平衡树的海洋中畅游(四)——FHQ Treap

    Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...

  7. 可持久化treap(FHQ treap)

    FHQ treap 的整理 treap = tree + heap,即同时满足二叉搜索树和堆的性质. 为了使树尽可能的保证两边的大小平衡,所以有一个key值,使他满足堆得性质,来维护树的平衡,key值 ...

  8. 并不对劲的fhq treap

    听说很对劲的太刀流不止会splay一种平衡树,并不对劲的片手流为了反驳他,并与之针锋相对,决定学学高端操作. 很对劲的太刀流-> 据说splay常数极大,但是由于只知道splay一种平衡树能对序 ...

  9. FHQ treap学习(复习)笔记

    .....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...

  10. 简析平衡树(四)——FHQ Treap

    前言 好久没码过平衡树了! 这次在闪指导的指导下学会了\(FHQ\ Treap\),一方面是因为听说它可以可持久化,另一方面则是因为听说它是真的好写. 简介 \(FHQ\ Treap\),又称作非旋\ ...

随机推荐

  1. Windows下80端口被占用的解决方法(SQL Server)

    查找80端口被谁占用的方法 进入命令提示行(WIN+R 输入 CMD),输入命令 netstat -ano|findstr 80 (显示包含:80的网络连接) ,就可以看到本机所有端口的使用情况,一般 ...

  2. 【MySQL】统计累计求和

    https://geek-docs.com/sql/sql-examples/sql-cumulative-sum.html

  3. Mysql资料 xtrabackup

    目录 一.简介 原理 优缺点 二.安装 三.日常使用 备份所有库 增量备份 远程备份 四.参数 一.简介 原理 其实XtraBackup也是基于INNODB的 crash-recovery功能来实现的 ...

  4. pipeline 结构设计

    目录 一.pipeline步骤 二.案例 pipeline详解 只生成一次制品 不同环境部署 系统集成测试 指定版本部署 一.pipeline步骤 当团队开始设计第一个pipeline时,该如何下手呢 ...

  5. 搞IT的应届生如何写好简历?

    本人在互联网大厂和外企做过技术面试官,也有过校招和招聘应届毕业生的经验,所以自认为在这个问题上有一定的发言权.   应届毕业生(其实其他求职者也一样)首先要知道,面试官凭什么决定这份简历有面试机会?而 ...

  6. bcloud_bctf_2016

    目录 bcloud_bctf_2016 总结 题目分析 checksec 函数分析 main welcome get_name get_org_host menu new_note show_note ...

  7. bjdctf r2t3 onegadget

    没错,这就是一篇很水的随笔.... 两道很简单的题,先来看第一道.r2t3,保护检查了一下是只开启了堆栈不可执行. 简单看一下ida的伪代码. main函数让你输入一个name,然后会执行一个name ...

  8. qt5读取所有本机IP

    说明 需要添加 network模块 本文介绍的函数将读取所有本机IP,包括 ipv4和ipv6 本文演示版本 qt5.14 头文件 #include <QHostAddress> #inc ...

  9. AndroidStudio-快捷键

    Windows: Ctrl + Alt +L (Ctrl +Shift+F 无效) (亲测,和qq热键冲突,我的解决方式是把qq除捕获屏幕外的热键全部设置为无) Mac: OPTION + CMD + ...

  10. 1079 - Just another Robbery

    1079 - Just another Robbery   PDF (English) Statistics Forum Time Limit: 4 second(s) Memory Limit: 3 ...