无旋平衡树 fhq treap

2023-10-21 12:10
文章标签 平衡 treap 无旋 fhq

本文主要是介绍无旋平衡树 fhq treap,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

普通平衡树

您需要写一种数据结构,来维护一些数,其中需要提供以下操作:

  1. 插入一个整数 x x x
  2. 删除一个整数 x x x (若有多个相同的数,只删除一个)
  3. 查询整数 x x x 的排名(排名定义为比当前数小的数的个数 + 1 +1 +1。若有多个相同的数,因输出最小的排名)
  4. 查询排名为 x x x 的数
  5. x x x 的前驱(前驱定义为小于 x x x,且最大的数)
  6. x x x的后继(后继定义为大于 x x x,且最小的数)

fhq treap 是二叉搜索树。若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。但这棵树可能会是一条链,这会让操作的复杂度卡到线性。

那么 fhq treap 通过给结点赋一个随机数,当作这个点的优先级,通过随机化让平衡树尽可能的平衡。那么,应该如何维护 fhq treap 呢?

存储

需要以下变量来存储:

struct fhqtreap{int ch[N][2] /*0 为左儿子 1 为右儿子*/ , val[N] /*结点的值*/ , key[N] /*结点的优先级*/, sz[N] /* 以该结点为根的子树的大小(包括它本身)*/, root /*树的根*/, cnt /*结点个数*/;
} T;

信息上传

在进行一些操作的时候,可能要将儿子的信息传到父亲身上。

inline void pushup(int rt) {sz[rt] = sz[ch[rt][0]] + sz[ch[rt][1]] + 1; //以左儿子为根的子树 + 右儿子为根的子树 + 它本身即为 1 
}

新建结点

新建一个结点。

inline int newnode(int v) { // v 为新建的结点的值sz[++cnt] = 1; val[cnt] = v; key[cnt] = rand()/*随机一个优先级*/;return cnt;
}

合并子树

要按照优先级来合并两个子树和二叉搜索树的性质左小右大,这里规定,如果子树 x x x 的优先级比 y y y 高( k e y x < k e y y key_x < key_y keyx<keyy),那么将子树 y y y 合并到子树 x x x 的右儿子上。否则,将子树 x x x 合并到子树 y y y 的左儿子上。合并之后,需要把改变后的信息上传到父结点。

