带旋treap概念及模板,带例题:普通平衡树

带旋Treap

  • 二叉查找树BST(Binary Search Tree)定义
  • Treap定义
  • 模板合集(均为O(logn)O(logn)O(logn)
    • push_up模板
    • 旋转模板
    • 插入模板
    • 删除模板
    • 查找前驱模板
    • 查找后驱模板
    • 查找键值key模板
    • 查找节点的修正值rank模板
  • PS:rd的比较问题
  • 例题:普通平衡树
    • 题目
    • 代码实现

真的太难了,刚开始是直接用模板不断A题,其实自己根本不是很理解,于是今天放弃了刷题时间,理解并背诵了这个模板,写了这篇blog
在这里插入图片描述

二叉查找树BST(Binary Search Tree)定义

在这里插入图片描述
二叉查找树(Binary Search Tree)是基于插入思想的一种在线的排序数据结构,它又叫二叉搜索树(Binary Search Tree)、二叉排序树(Binary Sort Tree),简称BST

这种数据结构的基本思想是在二叉树的基础上,规定一定的顺序,使数据可以有序地存储

二叉查找树运用了像二分查找一样的查找方式,并且基于链式结构存储,从而实现了高效的查找效率和完美的插入时间


二叉查找树(Binary Search Tree)或者是一棵空树,或者是具有下列性质的二叉
树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 它的左、右子树也分别为二叉查找树

我只是为了让大家理解下面Treap的定义罢了

Treap定义

Treap是一种平衡树。这个单词的构造选取了Tree(树)的前两个字符和Heap(堆)的后三个字符,
Treap = Tree + Heap。
顾名思义,Treap把BST和Heap结合了起来。
它和BST一样满足许多优美的性质,而引入堆目的就是为了维护平衡。


Treap在BST的基础上,添加了一个修正值。在满足BST性质的基础上,Treap节点的修正值还满足最小堆性质 。最小堆性质可以被描述为每个子树根节点都小于等于其子节点。


Treap的定义
Treap可以定义为有以下性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值,而且它
    的根节点的修正值小于等于左子树根节点的修正值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值,而且它
    的根节点的修正值小于等于右子树根节点的修正值;
  3. 它的左、右子树也分别为Treap。

修正值是节点在插入到Treap中时随机生成的一个值,它与节点的值无关。


Treap维护平衡的原理
我们发现,BST会遇到不平衡的原因是因为有序的数据会使查找的路径退化成链,
而随机的数据使BST退化的概率是非常小的

在Treap中,修正值的引入恰恰是使树的结构不仅仅取决于节点的值,还取决于修正值的值然而修正值的值是随机生成的,出现有序的随机序列是小概率事件,所以Treap的结构是趋向于随机平衡的


模板合集(均为O(logn)O(logn)O(logn)

先给明要用到的数组含义:
Size:表示节点数量也可作最后一个点编号
cnt[p]:表示编号为p,值为x在treap中插入的次数
key[p]:表示该点p的值为x
rd[p]:就是我们自己搞的修正值,用rand函数随机生成
siz[p]:编号为p的子树包括本身在内的节点数量即大小
son[p][2]:son[p][0]表示p的左儿子,son[p][1]表示p的右儿子


push_up模板

就是更新x的子树大小,一定要多次更新,因为后面的操作总是要用到
一句话:不用白不用,能多用就多用

void push_up ( int x ) {siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x];
}

旋转模板

为了使Treap中的节点同时满足BST性质和小根堆性质,不可避免地要对其结构进行调整,调整方式被称为旋转(Rotate)
在维护Treap的过程中,只有两种旋转 ,分别是左旋和右旋。旋转是相对于子树而言的,左旋和右旋的命名体现了旋转的一条性质1:
左旋一个子树,会把它的根节点旋转到根的左子树位置,同时根节点的右子节点成为子树的根;
右旋一个子树,会把它的根节点旋转到根的右子树位置,同时根节点的左子节点成为子树的根

如图:
在这里插入图片描述
性质2:旋转后的子树仍然满足BST性质


void rotate ( int &x, int d ) {//0左旋,1右旋 int p = son[x][!d];son[x][!d] = son[p][d];son[p][d] = x;push_up ( x );push_up ( p );x = p;
}

插入模板

在Treap中插入元素,与在BST中插入方法相似。首先找到合适的插入位置,然后建立新的节点,存储元素。但是要注意建立新的节点的过程中,会随机地生成一个修正值,这个值可能会破坏堆序,因此我们要根据需要进行恰当的旋转。具体方法如下:

  1. 从根节点开始插入;
  2. 如果要插入的值小于等于当前节点的值,在当前节点的左子树中插入,插入后
    如果左子节点的修正值小于当前节点的修正值,对当前节点进行右旋;
  3. 如果要插入的值大于当前节点的值,在当前节点的右子树中插入,插入后如果
    右子节点的修正值小于当前节点的修正值,对当前节点进行左旋;
  4. 如果当前节点为空节点,在此建立新的节点,该节点的值为要插入的值,左右
    子树为空,插入成功
    在这里插入图片描述
void insert ( int &rt, int x ) {if ( ! rt ) {rt = ++ Size;siz[rt] = cnt[rt] = 1;key[rt] = x;rd[rt] = rand();return;}if ( key[rt] == x ) {cnt[rt] ++;siz[rt] ++;return;}int d = x > key[rt];insert ( son[rt][d], x );if ( rd[rt] < rd[son[rt][d]] )//说明是新插入的点,往另外一边旋转 rotate ( rt, !d );push_up ( rt );
/*如果旋转了就白做了,因为旋转中就已经更新了,
但是如果没旋转,这个就有用了,因为插入了以前的size就是错的*/
}

删除模板

Treap的删除因为要维护堆序,比较好的方法是利用旋转的方法把要删除的结点旋转到叶结点位置,再做删除操作。

情况一,该节点为叶节点,则该节点是可以直接删除的节点。若该节点有非空子节点,用非空子节点代替该节点的,否则用空节点代替该节点,然后删除该节点。

情况二,该节点有两个非空子节点。我们的策略是通过旋转,使该节点变为可以直接删除的节点。如果该节点的左子节点的修正值小于右子节点的修正值,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续讨论;反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,继续讨论,直到变成可以直接删除的节点


例如:要删除节点6,找到节点6,发现6有两个儿子,但是右儿子的修正值小于左儿子,选择左旋
在这里插入图片描述
接着发现6只剩下一个儿子了,就直接右旋让左儿子代替它的位置随后删掉
在这里插入图片描述
在这里插入图片描述

void delet ( int &rt, int x ) {if ( ! rt )return;if ( x != key[rt] )delet ( son[rt][x > key[rt]], x );else {if ( ! son[rt][0] && ! son[rt][1] ) {cnt[rt] --;siz[rt] --;if ( ! cnt[rt] )rt = 0;}else if ( son[rt][0] && ! son[rt][1] ) {rotate ( rt, 1 );delet ( son[rt][1], x );}else if ( ! son[rt][0] && son[rt][1] ) {rotate ( rt, 0 );delet ( son[rt][0], x );}else {int d = rd[son[rt][0]] > rd[son[rt][1]];rotate ( rt, d );delet ( son[rt][d], x );}}push_up ( rt );
}

接下来就比较轻松了,好理解了
在这里插入图片描述

查找前驱模板

找前驱我们发现,其实就是一直往左儿子找,如果节点p与查找值x大小比较后要查找p的右儿子,这个右儿子就是x的一个前驱但不一定是最接近的,所以需要比较并继续往下找,掘地三尺

int pre ( int rt, int x ) {if ( ! rt )return -INF;if ( key[rt] >= x )return pre ( son[rt][0], x );elsereturn max ( key[rt], pre ( son[rt][1], x ) );
}

查找后驱模板

找后驱我们发现与前驱相似,其实就是一直往右儿子找,如果节点p与查找值x大小比较后要查找p的左儿子,这个左儿子就是x的一个前驱但不一定是最接近的,所以需要比较

int suf ( int rt, int x ) {if ( ! rt )return INF;if ( key[rt] <= x )return suf ( son[rt][1], x );elsereturn min ( key[rt], suf ( son[rt][0], x ) );
}

查找键值key模板

就是与siz子树个数比较,看属于哪个部分

int find ( int rt, int x ) {if ( ! rt )return 0;if ( siz[son[rt][0]] >= x )return find ( son[rt][0], x );else if ( siz[son[rt][0]] + cnt[rt] < x )return find ( son[rt][1], x - cnt[rt] - siz[son[rt][0]] );elsereturn key[rt];
}

查找节点的修正值rank模板

和find差不多的思想,应该很好理解的

int search_rank ( int rt, int x ) {if ( ! rt )return 0;if ( key[rt] == x )return siz[son[rt][0]] + 1;if ( key[rt] < x )return siz[son[rt][0]] + cnt[rt] + search_rank ( son[rt][1], x );return search_rank ( son[rt][0], x );
}

PS:rd的比较问题

认真的同学会发现,我在写讲解博客的时候是维护的rd从小到大,而代码却是写的从大到小,其实这rd本来就是我们自己强加的修正值,所以本质是没有太大的区别的,只需要注意对应insert和delet维护好自己锁定的rd顺序就行了
在这里插入图片描述
1:

insert:if ( rd[p] < rd[son[p][d]] )
delet:int d = rd[son[p][0]] > rd[son[p][1]];

2:

insert:if ( rd[p] > rd[son[p][d]] )
delet:int d = rd[son[p][0]] < rd[son[p][1]];

例题:普通平衡树

题目

点击查看题目

代码实现

既然是入门模板题,自然是把所有的模板拼接上再加上输入输出即可AC,不再多说!!

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define MAXN 100005
#define INF 0x7f7f7f7f
int n, Size;
int siz[MAXN], cnt[MAXN], key[MAXN], rd[MAXN];
int son[MAXN][2];void push_up ( int x ) {siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x];
}void rotate ( int &x, int d ) {int p = son[x][!d];son[x][!d] = son[p][d];son[p][d] = x;push_up ( x );push_up ( p );x = p;
}void insert ( int &rt, int x ) {if ( ! rt ) {rt = ++ Size;siz[rt] = cnt[rt] = 1;key[rt] = x;rd[rt] = rand();return;}if ( key[rt] == x ) {cnt[rt] ++;siz[rt] ++;return;}int d = x > key[rt];insert ( son[rt][d], x );if ( rd[rt] < rd[son[rt][d]] )rotate ( rt, !d );push_up ( rt );
}void delet ( int &rt, int x ) {if ( ! rt )return;if ( x != key[rt] )delet ( son[rt][x > key[rt]], x );else {if ( ! son[rt][0] && ! son[rt][1] ) {cnt[rt] --;siz[rt] --;if ( ! cnt[rt] )rt = 0;}else if ( son[rt][0] && ! son[rt][1] ) {rotate ( rt, 1 );delet ( son[rt][1], x );}else if ( ! son[rt][0] && son[rt][1] ) {rotate ( rt, 0 );delet ( son[rt][0], x );}else {int d = rd[son[rt][0]] > rd[son[rt][1]];rotate ( rt, d );delet ( son[rt][d], x );}}push_up ( rt );
}int search_rank ( int rt, int x ) {if ( ! rt )return 0;if ( key[rt] == x )return siz[son[rt][0]] + 1;if ( key[rt] < x )return siz[son[rt][0]] + cnt[rt] + search_rank ( son[rt][1], x );return search_rank ( son[rt][0], x );
}int find ( int rt, int x ) {if ( ! rt )return 0;if ( siz[son[rt][0]] >= x )return find ( son[rt][0], x );else if ( siz[son[rt][0]] + cnt[rt] < x )return find ( son[rt][1], x - cnt[rt] - siz[son[rt][0]] );elsereturn key[rt];
}int pre ( int rt, int x ) {if ( ! rt )return -INF;if ( key[rt] >= x )return pre ( son[rt][0], x );elsereturn max ( key[rt], pre ( son[rt][1], x ) );
}int suf ( int rt, int x ) {if ( ! rt )return INF;if ( key[rt] <= x )return suf ( son[rt][1], x );elsereturn min ( key[rt], suf ( son[rt][0], x ) );
}int main() {scanf ( "%d", &n );int root = 0;while ( n -- ) {int opt, x;scanf ( "%d %d", &opt, &x );switch ( opt ) {case 1 : insert ( root, x );break;case 2 : delet ( root, x );break;case 3 : printf ( "%d\n", search_rank ( root, x ) );break;case 4 : printf ( "%d\n", find ( root, x ) );break;case 5 : printf ( "%d\n", pre ( root, x ) );break;case 6 : printf ( "%d\n", suf ( root, x ) );break;}}return 0;
} 

