前言(个人评价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. oracle 执行计划的获取方法

    1.用explain plan for来获取执行计划 explain plan for <sql>; select * from table(dbms_xplan.display()); ...

  2. vue-cli4脚手架搭建二

    vue-cli4脚手架搭建一 vue create vue3 cd vue3 yarn serve http://localhost:8080/#/ main.js文件 import Vue from ...

  3. sax方式解析XML学习笔记

    原理:对文档进行顺序扫描,当扫描到文档(document)开始与结束,元素开始与结束.文档结束等地方 通知事件处理函数,由事件处理函数相应动作然后继续同样的扫描,直至文档结束. 优点:消耗资源比较少: ...

  4. linux下把一个用户从某个组中删除,而不删除用户

    查看当前用户/登录用户 基本语法 whoami / who am I 用户组 介绍 类似于角色,系统可以对有共性的多个用户进行统一的管理. 新增组 语法 groupadd 组名 案例演示 添加test ...

  5. SpringBoot 项目不加载 application.properties 配置文件

    起因:新安装的idea第一次运行springboot项目报url错误(Failed to configure a DataSource: 'url' attribute is not specifie ...

  6. Next_day()函数的用法

    一.定义 NEXT_DAY(date,char)  date参数为日期型,  char:为1~7或Monday/Mon~Sunday/    指定时间的下一个星期几(由char指定)所在的日期,  c ...

  7. 3.Vue.js-目录结构

    Vue.js 目录结构 上一章节中我们使用了 npm 安装项目,我们在 IDE(Eclipse.Atom等) 中打开该目录,结构如下所示: 目录解析 目录/文件 说明 build 项目构建(webpa ...

  8. 【C/C++】二维数组的传参的方法/二维字符数组的声明,使用,输入,传参

    [问题] 定义了一个子函数,传参的内容是一个二维数组 编译提示错误 因为多维数组作为形参传入时,必须声明除第一位维外的确定值,否则系统无法编译(算不出偏移地址) [二维数组的传参] 方法一:形参为二维 ...

  9. 熔断和降级的初步详解实现(NET Core控制台输出讲解Polly)

    概述 很多朋友包括我,对于"八股文"可以说是比较熟练的,每次面试前都会专研不少东西,各种固定答案.专业术语都是张口就来,一个字,稳. 八股文:程序员八股文是指程序员在面试过程中经常 ...

  10. Python 中更安全的 eval

    问题 想要将一段列表形式的字符串转为 list,但是担心这个动态的字符串可能是恶意的代码?使用 eval 将带来安全隐患.比如: # 期望是 eval('[1, 2, 3]') # 实际上是 eval ...