int merge(int x, int y) {//将 x 和 y 合并返回合并后的子树的编号if (!x || !y) return x + y;//如果有任何一个子树为空,那么返回另一个(如果两个都是空返回 0)if (key[x] < key[y]) {ch[x][1] = merge(ch[x][1], y); //递归pushup(x); return x;} else {ch[y][0] = merge(x, ch[y][0]);pushup(y); return y;}}

分裂子树

有两个方法,一个是根据权值分,另一个是根据大小分。这道题最方便的方法是用权值分,大小分在文艺平衡树讲。将所有点权小于等于传入的参数的点分裂到左子树,其他的分裂到右子树。递归求解即可,最后将分裂后子树的信息上传。

void split(int rt, int v, int &x, int &y) {//将 rt 分成两棵子树 x 和 y,带 & 方便修改if (!rt) x = y = 0;else {if (val[rt] <= v) {x = rt; //分给右子树split(ch[rt][1], v, ch[rt][1], y);} else {y = rt; //分给左子树split(ch[rt][0], v, x, ch[rt][0]);}pushup(rt);//上传}}

插入结点

要将结点插入正确的位置。先把树分为 x , y x,y x,y 两部分,然后把新的结点看做是一棵树,先与 x x x 合并,合并完之后将合并的整体与 y y y 合并。

inline void insert(int v) { //新建权值为 v 的点int x, y; split(root, v, x, y); // x 中的点权小于等于 v,y 中的点权大于 vroot = merge(merge(x, newnode(v)), y); // 因为 x 的点权是 v,所以和 <= v 的子树 x 合并,再与 y 合并}

删除结点

首先把树分为 x x x z z z 两部分,设删除结点的权值为 a a a,再把 x x x 分为 x x x y y y 两部分,使得 x x x 中结点的权值全部小于 a a a y y y 中的全部大于 a a a。这就相当于传进的参数 v = a − 1 v = a - 1 v=a1。 而且呢,权值为 a a a 的结点正好是 y y y 树的根(左小右大根相等)。 然后可以无视 y y y 的根结点,直接把 y y y 的左右孩子合并起来,这样就成功的删除了根结点,最后再把 x , y , z x,y,z x,y,z 合并起来就好。

在这里插入图片描述

 inline void del(int v) { // 删除权值为 v 的点int x, y, z;split(root, v, x, z); split(x, v - 1, x, y);y = merge(ch[y][0], ch[y][1]); // 将左右儿子合并,忽视了根结点root = merge(merge(x, y), z);
}

求一个数的排名

考虑二叉查找树的性质,左儿子的值比父亲的小,右儿子的值比父亲大。那么,一个数的排名就是所有比它小的数加上他自己。

inline int lev(int v) {int x, y, res;split(root, v - 1, x, y);res = sz[x] + 1;root = merge(x, y); //分裂后别忘合并return res;}

求排名为任意值的数

从根结点出发,左子树中的数都比根结点小,右边的数都比根结点大。因为是从小到大排名,所以左子树的大小为它占的排名,而左子树的大小加一为当前结点排名,右子树的大小占的是倒数的排名。所以看看排名是不是在左子树的大小内,是的话向左子树走,不是的话再看看是不是正好排名相等,如果相等返回当前结点的值,不相等那么一定在右子树,那么向右子树走,向右子树走时要将左子树大小加根节点大小所占的排名减掉。

inline int kth(int rt, int v) {while (1) { // 用 while 循环if (v <= sz[ch[rt][0]]) rt = ch[rt][0];else if (v == sz[ch[rt][0]] + 1)return val[rt];else {v -= sz[ch[rt][0]] + 1;rt = ch[rt][1];}}}

求一个数的前驱

因为要小于 a a a ,那么按照 a − 1 a-1 a1 的权值分裂成 x x x y y y x x x 中最大的一定是 ≤ a − 1 \leq a - 1 a1的,所以直接输出 x x x 中最大的数即可。 x x x 中最大的数还是根据左小右大,那么是最后一个右儿子即为最后一个点。

inline int pre(int v) {int x, y, res;split(root, v - 1, x, y);res = kth(x, sz[x]);  // x 中最大的数root = merge(x, y); // 分裂后一定合并return res;}

求一个数的后继

与求前驱相似,只需找 ≥ a \ge a a 的最小的数就可以了。

inline int suf(int v) {int x, y, res;split(root, v, x, y);res = kth(y, 1);root = merge(x, y);return res;}

Link


文艺平衡树

您需要写一种数据结构,来维护一个有序排列,支持翻转区间的操作。

懒标记下传

直接更新所有的儿子往往会超时,所以要用懒标记,用一个数,这个数为 0 0 0 1 1 1,用来表示是否需要反转。

inl void pushdown(reg int rt) {if (tag[rt]) {swap(ch[rt][0], ch[rt][1]);if (ch[rt][0]) tag[ch[rt][0]] ^= 1;if (ch[rt][1]) tag[ch[rt][1]] ^= 1;tag[rt] = 0;}
}

分裂子树

设给定的大小为 v v v,那么把树分成大小为 v v v 与大小为 s z r t − v sz_{rt} - v szrtv 的两棵树。那么分的策略就是:先找左子树,左子树多了分给右子树,不够去和右子树要。

void split(reg int rt, reg int pos, reg int &x, reg int &y) {if (!rt) x = y = 0;else {pushdown(rt);if (pos <= sz[ch[rt][0]]) {y = rt;split(ch[rt][0], pos, x, ch[rt][0]);} else {x = rt;split(ch[rt][1], pos - sz[ch[rt][0]] - 1, ch[rt][1], y);}pushup(rt);}}

区间反转

将树的 l − 1 l - 1 l1 部分分给 x x x,那么 y y y 表示的区间为 [ y , n ] [y,n] [y,n]。再将 y y y 分裂出一个 z z z,长度为 r − l + 1 r - l + 1 rl+1

inl void rev(reg int l, reg int r) {reg int x, y, z;split(root, l - 1, x, y);split(y, r - l + 1, y, z);tag[y] ^= 1;root = merge(x, merge(y, z));
}

中序遍历

AVL 可以通过中序遍历得到反转后的序列。

void output(reg int rt) {if (!rt) return;if (tag[rt]) pushdown(rt);output(ch[rt][0]);printf("%d ", val[rt]);output(ch[rt][1]);
}

Link


可持久化文艺平衡树

您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):

  1. 在第 p p p 个数后插入数 x x x
  2. 删除第 p p p 个数。
  3. 翻转区间 [ l , r ] [l,r] [l,r],例如原序列是 { 5 , 4 , 3 , 2 , 1 } \{5,4,3,2,1\} {5,4,3,2,1},翻转区间 [ 2 , 4 ] [2,4] [2,4] 后,结果是 { 5 , 2 , 3 , 4 , 1 } \{5,2,3,4,1\} {5,2,3,4,1}
  4. 查询区间 [ l , r ] [l,r] [l,r] 中所有数的和。

和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 4 4 4 即保持原版本无变化),新版本即编号为此次操作的序号。

本题强制在线。

克隆结点

在每次合并与分裂的时候,需要新建一个结点修改,而不是在原来结点上修改。介绍一个小技巧,每次删除结点的时候,用一个队列记录这个点所占用的空间被 释放了,之后复制结点复制再这个位置上就可以了。

int clone(int y) {int x;if (!q.empty()) {x = q.front();q.pop();} else x = ++tot;t[x] = t[y];return x;
}

Link

这篇关于无旋平衡树 fhq treap的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/254285

相关文章

FHQ Treap模版(luogu P3369)

FHQ Treap模版(自用),带注释 #include<bits/stdc++.h>using namespace std;const int N=1e5+10;int n,root,idx;struct node{int l,r;int val,key,size;}tr[N];int getnew(int v){tr[++idx].val=v;//权值tr[idx].key=rand(

写给大数据开发:你真的“慢“了吗?揭秘技术与职场的平衡艺术

你是否曾经在深夜里,面对着一个棘手的数据处理问题,感到无比沮丧?或者在一次重要的项目汇报中,突然语塞,无法清晰地表达你的技术方案?作为一名大数据开发者,这些场景可能再熟悉不过。但别担心,因为你并不孤单。让我们一起探讨如何在这个瞬息万变的行业中,既磨练技术利刃,又培养职场软实力。 目录 技术与时间的赛跑1. 长远视角的重要性2. 复利效应在技能学习中的应用 跨界思维:数据结构教我们的职场智

代码随想录 -- 二叉树 -- 平衡二叉树

110. 平衡二叉树 - 力扣(LeetCode) 思路:仍然是递归调用 1. 定义一个递归函数 count 用来计算二叉树的层数 2. isBalanced 函数:如果传入根节点为空返回真;如果根节点 | 左子树的层数 - 右子树的层数 | 大于1,返回假;最后返回根节点左子树、右子树是否是平衡二叉树。 class Solution(object):def count(self,root

构建STM32智能平衡车项目:PID控制算法与蓝牙通信技术

一、项目概述 项目目标和用途 本项目旨在设计和实现一款基于STM32单片机的平衡车。平衡车是一种新型的个人交通工具,广泛应用于短途出行、休闲娱乐等场景。通过本项目,我们希望能够实现一款具备良好稳定性和操控性的平衡车,能够在不同的地形上自如行驶。 解决的问题和带来的价值 平衡车的核心问题在于如何保持其平衡。传统的平衡车往往依赖于复杂的控制算法和高精度的传感器。通过本项目,我们将利用STM32

二叉搜索树、平衡二叉树、B树、B+树、B*树

二叉查找树 二叉查找树,由于不平衡,如果连续插入的数据是有顺序的、会导致如下图B的所示,此时搜索会退化到O(N)   二叉查找树,也称二叉搜索树,或二叉排序树。其定义也比较简单,要么是一颗空树,要么就是具有如下性质的二叉树: (1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2) 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3) 任

【C++】【数据结构】一步一步写平衡二叉树[AVL]

转载:有修正,原作者存在一些错误,这里进行了更正。/* 平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体第一个引入平衡概念的二叉树。特点:对于每一个结点,它的左右子树的高度之差不能超过1,若插入或删除一个节点之后使得高度之差大于1,就要进行节点之间的旋转,将二叉树重新维持在一个平衡状态。这个方案很好的解决的了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度

数据结构(13)——平衡二叉树(红黑树)

欢迎来到博主的专栏——数据结构 博主ID:代码小号 文章目录 红黑树红黑树节点之间的关系红黑树的插入uncle节点为红色uncle节点是黑色或者没有uncle节点 红黑树 平衡二叉树最出名的除了AVL树之外就是红黑树(RBTree),所谓红黑树,即拥有以下特性的平衡二叉树 (1)每个节点要么是红色,要么是黑色 (2)根节点必须是黑色 (3)红色节点的子节点必须是黑色 (

C语言《智能自平衡小车,实现平衡功能的基础上,加入了超声波避障、超声波跟随、蓝牙遥控等功能》+源代码+文档说明

文章目录 源代码下载地址项目介绍项目功能 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 C语言《智能自平衡小车,实现平衡功能的基础上,加入了超声波避障、超声波跟随、蓝牙遥控等功能》+源代码+文档说明 项目功能 为了实现小车功能,小车硬件主要包括: 控制核心板带编码器的直流电机车架12V 1900mah锂电池 项目备注 1、该资源内项目代码都经过

数据结构-高层数据结构:映射/字典(Map)【有序字典:基于二分搜索树、基于平衡二叉树、基于红黑树、基于链表】【无序字典:基于哈希表】

Map.java package map;/*** 映射*/public interface Map<K,V> {/*** 添加元素** @param key* @param value* @return void*/void add(K key,V value);/*** 删除元素** @param key* @return V*/V remove(K key);/*** 查看是

数据结构-高层数据结构:集合(Set)【元素不重复】【基于二分搜索树(有序集合O(logn))、基于平衡二叉树(有序集合O(logn))、基于链表(无序集合O(n))、基于哈希表(无序集合O(n))】

Set.java package set;/*** 集合** @author ronglexie* @version 2018/8/18*/public interface Set<E> {/*** 向集合中添加元素** @param e* @return void* @author ronglexie* @version 2018/8/18*/void add(E e);/*** 删