如果有任何问题欢迎提出,bye,让我们与非旋treap不见不散

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318132.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务系列实践 .NET CORE

从事这个行业转眼已经6年了&#xff0c;从当初刚毕业的在北京朝八晚十&#xff0c;从二环到五环&#xff0c;仍每天精力充沛的小愤青&#xff1b;再到深圳一点一滴的辛勤在软件行业的耕种&#xff0c;从当初单体应用架构到现在微服务架构的经历&#xff0c;回想起来自己的收获倒…

P2607 [ZJOI2008]骑士

P2607 [ZJOI2008]骑士 题意&#xff1a; n个点n个边&#xff0c;每个点都有权值&#xff0c;相邻的点不能同时选择&#xff0c;问如何选择能使得权值最大 题解&#xff1a; 这个题很有P1352 没有上司的舞会这个题的感觉&#xff0c;唯一的区别是那个题保证是树&#xff0c;…

模板:线段树优化建图

前言 百川到海&#xff0c;天下归一 解析 线段树优化建图是用于对一个区间的点连边时的优化方法 建一棵in树一棵出树分别往上和下指即可 大概长这样 &#xff08;pia的洛谷的照片&#xff09; 建树 正常动态开点即可 void build(int &k,int l,int r){tr[ktot](tree){0…

