ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

平衡树 - FHQ 学习笔记

2022-03-31 16:32:49  阅读:192  来源: 互联网

标签:val int tree 笔记 merge split 平衡 FHQ


平衡树 - FHQ 学习笔记

主要参考万万没想到 的 FHQ-Treap学习笔记

本片文章的姊妹篇:平衡树 - Splay 学习笔记

感觉完全不会平衡树,又重新学习了一遍 FHQ,一口气把常见套路都学完了。

一、大致内容及分类

FHQ,全称非旋转 Treap,是一种可以用于维护按权值、排名分裂的数据结构。它相比与 Splay 虽然常数较大,但是实现起来代码难度相对容易,而且由于它非旋的特点,也可以用来实现可持久化。

既然叫做非旋 Treap,它兼有 Treap 的特点又有非旋转独特的优势。

  • 从 Treap 角度看,他们同样都是依赖修正值 rnd 是随机的,用将他们按照 rnd 形成一个小根堆。与 Treap 相同,它也满足笛卡尔树的性质,它的中序遍历和它的插入顺序相同,即 \(1\) 到 \(n\) 的序列。
  • 从非旋角度看,FHQ 直接通过 splitmerge 操作实现添加、删除元素,不用再树上旋转了。

根据不同题目要求,将平衡树分为序列平衡树权值平衡树

  • 序列平衡树的中序遍历为每个元素的下标,权值为每个元素具体的值,常见题型为区间翻转等。
  • 权值平衡树的中序遍历是所有元素的排名,即按照中序遍历提取所有元素后元素权值递增,常见操作为全局第 \(k\) 大等。

如果毒瘤出题人同时综合了以上两种操作,即区间翻转 \(+\) 区间第 \(k\) 大,应该怎么做呢?好吧,如果真是这样,这篇文章可能不能够帮到你,用用树套树吧。

二、基本操作

FHQ 的核心操作就是 split 出操作区间,操作完后 merge 回去。

下边讲解中默认的平衡树类型为权值平衡树,序列平衡树其实是将某些 \(val\) 改为了 \(siz\)。

分裂 split

无论是按照排名还是权值分裂,他们都是将原树分为左右两半,可以利用中序遍历的性质进行分裂。

具体操作时,我们新建两个临时变量 \(x,y\) 分别表示分裂出来的左边、右边的那颗平衡树。

如果我们遇到一个应该属于 \(x\) 树的节点,就将这个点以及这个点的左子树加入 \(x\) 树中,并递归分裂右子树;如果遇到属于 \(y\) 的,就将这个点与它的右子树加入 \(y\) 树中,并递归分裂左子树。

可以写出伪代码如下:

void split(int p,int k,int &x,int &y) // 分裂出 (-infty,k],(k,+infty) 
{ 
	 if(!p) { x=y=0; return; }
	 pushdown(p);
	 if(tree[p].val<=k) x=p,split(tree[p].pr,k,tree[x].pr,y);
	 else y=p,split(tree[p].pl,k,x,tree[y].pl);
	 pushup(p);
}

合并 merge

由于这是 FHQ 的 merge,需要在合并时既保证小根堆性质又不破坏中序遍历的特点,对合并的两棵树有特殊的要求:左右区间不能够相交或者顺序颠倒!

所以我们在合并时必须按照顺序从左到右合并。

具体操作时,可以直接将 rnd 小的作为新树的根节点,如果这个根节点来自左子树就递归右子树,相反来自右子树就递归左子树(由于满足上面区间不相交也不颠倒的特点)。

写出伪代码:

int merge(int x,int y)
{
	 if(!x || !y) return x+y;
	 if(tree[x].rnd<tree[y].rnd)
	 	 pushdown(x),tree[x].pr=merge(tree[x].pr,y),pushup(x);
	 else
	 	 pushdown(y),tree[y].pl=merge(x,tree[y].pl),pushup(y);
}

新建节点 new

没什么可说的,就是给新节点附一个随机的 rnd

inline int New(int Val)
{
	 tree[++cnt].rnd=rand();
	 tree[cnt].val=Val;
	 tree[cnt].siz=1;
	 tree[cnt].pl=tree[cnt].pr=0;
	 return cnt;
}

插入 insert

直接分裂出两端区间,把新建的加点放到两棵树中间在合并即可。

inline void Insert(int val)
{
	 split(root,val,x,y);
	 root=merge(merge(x,New(val)),y);
}

删除 delete

FHQ 可以实现删除一个数或删除这个值的所有数,唯一区别就在于分裂时的不同。

inline void Delete_one(int val)
{
	 split(root,val,x,z);
	 split(x,val-1,x,y);
	 root=merge(x,z);
}
inline void Delete_all(int val)
{
	 split(root,val,x,z);
	 split(x,val-1,x,y);
	 y=merge(tree[y].pl,tree[y].pr);
	 root=merge(merge(x,y),z);
}

查询排名对应权值 Rank_to_Value

查询权值对应排名 Value_to_Rank

三、可持久化平衡树

四、常见优化技巧

五、模板

六、例题

标签:val,int,tree,笔记,merge,split,平衡,FHQ
来源: https://www.cnblogs.com/EricQian/p/16082023.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有