一.Treap的性质

Treap就是集BST、Heap二者的性质于一身,即能够支持BST的操作,有能够保证Heap的深度。但BST和Heap的性质似乎有些矛盾,前者是左子树<根<右子树,后者是根<左儿子<右儿子(小顶堆)。

其实Treap的本质还是BST,对于任意节点,保证根左侧子树的所有节点比根小,右侧的所有节点比根大的树(没有相同节点),Treap只是利用堆的性质,赋予每一个节点一个随机值,按照随机值维护堆的形状,即在插入的情况中,如果树A的根节点的权重比树B的大,那树B就一定是树A的子树,再根据树B的根节点的值的大小确定是在树A的左子树还是右子树。

二.节点的结构

左右子树、值、权重、树的大小

三.核心操作一:分裂split

分为两种形式:按值分裂和按大小分裂
按值分裂:将树拆成两棵树,拆出来的一棵树的值全部小于等于给定的值,另一棵树的值全部大于给定的值;
按大小分裂:将树拆成两棵树,拆出来的一棵树的节点数量等于给定的大小,另一棵树就包括剩余部分;
对应到具体实现上就是用两个节点变量接收分裂后的两个根节点就行
一般来说,在使用fhq Treap当平衡树用的时候就是按值分裂,而在维护区间信息的时候就是按大小分裂(例如文艺平衡树)

四.核心操作二:合并merge

合并就是将两个树合并成一棵树,其中x树上的全部值小于等于y树上的所有值,并且合并出来的树依然满足Treap的性质,具体逻辑可见下面的代码,文字不想写了
基于这两种操作就可以有插入、删除、查询一个数在树对应的有升序序列中的排名、依据排名获取相应的节点以及获取前驱后继的函数

五.一般Treap(按值分裂)

