K:Treap(堆树)
Treap=Tree+Heap。Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是, Treap记录一个额外的数据, 就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是 二叉堆必须是完全二叉树,而Treap不一定是完全二叉树。 该博文主要讲解了Treap树相关的知识点及其实现。
Treap
我们都知道,在二叉查找树中,当插入的数据为随机的时候,其有较好的性能使得孩子节点大体上左右均匀分布。但是,当插入的数据为有序的时候,其会退化为一个链表的状态,从而达不到效果。一般,我们有AVL或者红黑树以及Splay等平衡二叉树结构。但是,其在实现上难度较高。为此,引入了Treap,treap采用了二叉堆的性质来保持二叉树的平衡性。因为对于一个堆而言,其需要满足如下的性质:一个节点的两个孩子节点的值都小于节点的本身的值。但是,对于一棵二叉查找树而言,其需要满足一棵树的左孩子节点的值小于根节点的值并且右孩子节点的值大于根节点的值。这显然出现了冲突。为此,我们需要增加一个变量,用于满足这样的性质。我们将用于满足二叉搜索树的性质的值称之为key,用于满足堆的性质的值称之为priority(优先级)。
每个节点的key我们是无法改变的,必须要按照要求来,但是为了保持treap的平衡性,我们只能从priority上做文章。其实也并没有复杂之处,就是让每个节点的priority都取一个随机值,这样我们就可以保证这棵树“基本平衡”。
treap的基本操作
为了要满足二叉堆和平衡树的性质,treap中有两种操作用于将不满足二叉堆的性质的二叉查找树进行相应的调整,使其满足相应的性质。这两个操作分别为左旋和右旋:
旋转操作的目的是使得不满足堆序的两个节点通过调整位置,使其重新满足堆序。且不改变二叉查找树的性质。
ps:我们知道,堆有两种形式,一种是大顶堆,一种是小顶堆,以下讨论的,我们采用小顶堆的方式对其进行操作
treap的相关操作
在对treap树进行相关的操作之前,我们先定义treap的节点类:
class Node{
/**
* 元素的关键字值
*/
T key;
/**
* 节点的优先级,用于满足堆的性质
*/
int priority;
/**
* 该节点的左右孩子节点
*/
Node left;
Node right;
/**
* 一个随机数生成器,用于随机生成节点的元素的优先级
*/
Random random=new Random();
public Node(T key){
this(key,null,null);
}
public Node(T key,Node left,Node right){
this.key=key;
this.left=left;
this.right=right;
this.priority=random();
}
treap相关的操作有插入、删除、查找。
查找:
treap的查找操作并不影响treap树相关的性质,其只需要按照普通的二叉查找树的检索方式进行查找即可。
具体代码如下:
/**
* 用于treap树的查找操作
* @param key 需要进行查找的关键字
* @return 查找的相应的节点,当没有找到对应的节点时,其返回null
*/
public Node search(T key){
Node temp=this.root;
while(temp!=null){
int cmp=key.compareTo(temp.key);
if(cmp<0){
temp=temp.left;
}
else if(cmp>0){
temp=temp.right;
}
else{
break;
}
}
return temp;
}
插入:
在Treap中插入元素,与在BST中插入方法相似。首先找到合适的插入位置,然后建立新的节点,存储元素。但是要注意建立新的节点的过程中,会随机地生成一个修正值,这个值可能会破坏堆序,因此我们要根据需要进行恰当的旋转。具体方法如下:
从根节点开始插入;
如果要插入的值小于等于当前节点的值,在当前节点的左子树中插入,插入后如果左子节点的修正值小于当前节点的修正值,对当前节点进行右旋;
如果要插入的值大于当前节点的值,在当前节点的右子树中插入,插入后如果右子节点的修正值小于当前节点的修正值,对当前节点进行左旋;
如果当前节点为空节点,在此建立新的节点,该节点的值为要插入的值,左右子树为空,插入成功。
其过程如下例:
具体代码如下:
/**
* 用于往treap树中插入相应的节点
* @param key 节点元素的值
*/
public void insert(T key){
//当节点元素存在的时候
if(key==null||search(key)!=null){
return;
}
this.root=insert(this.root,key);
}
/**
* 用于插入元素相应的节点值
* @param node 需要进行比较的节点
* @param key 需要进行插入的键值
* @return 树对应的根节点
*/
private Node insert(Node node,T key){
if(node==null){
node=new Node(key);
}
else if(key.compareTo(node.key)<0){
node.left=insert(node.left,key);
//当满足情况的时候,对其进行旋转操作
if(node.left.priority<node.priority){
node=rotateRight(node);
}
}
else{
node.right=insert(node.right,key);
//当满足情况的时候,对其进行旋转操作
if(node.right.priority<node.priority){
node=rotateLeft(node);
}
}
return node;
}
删除:
与BST一样,在Treap中删除元素要考虑多种情况。我们可以按照在BST中删除元素同样的方法来删除Treap中的元素,即用它的后继(或前驱)节点的值代替它,然后删除它的后继(或前驱)节点并对其进行相应的旋转调整操作使其符合Treap中堆性质的要求。为了不使Treap向一边偏沉,我们需要随机地选取是用后继还是前驱代替它,并保证两种选择的概率均等。上述方法期望时间复杂度为O(logN),但是这种方法并没有充分利用Treap已有的随机性质,而是重新得随机选取代替节点。我们给出一种更为通用的删除方法,这种方法是基于旋转调整的。首先要在Treap树中找到待删除节点的位置,然后分情况讨论:
情况一, 该节点为叶节点或链节点(即只有一个孩子节点的节点),则该节点是可以直接删除的节点。若该节点有非空子节点,用非空子节点代替该节点的,否则用空节点代替该节点,然后删除该节点。
情况二, 该节点有两个非空子节点。我们的策略是通过旋转,使该节点变为可以直接删除的节点。如果该节点的左子节点的修正值小于右子节点的修正值,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续讨论;反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,继续讨论,直到变成可以直接删除的节点。(也就是让该节点的左右孩子节点中的最小优先级的节点称为该节点)
其过程如下例:
具体代码如下:
/**
* 节点的删除操作
* @param key 需要进行删除的节点的键值
*/
public void remove(T key){
//用于判空操作
if(key==null||search(key)==null){
return;
}
this.root=remove(this.root,key);
}
/**
* 删除对应的节点
* @param node 开始进行比较的节点
* @param key 进行删除的节点的关键字
* @return 其子树的根节点
*/
private Node remove(Node node,T key){
if(node==null){
return null;
}
//左子树
if (key.compareTo(node.key)<0){
node.left=remove(node.left,key);
}
//右子树
if(key.compareTo(node.key)>0){
node.right=remove(node.right,key);
}
//相等的情况,即删除的节点为该节点时
if(key.compareTo(node.key)==0){
//当存在左右孩子节点的时候
if(node.left!=null&&node.right!=null){
//如果左孩子优先级低就右旋
if(node.left.priority<node.right.priority){
node=rotateRight(node);
}
else{
node=rotateLeft(node);
}
//旋转后继续进行删除操作
node=remove(node,key);
}
else{
//当其为根节点的时候
if(node.left==null&&node.right==null){
return null;
}
//当其为单分支树的时候
node=node.left==null?node.right:node.left;
}
}
return node;
}
完整代码如下:
import java.util.Random;
/**
* @author 学徒
*用于实现Treap
*/
public class Treap<T extends Comparable<T>>{
/**
* 树的根节点
*/
private Node root;
/**
* 树的节点类
*/
private class Node{
/**
* 元素的关键字值
*/
T key;
/**
* 节点的优先级,用于满足堆的性质
*/
int priority;
/**
* 该节点的左右孩子节点
*/
Node left;
Node right;
/**
* 一个随机数生成器,用于随机生成节点的元素的优先级
*/
Random random=new Random();
public Node(T key){
this(key,null,null);
}
public Node(T key,Node left,Node right){
this.key=key;
this.left=left;
this.right=right;
this.priority=random();
}
/**
* 用于随机获取节点的优先级的随机数生成函数
* @return 随机数值
* 参考自:https://blog.csdn.net/chen_tr/article/details/50924073 随机数的生成。这样做,据说可以使得随机数的值
* 可以不发生重复
*/
private int random(){
int seed=random.nextInt();
return (int)(seed*48271L%Integer.MAX_VALUE);
}
}
/**
* 用于其相应的左旋操作
* @param node 进行旋转的节点
* @return 旋转后的根节点
*/
private Node rotateLeft(Node node){
Node temp=node.right;
node.right=temp.left;
temp.left=node;
return temp;
}
/**
* 用于其相应的右旋操作
* @param node 进行旋转的节点
* @return 旋转后的根节点
*/
private Node rotateRight(Node node){
Node temp=node.left;
node.left=temp.right;
temp.right=node;
return temp;
}
/**
* 用于treap树的查找操作
* @param key 需要进行查找的关键字
* @return 查找的相应的节点,当没有找到对应的节点时,其返回null
*/
public Node search(T key){
Node temp=this.root;
while(temp!=null){
int cmp=key.compareTo(temp.key);
if(cmp<0){
temp=temp.left;
}
else if(cmp>0){
temp=temp.right;
}
else{
break;
}
}
return temp;
}
/**
* 用于往treap树中插入相应的节点
* @param key 节点元素的值
*/
public void insert(T key){
//当节点元素存在的时候
if(key==null||search(key)!=null){
return;
}
this.root=insert(this.root,key);
}
/**
* 用于插入元素相应的节点值
* @param node 需要进行比较的节点
* @param key 需要进行插入的键值
* @return 树对应的根节点
*/
private Node insert(Node node,T key){
if(node==null){
node=new Node(key);
}
else if(key.compareTo(node.key)<0){
node.left=insert(node.left,key);
//当满足情况的时候,对其进行旋转操作
if(node.left.priority<node.priority){
node=rotateRight(node);
}
}
else{
node.right=insert(node.right,key);
//当满足情况的时候,对其进行旋转操作
if(node.right.priority<node.priority){
node=rotateLeft(node);
}
}
return node;
}
/**
* 节点的删除操作
* @param key 需要进行删除的节点的键值
*/
public void remove(T key){
//用于判空操作
if(key==null||search(key)==null){
return;
}
this.root=remove(this.root,key);
}
/**
* 删除对应的节点
* @param node 开始进行比较的节点
* @param key 进行删除的节点的关键字
* @return 其子树的根节点
*/
private Node remove(Node node,T key){
if(node==null){
return null;
}
//左子树
if (key.compareTo(node.key)<0){
node.left=remove(node.left,key);
}
//右子树
if(key.compareTo(node.key)>0){
node.right=remove(node.right,key);
}
//相等的情况,即删除的节点为该节点时
if(key.compareTo(node.key)==0){
//当存在左右孩子节点的时候
if(node.left!=null&&node.right!=null){
//如果左孩子优先级低就右旋
if(node.left.priority<node.right.priority){
node=rotateRight(node);
}
else{
node=rotateLeft(node);
}
//旋转后继续进行删除操作
node=remove(node,key);
}
else{
//当其为根节点的时候
if(node.left==null&&node.right==null){
return null;
}
//当其为单分支树的时候
node=node.left==null?node.right:node.left;
}
}
return node;
}
}
K:Treap(堆树)的更多相关文章
- Treap(树堆)入门
作者:zifeiy 标签:Treap 首先,我么要知道:Treap=Tree+Heap. 这里: Tree指的是二叉排序树: Heap指的是堆. 所以在阅读这篇文章之前需要大家对 二叉查找树 和 堆( ...
- treap(堆树)
# 2018-09-27 17:35:58 我实现的这个treap不能算是堆.有问题 最近对堆这种结构有点感兴趣,然后想用指针的方式实现一个堆而不是利用数组这种结构,于是自己想到了一个用二叉树结构实现 ...
- [模板] 平衡树: Splay, 非旋Treap, 替罪羊树
简介 二叉搜索树, 可以维护一个集合/序列, 同时维护节点的 \(size\), 因此可以支持 insert(v), delete(v), kth(p,k), rank(v)等操作. 另外, prev ...
- NOI 2015 荷马史诗【BZOJ 4198】k叉Huffman树
抱歉因为NOIP集训,好长时间没再写题解了. NOI 2015也就只有这道题一看就能懂了-- 4198: [Noi2015]荷马史诗 Time Limit: 10 Sec Memory Limit: ...
- 「模板」「讲解」Treap名次树
Treap实现名次树 前言 学平衡树的过程可以说是相当艰难.浏览Blog的过程中看到大量指针版平衡树,不擅长指针操作的我已经接近崩溃.于是,我想着一定要写一篇非指针实现的Treap的Blog. 具体如 ...
- 计蒜客 38229.Distance on the tree-1.树链剖分(边权)+可持久化线段树(区间小于等于k的数的个数)+离散化+离线处理 or 2.树上第k大(主席树)+二分+离散化+在线查询 (The Preliminary Contest for ICPC China Nanchang National Invitational 南昌邀请赛网络赛)
Distance on the tree DSM(Data Structure Master) once learned about tree when he was preparing for NO ...
- Cogs 1345. [ZJOI2013] K大数查询(树套树)
[ZJOI2013] K大数查询 /* 树套树写法. bzoj过不了. 可能有负数要离散吧. 线段树套线段树. 外层权值线段树,内层区间线段树维护标记. 对权值建一棵权值线段树. 某个点表示权值在某个 ...
- 查找——图文翔解Treap(树堆)
之前我们讲到二叉搜索树,从二叉搜索树到2-3树到红黑树到B-树. 二叉搜索树的主要问题就是其结构与数据相关,树的深度可能会非常大,Treap树就是一种解决二叉搜索树可能深度过大的还有一种数据结构. T ...
- zoj 2112 Dynamic Rankings 动态第k大 线段树套Treap
Dynamic Rankings Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.zju.edu.cn/onlinejudge/show ...
随机推荐
- jzoj5950
我們發現如下結論: 1.只有編號小的點才會對編號大的點產生影響 2.當當前點的y坐標大小為1~i最大的,則不會被以前的點影響 於是,維護一個斜率單調遞增的隊列 當當前點的y坐標大小為1~i最大的,則這 ...
- MySQL(安装,服务,创建用户及授权)
参考:http://www.cnblogs.com/wupeiqi/p/5713315.html 单机程序(自己DB) 单机程序(公用DB) MySQL:是用于管理文件的一 ...
- java实现简单扫雷游戏
/** * 一个简单的扫雷游戏 MainFram.java */ package www.waston; import java.awt.BorderLayout; import java.awt.C ...
- 重新设置Linux文件共享密码..
今天同事把我主机踹倒了,鼠键都没反应,于是我在Linux运行的情况下强制重启了下电脑. 启动完了VMware后Linux重启,正常使用.后来我想起来有快照功能 之前也没用到过,于是就点了一下刚开始安装 ...
- Jmeter 多台机器产生负载及问题解决方法
JMeter 使用多台机器产生负载的操作步骤如下: 关于linux环境运行jmeter,分布式测试 见 http://www.51testing.com/html/55/383255-847895.h ...
- NOIP2017滚粗记【上】
Day0: NOIP前停课训练的最后一天,上午打了一场三题都见过的比赛,一窝人AK. 下午一群人在机房缓慢氧化,到了晚上因为比赛在我们学校打,所以所有的机房都断网了(百思不得其解为什么两个竞赛室也被断 ...
- Label Propagation Algorithm LPA 标签传播算法解析及matlab代码实现
转载请注明出处:http://www.cnblogs.com/bethansy/p/6953625.html LPA算法的思路: 首先每个节点有一个自己特有的标签,节点会选择自己邻居中出现次数最多的标 ...
- openssl生成RSA格式的公私钥,并转为pkcs8格式
第一步:生成私钥,这里我们指定私钥的长度为2048 openssl genrsa -out rsa_private_key.pem 2048 第二步:根据私钥生成对应的公钥: openssl rsa ...
- UBUNTU 下 APACHE2 Too many open files: Error retrieving pid file /var/run/apache2.pid
cat /proc/sys/fs/file-max 系统可打开的最大文件个数 ulimit -n 当前系统限制的个数 ulimit -n 10240 调整当前系统的限制 修改/etc/sysctl.c ...
- Android 开发工具类 32_通过 HTTP 协议实现文件上传
完成像带有文件的用户数据表单的上传,而且可以上传多个文件,这在用户注册并拍照时尤其有用. import java.io.BufferedReader; import java.io.ByteArray ...