[非旋平衡树]fhq_treap概念及模板,例题:普通平衡树,文艺线段树

文章目录概念全套模板push_up模板split拆树模板(按权值拆)split拆树模板(按个数拆)merge合并模板&#xff08;地址版&#xff09;merge合并模板&#xff08;带返回根&#xff09;区间模板insert插入模板delete删除模板find_kth找第k大模板get_rank找排名模板pre找前驱模板suf找…

surging 微服务引擎 1.0 正式发布

surging 是一个分布式微服务引擎,提供高性能RPC远程服务调用&#xff0c;服务引擎支持http、TCP、WS、Mqtt协议,采用Zookeeper、Consul作为surging服务的注册中心&#xff0c;集成了哈希一致性&#xff0c;随机&#xff0c;轮询、压力最小优先作为负载均衡的算法&#xff0c;底…

YBTOJ:彩色圆环

文章目录前言题目描述InputOutputSample InputSample Output解析代码前言 尽信书&#xff0c;则不如无书 题目描述 Input 仅有一行&#xff0c;该行给出依次两个正整数N, M&#xff0c;分别表示宝石的个数和宝石在变化时可能变成的颜色种类数。 Output 应仅有一行&#xff0…

【2019CSP-J 普及组题解】数字游戏(number),公交换乘(transfer),纪念品(souvenir),加工领奖(work) CSP普及游记