import java.security.SecureRandom;
import java.util.ArrayList;public class FHQTreap<T extends Comparable<T>> {private TreapNode<T> root;//当前的根节点private SecureRandom random = new SecureRandom();//用法:random.nextInt(range+1)——生成处以0-range的随机数private static final int range = 100;//后续的权值就设置为0-100内的随机数吧public FHQTreap() {root = null;}public void insert(T val) {//1.从根节点开始,按照val进行值分裂,得到part1是值小于等于val的全部节点组成的树,part2是值大于val的ArrayList<TreapNode<T>> res = splitByVal(root, val);//2.合并root = merge(merge(res.get(0), new TreapNode<>(val)), res.get(1));}public void remove(T val) {//1.从根节点开,按照val值进行值分裂,得到res[0]是值小于等于val的全部节点组成的树,res[1]是值大于val的ArrayList<TreapNode<T>> res = splitByVal(root, val);//2.再对res[0]按照val-1进行值分裂,得到res2[0]是值小于val的全部节点组成的树,res2[1]是值等于val的ArrayList<TreapNode<T>> res2 = splitByVal2(res.get(0), val);//3.删除res2[1]的根节点,即将res2[1]的左右子树合并得到新节点给到part3TreapNode<T> res21 = res2.get(1);TreapNode<T> tmpNode = merge(res21.left, res21.right);//4.合并,更新rootroot = merge(merge(res2.get(0), tmpNode), res.get(1));}private ArrayList<TreapNode<T>> splitByVal(TreapNode<T> node, T val) {//返回的数组中,第一个表示小于等于val的节点,第二个是大于val的ArrayList<TreapNode<T>> res = new ArrayList<>();if(node == null) {res.add(null);res.add(null);} else {int cmp = node.value.compareTo(val);if(cmp <= 0) {//提醒:本质上还是BST,即当前节点的左子树的值小于当前节点的值,右子树的值大于等于当前节点的值//当前节点的值小于传入的值,而res[0]是小于等于当前节点的全部节点的树,则当前节点以及其左子树都是属于res[0]的//那此时就是要向右子节点走下去了,res[0]就可以先确定为node了,然后往右子节点递归判断得到的属于res[0]的节点就//放到res[0]的右子树上ArrayList<TreapNode<T>> tmp1 = splitByVal(node.right, val);node.right = tmp1.get(0);res.add(node);res.add(tmp1.get(1));} else {ArrayList<TreapNode<T>> tmp2 = splitByVal(node.left, val);node.left = tmp2.get(1);res.add(tmp2.get(0));res.add(node);}int leftSize = node.left != null ? node.left.size : 0;int rightSize = node.right != null ? node.right.size : 0;node.size = leftSize + rightSize + 1;}return res;}private ArrayList<TreapNode<T>> splitByVal2(TreapNode<T> node, T val) {//返回的数组中,第一个表示小于val的节点,第二个是大于等于val的//这个函数的主要是为了解决泛型的val不能减一的问题,就是后面那个删除那里的,本来是从小于等于val的// 树分裂出小于等于val-1和等于val的,但是泛型减不了,只能从这里的比较入手了,原理和上面那个几乎同名的函数是一样的ArrayList<TreapNode<T>> res = new ArrayList<>();if(node == null) {res.add(null);res.add(null);} else {int cmp = node.value.compareTo(val);if(cmp < 0) {ArrayList<TreapNode<T>> tmp1 = splitByVal2(node.right, val);node.right = tmp1.get(0);res.add(node);res.add(tmp1.get(1));} else {ArrayList<TreapNode<T>> tmp2 = splitByVal2(node.left, val);node.left = tmp2.get(1);res.add(tmp2.get(0));res.add(node);}int leftSize = node.left != null ? node.left.size : 0;int rightSize = node.right != null ? node.right.size : 0;node.size = leftSize + rightSize + 1;}return res;}private TreapNode<T> merge(TreapNode<T> node1, TreapNode<T> node2) {//权值按照大顶堆if(node1 == null) return node2;else if (node2 == null) return node1;else {//这里要确保node1的值小于node2if (node1.value.compareTo(node2.value) >= 0) {TreapNode<T> tmp = node1;node1 = node2;node2 = tmp;}if (node1.weight > node2.weight) {//node1的权重大于node2,那node2会成为node1的子节点,又node1的值小于node2的值,那node2就在node1的右子node1.right = merge(node1.right, node2);int leftSize = node1.left != null ? node1.left.size : 0;int rightSize = node1.right != null ? node1.right.size : 0;node1.size = leftSize + rightSize + 1;return node1;} else {//node1的权重小于等于node2,那node1会成为node2的子节点,又node1的值小于node2的值,那node1就在node2的左子node2.left = merge(node2.left, node1);int leftSize = node2.left != null ? node2.left.size : 0;int rightSize = node2.right != null ? node2.right.size : 0;node2.size = leftSize + rightSize + 1;return node2;}}}public int getRank(T val) {ArrayList<TreapNode<T>> tmp = splitByVal(root, val);TreapNode<T> tmpL = tmp.get(0);while (tmpL.right != null) tmpL = tmpL.right;//一直找到最右if(tmpL.value.equals(val)) return tmp.get(0).size;else return tmp.get(0).size + 1;}public String getNodeByRank(int rank) {//根据排名获取节点,只在删除标记为true的数找,不考虑那些打上删除标记的数TreapNode<T> tmp = root;while (tmp != null) {//如果当前节点左子的存在节点和当前节点(如果存在)的存在节点刚好等于排名,那当前节点就是待查询的节点int leftRealSize = tmp.left == null ? 0 : tmp.left.size;if(leftRealSize + 1 == rank) break;else if(leftRealSize >= rank) {//如果当前节点的左子节点的存在节点大于等于排名,那待查询节点一定在左子树上tmp = tmp.left;} else {//如果当前节点+左子树的存在节点的全部存在节点都比排名小,那就要到右子树去找//这个时候就要查找以右子节点为根的树,排名要减去tmp节点和tmp左子树的存活节点rank -= leftRealSize + 1;tmp = tmp.right;}}if(tmp == null) return "NoExist!";return tmp.toString();}private void get_Pre(TreapNode<T> node) {if(node == null) return;;System.out.print("v:" + node.value+ " size:" + node.size + " w:" + node.weight);System.out.println(" left:" + (node.left == null ? "无" : node.left.value)+ " right:" + (node.right == null ? "无" : node.right.value));get_Pre(node.left);get_Pre(node.right);}public void getPre() {get_Pre(root);}public class TreapNode<T extends Comparable<T>> {T value;//节点所存储的值int weight;//节点的权值,到插入的时候再分配TreapNode<T> left;//左子节点TreapNode<T> right;//右子节点int size;//树的大小public TreapNode(T value) {this.value = value;this.size = 1;this.weight = random.nextInt(range + 1);//给新节点生成一个随机权值}@Overridepublic String toString() {return "TreapNode{" +"value=" + value +", weight=" + weight +", left=" + left +", right=" + right +", size=" + size +'}';}}
}
public class test {public static void main(String[] args) {FHQTreap<Integer> fhq = new FHQTreap<>();//1.insert test
//        fhq.insert(1);
//        fhq.insert(3);
//        fhq.insert(5);
//        fhq.insert(2);
//        fhq.insert(4);
//        fhq.getPre();//2.remove test
//        fhq.remove(3);
//        System.out.println("删除3:");
//        fhq.getPre();
//        fhq.remove(2);
//        System.out.println("删除2:");
//        fhq.getPre();//3.getRank test
//        int SearchNum = 5;
//        int rank = fhq.getRank(SearchNum);
//        System.out.println(SearchNum + "的排名:" + rank);//4.getNodeByRank test
//        System.out.println(fhq.getNodeByRank(1));
//        System.out.println(fhq.getNodeByRank(2));
//        System.out.println(fhq.getNodeByRank(3));
//        System.out.println(fhq.getNodeByRank(4));}
}

六.文艺平衡树(按大小分裂)

维护区间[l,r]的具体步骤:
1.把Treap 按大小 l-1 拆成x和y两棵树;
2.再将y按 r-l+1 拆成y和z两棵树;
3.此时y树就是需要操作的区间,处理完后将上面三者合并回去就行

怎么解决文艺平衡树的问题呢?
需要用到线段树常用的应该概念:懒标记,每当一个区间需要翻转时,就对这个区间打上懒标记(如果已有懒标记就去掉懒标记,因为翻转两次相当于没翻转),在具体实现时,因为一个节点存储的是一个值,那个节点被打上懒标记的话,就意味着以这个节点为根的树的中序遍历的序列是需要翻转的,但是真正的翻转操作是要等到这个节点的子节点有可能被修改,或者需要输出整棵树对应的中序遍历时,再真正进行翻转(真正翻转的函数就是下传函数);
懒标记的下传操作——如果当前节点的左右子树在后续代码中有被更改风险时就要下传,就是对当前节点的左右子树进行交换,然后将翻转标记传给子树,当前节点的翻转标记就可以去除.
下传的意思应该是除了叶子节点不需要下传翻转标记的情况外,其他的节点都是只在当前节点完成左右子树交换,然后子树修改翻转标记的状态(修改的方式就是待翻转变不用翻转,不翻转变待翻转,即如果子树的翻转标记置为true,子树也不需要现在就执行真正的翻转,它要想真正翻转也是和上面的逻辑一样),并没有每一次都将树转换到理论上翻转后序列所对应的树。

import java.security.SecureRandom;
import java.util.ArrayList;public class WenYiTree<T extends Comparable<T>> {private TreapNode<T> root;//当前的根节点private SecureRandom random = new SecureRandom();//用法:random.nextInt(range+1)——生成处以0-range的随机数private static final int range = 100;//后续的权值就设置为0-100内的随机数吧public WenYiTree() {root = null;}public void reverse(int L, int R) {if (R == L) {return;}L = Math.max(Math.min(L, R), 1);R = Math.min(Math.max(L, R), root.size);System.out.println("翻转的区间为[" + L + "," + R + "]");//翻转函数ArrayList<TreapNode<T>> res1 = splitBySize(root, L - 1);ArrayList<TreapNode<T>> res2 = splitBySize(res1.get(1), R - L + 1);if (res2.get(0) != null) res2.get(0).lazyReverse = !res2.get(0).lazyReverse;root = merge(merge(res1.get(0), res2.get(0)), res2.get(1));}private TreapNode<T> pushDown(TreapNode<T> node) {if (node == null) return null;else if (node.left == null && node.right == null) {node.lazyReverse = false;return node;}//下传操作,我的理解是就是将当前节点所对应的区间真正进行翻转操作,然后将需要翻转一次的标记传给子节点的翻转标记//我觉得就是当要求在某一个区间进行翻转操作时,就是在那个区间对应的节点打上懒标记,而其子节点是没有懒标记的,除非等到//父节点有懒标记然后执行真正的翻转操作后,其再将懒标记传给子节点//这里就是执行真正的翻转操作,首先交换两个子树TreapNode<T> tmp = node.left;node.left = node.right;node.right = tmp;//将当前节点的懒标记往下传,即相当于对两边的子树对应的区间进行一次翻转if (node.left != null) node.left.lazyReverse = !node.left.lazyReverse;if (node.right != null) node.right.lazyReverse = !node.right.lazyReverse;node.lazyReverse = false;//当前节点对应的区间完成翻转,去掉当前节点的懒标记return node;}private ArrayList<TreapNode<T>> splitBySize(TreapNode<T> node, int size) {//返回的数组中,第一个表示等于val的节点,第二个是剩余部分的树ArrayList<TreapNode<T>> res = new ArrayList<>();if (node == null) {res.add(null);res.add(null);} else if (size >= root.size) {res.add(node);res.add(null);} else if (size <= 0) {res.add(null);res.add(node);} else {if (node.lazyReverse) node = pushDown(node);//如果当前节点有懒标记,就先翻转一下int leftSize = node.left != null ? node.left.size : 0;if (leftSize < size) {//如果左子树大小 < 输入的大小,说明当前的node肯定属于等于size大小部分的树,那node就作为最高的节点//然后剩余的数量size-leftsize-1部分就到node的右节点去找ArrayList<TreapNode<T>> tmp1 = splitBySize(node.right, size - leftSize - 1);node.right = tmp1.get(0);res.add(node);res.add(tmp1.get(1));} else {//说明左子树大小 大于等于 输入的大小,那当前节点就应该属于剩余部分的树ArrayList<TreapNode<T>> tmp2 = splitBySize(node.left, size);node.left = tmp2.get(1);res.add(tmp2.get(0));res.add(node);}leftSize = node.left != null ? node.left.size : 0;int rightSize = node.right != null ? node.right.size : 0;node.size = leftSize + rightSize + 1;}return res;}private TreapNode<T> merge(TreapNode<T> node1, TreapNode<T> node2) {//权值按照大顶堆if (node1 == null) return node2;else if (node2 == null) return node1;else {//这里要确保node1的值小于node2if (node1.value.compareTo(node2.value) >= 0) {TreapNode<T> tmp = node1;node1 = node2;node2 = tmp;}if (node1.weight > node2.weight) {//node1的权重大于node2,那node2会成为node1的子节点,又node1的值小于node2的值,那node2就在node1的右子//只要节点涉及到修改子节点,就要先进行一次下传if (node1.lazyReverse) {node1 = pushDown(node1);}node1.right = merge(node1.right, node2);int leftSize = node1.left != null ? node1.left.size : 0;int rightSize = node1.right != null ? node1.right.size : 0;node1.size = leftSize + rightSize + 1;return node1;} else {//node1的权重小于等于node2,那node1会成为node2的子节点,又node1的值小于node2的值,那node1就在node2的左子//只要节点涉及到修改子节点,就要先进行一次下传if (node2.lazyReverse) node2 = pushDown(node2);node2.left = merge(node2.left, node1);int leftSize = node2.left != null ? node2.left.size : 0;int rightSize = node2.right != null ? node2.right.size : 0;node2.size = leftSize + rightSize + 1;return node2;}}}public void merge(T val) {root = merge(root, new TreapNode<>(val));}private void get_In(TreapNode<T> node) {if (node == null) return;if (node.lazyReverse) node = pushDown(node);//输出的时候肯定就要完成全部翻转了get_In(node.left);System.out.print(node.value + " ");get_In(node.right);}public void getIn() {get_In(root);}public class TreapNode<T extends Comparable<T>> {private T value;//节点所存储的值private int weight;//节点的权值,到插入的时候再分配private TreapNode<T> left;//左子节点private TreapNode<T> right;//右子节点private int size;//树的大小private boolean lazyReverse;//这里相比于普通的fhqTreap就是多了个懒标记private TreapNode(T value) {this.value = value;this.size = 1;this.weight = random.nextInt(range + 1);//给新节点生成一个随机权值this.lazyReverse = false;}}
}
public class test {public static void main(String[] args) {WenYiTree<Integer> fhq = new WenYiTree<>();for(int i = 1; i <= 5; i++) {fhq.merge(i);}fhq.getIn();System.out.println();fhq.reverse(2,6);fhq.getIn();}
}

无旋Treap(fhq)相关推荐

