前言(个人评价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. implicit declaration of function 'NSFileTypeForHFSTypeCode' is invalid in c99

    问题:implicit declaration of function 'NSFileTypeForHFSTypeCode' is invalid in c99 解决办法: 在出现该问题的函数前后加上 ...

  2. 【Python】【Module】hashlib

    用于加密相关的操作,代替了md5模块和sha模块,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法 import hashlib # ######## ...

  3. Linux(CentOS 7)使用gcc编译c,c++代码

    安装gcc: 1.使用 yum -list gcc* 查询 centos 官方gcc的所有包: 可安装的软件包 gcc.x86_64 gcc-c++.x86_64 gcc-gfortran.x86_6 ...

  4. VUE3 之 生命周期函数

    1. 概述 老话说的好:天生我材必有用,千金散尽还复来. 言归正传,今天我们来聊一下 VUE 的生命周期函数. 所谓生命周期函数,就是在某一条件下被自动触发的函数. 2. VUE3 生命周期函数介绍 ...

  5. SpringCloud技术涵盖简介

    SpringCloud是微服务架构的集大成者,云计算最佳业务实践. 我们平常使用的Spring和他们的关系,对Spring,springboot , SpringCloud 的 概念区分,上图: Sp ...

  6. 攻击科普:ARP攻击

    目录 一.介绍 二.解决办法 一.介绍 ARP攻击的局限性 ARP攻击仅能在以太网(局域网如:机房.内网.公司网络等)进行. 无法对外网(互联网.非本区域内的局域网)进行攻击. ARP攻击就是通过伪造 ...

  7. GIT最基本使用

    带'*':必须操作 不带'*':可能需要而且经常用的 常见步骤为下: *1.克隆项目:有两种不同类型的网址(https/ssh) git clone [url] *2.初始化本地仓库 git init ...

  8. Java应用日志如何与Jaeger的trace关联

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. mysql使用自定义序列实现row_number功能

    看了一些文章,终于知道该怎么在 mysql 里面实现 row_number() 排序 话不多说,show you the code: 第一步:建表: create table grades( `nam ...

  10. LuoguP7911 [CSP-J 2021] 网络连接 题解

    Content 题目过于难解释,请前往题面查看.以下直接给出本题做法. Solution 入门组 T3 在我印象中向来都不是很容易能做出来的题目,但是今年这个 T3 不得不说还是挺好做的. 我们先不妨 ...