文章目录T1&#xff1a;数字游戏题目CODET2&#xff1a;公交换乘题目CODET3&#xff1a;纪念品题目题解CODET4&#xff1a;加工领奖题目题解CODE关于普及组的想法&游记T1&#xff1a;数字游戏 题目 小 K 同学向小 P 同学发送了一个长度为 8 的 01 字符串来玩数字游戏&…

搭建基于Docker社区版的Kubernetes本地集群

Kubernetes的本地集群搭建是一件颇费苦心的活&#xff0c;网上有各种参考资源&#xff0c;由于版本和容器的不断发展&#xff0c;搭建的方式也是各不相同&#xff0c;这里基于Docker CE的18.09.0版本&#xff0c;在Mac OS、Win10下分别搭建了一次。一、Mac OS下搭建安装Docker …

Infinite Tree

Infinite Tree 题意&#xff1a; 题解&#xff1a; 参考博客 看了好一阵子才明白。。。emm。 我们先按照题意画出一部分树 我们先不考虑复杂度&#xff0c;这题应该怎么做&#xff1f; 题目给了每个点的权值w[i]&#xff0c;问一个点到所有的节点路径长度*点权之和最小是多少…

IdentityServer4-从数据库获取User登录并对Claims授权验证(五)

本节将在第四节基础上介绍如何实现IdentityServer4从数据库获取User进行验证&#xff0c;并对Claim进行权限设置。一、新建Web API资源服务&#xff0c;命名为ResourceAPI&#xff08;1&#xff09;新建API项目&#xff0c;用来进行user的身份验证服务。&#xff08;2&#xff…

周末狂欢赛1(玩游戏/Game,函数,JOIOI王国)