  1. 浅尝无旋Treap (基于洛谷P3391 文艺平衡树)

    说是浅尝吧,确实也挺浅的,完全是基于下面这道题写的↓ 洛谷P3391 自己去看题,我是懒得粘了... 分析 其实也没有什么好分析的,这就是一道Splay树的模板题,解决一般的Treap不能解决的区间维 ...

  2. luogu P2596 [ZJOI2006]书架(平衡树、无旋treap(按排名分裂)一些更复杂的操作)

    P2596 [ZJOI2006]书架 无旋treap可以维护一棵树的中序遍历结果.但是不支持通过编号来找节点.于是在无旋treap的基础上,我维护了每个节点的父亲,这样就可以求出一个节点是中序遍历中的 ...

  3. 无旋treap的简单思想以及模板

    因为学了treap,不想弃坑去学splay,终于理解了无旋treap... 好像普通treap没卵用...(再次大雾) 简单说一下思想免得以后忘记.普通treap因为带旋转操作似乎没卵用,而无旋tre ...

  4. 模板:无旋treap

    文章目录 前言 操作 合并 分裂 插入 删除 查找第k大 查询x的排名 前驱后继 完整代码 所谓无旋treap,就是不带旋转的treap 前言 现在"理论上"我会四种平衡树了 之前 ...

  5. BST、AVL、BTree、B+Tree、B*Tree、23Tree、234Tree、TTree、RBTree、LLRBTree、AATree、SplayTree、Treap、无旋Treap、scap

    喜欢这篇文章吗?喜欢的话去看博主的置顶博客,即可依据分类找到此文章的原版得到更好的体验, 图片及代码显示的问题,笔者深感抱歉,想要更好的体验去原博文即可. title: tree mathjax: t ...

  6. 无旋treap 文艺平衡树

    因为需要用到区间修改,所以该用splay(尚未填坑)或者无旋treap(刚刚填上) 最开始的建树用到了建笛卡尔树的方法,把id大于当前点的点不断出栈,又因为这道题的点是按序入栈的,所以当它无法让更多点 ...

  7. [BZOJ1503][NOI2004]郁闷的出纳员 无旋Treap

    1503: [NOI2004]郁闷的出纳员 Time Limit: 5 Sec  Memory Limit: 64 MB Description OIER公司是一家大型专业化软件公司,有着数以万计的员 ...

  8. P3835-[模板]可持久化平衡树【无旋Treap】

    正题 题目链接:https://www.luogu.com.cn/problem/P3835 题目大意 一个空可重集,要求支持 插入一个数xxx 删除一个数xxx 询问一个数xxx的排名 询问排名第x ...