狂欢1T1&#xff1a;玩游戏 / Game题目题解代码实现T2&#xff1a;函数题目题解代码实现T3&#xff1a;JOIOI王国题目题解代码实现T1&#xff1a;玩游戏 / Game 题目 ljcc 和他的学妹在玩游戏&#xff0c;这个游戏共有 n 轮&#xff0c;在第 i 轮获胜会获得 i 分&#xff0c;…

用ABP只要加人即可马上加快项目进展(二) - 分工篇 - BDD实战篇 - .NET Core里跑Specflow...

这是<如何用ABP框架快速完成项目 >系列中的一篇文章。BDD很赞&#xff01;比TDD先进很多&#xff0c;能够大大提高编码效率。上一篇文章说了如何在.NET Core里安装Specflow. 然而文章成果只到了hello world级别。要想真的和实际业务结合&#xff0c;比如要能够IOC new cl…

【做题记录】CodeForces 做题记录

链接放的是洛谷上的链接&#xff0c;难度就是 CF 上的评分。 <details><summary>$\texttt{solution}$</summary></details> CF10D LCIS 难度&#xff1a;\(\tt{2800}\) 求两个串的最长公共上升子序列。\(n\le 500\) $\texttt{solution}$ 严重虚高题&am…

周末狂欢赛2(冒泡排序,概率充电器,不勤劳的图书管理员)

狂欢2T1&#xff1a;冒泡排序题目题解CODET2&#xff1a;概率充电器题目题解CODET3&#xff1a;不勤劳的图书管理员题目题解CODE我不这么认为。。。。 T1&#xff1a;冒泡排序 题目 下面是一段实现冒泡排序算法的 C代码&#xff1a; for(int i1; i<n; i)for(int j1; j&l…

P5659-[CSP-S2019]树上的数【贪心】

正题 题目链接:https://www.luogu.com.cn/problem/P5659 题目大意 给出nnn个点的一棵树&#xff0c;每个节点上有一个数字&#xff0c;你每次可以选择一条边删除然后交换连接的两个点的数字&#xff0c;在删完所有数字后设pip_ipi​表示数字iii所在节点编号&#xff0c;要求使…

YBTOJ洛谷P3195:玩具装箱(斜率优化dp)

传送门 文章目录前言解析代码前言 斜率优化dp&#xff0c;就是利用斜率优化的dp &#xff08;逃&#xff09; 解析 第一道斜优的题 分析题目 设sumisum_isumi​为1-i的c的前缀和 容易写出dp转移式&#xff1a; dpimin(dpj(sumi−sumji−j−1−L)2)dp_imin(dp_j(sum_i-sum_ji-…

01.微服务系列介绍

微服务系列实践 .NET CORE在开始之前呢&#xff0c;还是得废话一下&#xff0c;毕竟还是需要介绍一下这个系列我们要实现什么样的一套服务架构&#xff0c;也让大家能初步的有一个了解&#xff0c;后续实践起来也有一个完整的概念&#xff0c;相对也会容易的多。互联网架构演变…

Walker

Walker 题意&#xff1a; 一个区间[0,n]&#xff0c;区间上有两个点&#xff0c;坐标分别是pos1&#xff0c;pos2&#xff0c;速度分别是v1&#xff0c;v2&#xff0c;这两个点是在移动&#xff0c;可以随时改变移动方向&#xff0c;问当区间的每一块均被一个点或两个点移动覆…

【网络流】最大流问题(EK算法带模板,Dinic算法带模板及弧优化,ISAP算法带模板及弧优化)上下界网络流

本blog重点是代码网络流的相关概念流网络(flow network)流(flow)网络的流残留网络(residual network)增广路径(augmenting path)Edmonds-Karp算法思想bfs模板调用EK&更新残留网络流模板luogu的AC代码(EK版)Dinic算法思路时间复杂度证明bfs模板模板1模板2dfs模板不带弧优化模…

Rainbond 5.0正式发布, 支持对接管理已有Kubernetes集群

今天很高兴的向大家宣布Rainbond v5.0正式发布&#xff0c;Rainbond是开源的企业应用云操作系统&#xff0c;支撑企业应用的开发、架构、交付和运维的全流程&#xff0c;通过无侵入架构&#xff0c;无缝衔接各类企业应用&#xff0c;底层资源可以对接和管理IaaS、虚拟机和物理服…