  9. P3369-[模板]普通平衡树【无旋Treap】

    正题 题目链接:https://www.luogu.com.cn/problem/P3369 题目大意 一个空可重集,要求支持 插入一个数xxx 删除一个数xxx 询问一个数xxx的排名 询问排名第x ...

最新文章

  1. window下python扩展包大全
  2. fiddler自动响应AutoResponder之正则匹配Rule Editor
  3. key位置 win10生成的ssh_华为路由器配置SSH远程登录,就这么简单
  4. windows系统下_ffmpeg编译_2011年
  5. brew php imagemagick,关于node使用gm和imageMagic在mac的坑
  6. Linux下日志分析的几个常用命令
  7. 如何使用jQuery刷新页面?
  8. 前端技术分享和发展网站总结
  9. 木质机器人挂坠_设计癖 2014 大盘点:十大木质产品
  10. MagicDraw建模显示中文问题
  11. 工作那些事(二十八)项目管理模式:项目型、职能型、矩阵型
  12. 2021年王道数据结构课后题
  13. 学习华为云人脸识别踩坑,The subservice has not been subscribed和FRS.0020错误,projectId的获取
  14. 利用Exchange服务同步iOS和android中的联系人
  15. 限流的抖音号怎么养?养号方法是什么?
  16. Quartz数据库表分析
  17. 如何将DotNetFX35SP1打进安装包
  18. js日期加横杆_把日期横杠转化为斜杠
  19. excel导出文件加密
  20. java.lang.NoClassDefFoundError: Could not initialize class错误原因

热门文章

  1. 再次慨叹生命的脆弱,我辈当惜之
  2. google官方图标 Material icons 全图标一览
  3. 数独-图片定位分割数字
  4. python制作英语字典_python制作英文学习字典案列
  5. MySQL-基本概念与查询操作(DESC/SELECT/FROM/WHERE/LIKE)
  6. Hamibot 基于AutoJs的自动化安卓app小程序赚钱神奇
  7. 如何用matlab实现小波变换
  8. 【Python开发】Flask中的单点登录解决方案
  9. Python爬取王者荣耀英雄的皮肤数据并下载皮肤图片项目
  10. 关于C语言的system函数用法