6天通吃树结构—— 第三天 Treap树
我们知道,二叉查找树相对来说比较容易形成最坏的链表情况,所以前辈们想尽了各种优化策略,包括AVL,红黑,以及今天
要讲的Treap树。
Treap树算是一种简单的优化策略,这名字大家也能猜到,树和堆的合体,其实原理比较简单,在树中维护一个"优先级“,”优先级“
采用随机数的方法,但是”优先级“必须满足根堆的性质,当然是“大根堆”或者“小根堆”都无所谓,比如下面的一棵树:
从树中我们可以看到:
①:节点中的key满足“二叉查找树”。
②:节点中的“优先级”满足小根堆。
一:基本操作
1:定义
#region Treap树节点
/// <summary>
/// Treap树
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
public class TreapNode<K, V>
{
/// <summary>
/// 节点元素
/// </summary>
public K key; /// <summary>
/// 优先级(采用随机数)
/// </summary>
public int priority; /// <summary>
/// 节点中的附加值
/// </summary>
public HashSet<V> attach = new HashSet<V>(); /// <summary>
/// 左节点
/// </summary>
public TreapNode<K, V> left; /// <summary>
/// 右节点
/// </summary>
public TreapNode<K, V> right; public TreapNode() { } public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right)
{
//KV键值对
this.key = key;
this.priority = new Random(DateTime.Now.Millisecond).Next(,int.MaxValue);
this.attach.Add(value); this.left = left;
this.right = right;
}
}
#endregion
节点里面定义了一个priority作为“堆定义”的旋转因子,因子采用“随机数“。
2:添加
首先我们知道各个节点的“优先级”是采用随机数的方法,那么就存在一个问题,当我们插入一个节点后,优先级不满足“堆定义"的
时候我们该怎么办,前辈说此时需要旋转,直到满足堆定义为止。
旋转有两种方式,如果大家玩转了AVL,那么对Treap中的旋转的理解轻而易举。
①: 左左情况旋转
从图中可以看出,当我们插入“节点12”的时候,此时“堆性质”遭到破坏,必须进行旋转,我们发现优先级是6<9,所以就要进行
左左情况旋转,最终也就形成了我们需要的结果。
②: 右右情况旋转
既然理解了”左左情况旋转“,右右情况也是同样的道理,优先级中发现“6<9",进行”右右旋转“最终达到我们要的效果。
#region 添加操作
/// <summary>
/// 添加操作
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(K key, V value)
{
node = Add(key, value, node);
}
#endregion #region 添加操作
/// <summary>
/// 添加操作
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="tree"></param>
/// <returns></returns>
public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree)
{
if (tree == null)
tree = new TreapNode<K, V>(key, value, null, null); //左子树
if (key.CompareTo(tree.key) < )
{
tree.left = Add(key, value, tree.left); //根据小根堆性质,需要”左左情况旋转”
if (tree.left.priority < tree.priority)
{
tree = RotateLL(tree);
}
} //右子树
if (key.CompareTo(tree.key) > )
{
tree.right = Add(key, value, tree.right); //根据小根堆性质,需要”右右情况旋转”
if (tree.right.priority < tree.priority)
{
tree = RotateRR(tree);
}
} //将value追加到附加值中(也可对应重复元素)
if (key.CompareTo(tree.key) == )
tree.attach.Add(value); return tree;
}
#endregion
3:删除
跟普通的二叉查找树一样,删除结点存在三种情况。
①:叶子结点
跟普通查找树一样,直接释放本节点即可。
②:单孩子结点
跟普通查找树一样操作。
③:满孩子结点
其实在treap中删除满孩子结点有两种方式。
第一种:跟普通的二叉查找树一样,找到“右子树”的最左结点(15),拷贝元素的值,但不拷贝元素的优先级,然后在右子树中
删除“结点15”即可,最终效果如下图。
第二种:将”结点下旋“,直到该节点不是”满孩子的情况“,该赋null的赋null,该将孩子结点顶上的就顶上,如下图:
当然从理论上来说,第二种删除方法更合理,这里我写的就是第二种情况的代码。
#region 删除当前树中的节点
/// <summary>
/// 删除当前树中的节点
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public void Remove(K key, V value)
{
node = Remove(key, value, node);
}
#endregion #region 删除当前树中的节点
/// <summary>
/// 删除当前树中的节点
/// </summary>
/// <param name="key"></param>
/// <param name="tree"></param>
/// <returns></returns>
public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree)
{
if (tree == null)
return null; //左子树
if (key.CompareTo(tree.key) < )
{
tree.left = Remove(key, value, tree.left);
}
//右子树
if (key.CompareTo(tree.key) > )
{
tree.right = Remove(key, value, tree.right);
}
/*相等的情况*/
if (key.CompareTo(tree.key) == )
{
//判断里面的HashSet是否有多值
if (tree.attach.Count > )
{
//实现惰性删除
tree.attach.Remove(value);
}
else
{
//有两个孩子的情况
if (tree.left != null && tree.right != null)
{
//如果左孩子的优先级低就需要“左旋”
if (tree.left.priority < tree.right.priority)
{
tree = RotateLL(tree);
}
else
{
//否则“右旋”
tree = RotateRR(tree);
} //继续旋转
tree = Remove(key, value, tree);
}
else
{
//如果旋转后已经变成了叶子节点则直接删除
if (tree == null)
return null; //最后就是单支树
tree = tree.left == null ? tree.right : tree.left;
}
}
} return tree;
}
#endregion
4:总结
treap树在CURD中是期望的logN,由于我们加了”优先级“,所以会出现”链表“的情况几乎不存在,但是他的Add和Remove相比严格的
平衡二叉树有更少的旋转操作,可以说性能是在”普通二叉树“和”平衡二叉树“之间。
最后是总运行代码,不过这里我就不做测试了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace DataStruct
{
#region Treap树节点
/// <summary>
/// Treap树
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
public class TreapNode<K, V>
{
/// <summary>
/// 节点元素
/// </summary>
public K key; /// <summary>
/// 优先级(采用随机数)
/// </summary>
public int priority; /// <summary>
/// 节点中的附加值
/// </summary>
public HashSet<V> attach = new HashSet<V>(); /// <summary>
/// 左节点
/// </summary>
public TreapNode<K, V> left; /// <summary>
/// 右节点
/// </summary>
public TreapNode<K, V> right; public TreapNode() { } public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right)
{
//KV键值对
this.key = key;
this.priority = new Random(DateTime.Now.Millisecond).Next(,int.MaxValue);
this.attach.Add(value); this.left = left;
this.right = right;
}
}
#endregion public class TreapTree<K, V> where K : IComparable
{
public TreapNode<K, V> node = null; #region 添加操作
/// <summary>
/// 添加操作
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(K key, V value)
{
node = Add(key, value, node);
}
#endregion #region 添加操作
/// <summary>
/// 添加操作
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="tree"></param>
/// <returns></returns>
public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree)
{
if (tree == null)
tree = new TreapNode<K, V>(key, value, null, null); //左子树
if (key.CompareTo(tree.key) < )
{
tree.left = Add(key, value, tree.left); //根据小根堆性质,需要”左左情况旋转”
if (tree.left.priority < tree.priority)
{
tree = RotateLL(tree);
}
} //右子树
if (key.CompareTo(tree.key) > )
{
tree.right = Add(key, value, tree.right); //根据小根堆性质,需要”右右情况旋转”
if (tree.right.priority < tree.priority)
{
tree = RotateRR(tree);
}
} //将value追加到附加值中(也可对应重复元素)
if (key.CompareTo(tree.key) == )
tree.attach.Add(value); return tree;
}
#endregion #region 第一种:左左旋转(单旋转)
/// <summary>
/// 第一种:左左旋转(单旋转)
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public TreapNode<K, V> RotateLL(TreapNode<K, V> node)
{
//top:需要作为顶级节点的元素
var top = node.left; //先截断当前节点的左孩子
node.left = top.right; //将当前节点作为temp的右孩子
top.right = node; return top;
}
#endregion #region 第二种:右右旋转(单旋转)
/// <summary>
/// 第二种:右右旋转(单旋转)
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public TreapNode<K, V> RotateRR(TreapNode<K, V> node)
{
//top:需要作为顶级节点的元素
var top = node.right; //先截断当前节点的右孩子
node.right = top.left; //将当前节点作为temp的右孩子
top.left = node; return top;
}
#endregion #region 树的指定范围查找
/// <summary>
/// 树的指定范围查找
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns></returns>
public HashSet<V> SearchRange(K min, K max)
{
HashSet<V> hashSet = new HashSet<V>(); hashSet = SearchRange(min, max, hashSet, node); return hashSet;
}
#endregion #region 树的指定范围查找
/// <summary>
/// 树的指定范围查找
/// </summary>
/// <param name="range1"></param>
/// <param name="range2"></param>
/// <param name="tree"></param>
/// <returns></returns>
public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, TreapNode<K, V> tree)
{
if (tree == null)
return hashSet; //遍历左子树(寻找下界)
if (min.CompareTo(tree.key) < )
SearchRange(min, max, hashSet, tree.left); //当前节点是否在选定范围内
if (min.CompareTo(tree.key) <= && max.CompareTo(tree.key) >= )
{
//等于这种情况
foreach (var item in tree.attach)
hashSet.Add(item);
} //遍历右子树(两种情况:①:找min的下限 ②:必须在Max范围之内)
if (min.CompareTo(tree.key) > || max.CompareTo(tree.key) > )
SearchRange(min, max, hashSet, tree.right); return hashSet;
}
#endregion #region 找到当前树的最小节点
/// <summary>
/// 找到当前树的最小节点
/// </summary>
/// <returns></returns>
public TreapNode<K, V> FindMin()
{
return FindMin(node);
}
#endregion #region 找到当前树的最小节点
/// <summary>
/// 找到当前树的最小节点
/// </summary>
/// <param name="tree"></param>
/// <returns></returns>
public TreapNode<K, V> FindMin(TreapNode<K, V> tree)
{
if (tree == null)
return null; if (tree.left == null)
return tree; return FindMin(tree.left);
}
#endregion #region 找到当前树的最大节点
/// <summary>
/// 找到当前树的最大节点
/// </summary>
/// <returns></returns>
public TreapNode<K, V> FindMax()
{
return FindMin(node);
}
#endregion #region 找到当前树的最大节点
/// <summary>
/// 找到当前树的最大节点
/// </summary>
/// <param name="tree"></param>
/// <returns></returns>
public TreapNode<K, V> FindMax(TreapNode<K, V> tree)
{
if (tree == null)
return null; if (tree.right == null)
return tree; return FindMax(tree.right);
}
#endregion #region 删除当前树中的节点
/// <summary>
/// 删除当前树中的节点
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public void Remove(K key, V value)
{
node = Remove(key, value, node);
}
#endregion #region 删除当前树中的节点
/// <summary>
/// 删除当前树中的节点
/// </summary>
/// <param name="key"></param>
/// <param name="tree"></param>
/// <returns></returns>
public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree)
{
if (tree == null)
return null; //左子树
if (key.CompareTo(tree.key) < )
{
tree.left = Remove(key, value, tree.left);
}
//右子树
if (key.CompareTo(tree.key) > )
{
tree.right = Remove(key, value, tree.right);
}
/*相等的情况*/
if (key.CompareTo(tree.key) == )
{
//判断里面的HashSet是否有多值
if (tree.attach.Count > )
{
//实现惰性删除
tree.attach.Remove(value);
}
else
{
//有两个孩子的情况
if (tree.left != null && tree.right != null)
{
//如果左孩子的优先级低就需要“左旋”
if (tree.left.priority < tree.right.priority)
{
tree = RotateLL(tree);
}
else
{
//否则“右旋”
tree = RotateRR(tree);
} //继续旋转
tree = Remove(key, value, tree);
}
else
{
//如果旋转后已经变成了叶子节点则直接删除
if (tree == null)
return null; //最后就是单支树
tree = tree.left == null ? tree.right : tree.left;
}
}
} return tree;
}
#endregion
}
}
6天通吃树结构—— 第三天 Treap树的更多相关文章
- 6天通吃树结构—— 第五天 Trie树
原文:6天通吃树结构-- 第五天 Trie树 很有段时间没写此系列了,今天我们来说Trie树,Trie树的名字有很多,比如字典树,前缀树等等. 一:概念 下面我们有and,as,at,cn,com这些 ...
- treap树模板
///treap树模板 typedef struct Node ///节点的结构体 { Node *l,*r; int val,pri; ///节点的值和优先级 int sz; ///节点子树的节点数 ...
- poj 2761 Feed the dogs (treap树)
/************************************************************* 题目: Feed the dogs(poj 2761) 链接: http: ...
- Atitit 常见的树形结构 红黑树 二叉树 B树 B+树 Trie树 attilax理解与总结
Atitit 常见的树形结构 红黑树 二叉树 B树 B+树 Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 ...
- treap树---Double Queue
HDU 1908 Description The new founded Balkan Investment Group Bank (BIG-Bank) opened a new office i ...
- Treap树的基础知识
原文 其它较好的的介绍:堆排序 AVL树 树堆,在数据结构中也称Treap(事实上在国内OI界常称为Traep,与之同理的还有"Tarjan神犇发明的"Spaly),是指有一个随 ...
- Treap树理解
title: Treap树理解 comments: true date: 2016-10-06 07:57:37 categories: 算法 tags: Treap树 树 Treap树理解 简介 随 ...
- bzoj2141 树状数组套Treap树
题目大意是在能够改变两个数的位置的情况下计算逆序对数 这因为是动态记录逆序对 本来单纯逆序对只要用树状数组计算即可,但这里因为更新,所以利用TReap树的删点和增加点来进行更新 大致是把每个树状数组所 ...
- treap树---营业额统计
台州学院 2924 描述 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况.Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额 ...
随机推荐
- Shell在大数据的魅力时代:从一点点思路百度大数据面试题
供Linux开发中的同学们,Shell这可以说是一个基本功. 对于同学们的操作和维护.Shell也可以说是一种必要的技能,Shell.对于Release Team,软件配置管理的同学来说.Shell也 ...
- 《Java程序猿面试笔试宝典》之Java与C/C++有什么异同
Java与C++都是面向对象语言,都使用了面向对象思想(比如封装.继承.多态等),因为面向对象有很多非常好的特性(继承.组合等),使得二者都有非常好的可重用性. 须要注意的是,二者并不是全然一样,以下 ...
- MVC模式编程演示样本-登录认证(静态)
好,部分博客分享我的总结JSP-Servlet-JavaBean思想认识和三层编程模型的基本流程,ZH- CNMVC该示例实现演示的编程模式-登录身份验证过程,在这里,我仍在使用静态验证usernam ...
- 每天进步一点点——Linux文件锁编程flock
转载请注明出处:http://blog.csdn.net/cywosp/article/details/30083015 1. 场景概述 在多线程开发中.相互排斥锁能够用于对临界资源的保护,防 ...
- AsyncSocket长连接棒包装问题解决
project正在使用长连接快来server沟通.因此,指定我们的协议前两个字节为数据长度来区分数据包 app这边数据有两种传输形式: 1.app主动请求所须要的数据: 2.app异步接收来自服务端的 ...
- POJ 2756 Autumn is a Genius 采用string大数减法
标题意味着小神童.加减可以计算. 只是说这个小神童的学科知识,究竟有多神,自己给自己找. 最后,因为数据是非常非常巨大的,我听说关闭50k结束了50000数字总和,可以想见他神教. 这似乎也是考试题目 ...
- Android 实现蘑菇街购物车动画效果
版本号:1.0 日期:2014.8.6 版权:© 2014 kince 转载注明出处 使用过蘑菇街的用户基本上都知道有一个增加购物车的动画效果,此处不详细描写叙述想知道的能够去下载体验一下. 1 ...
- C# WinForm中实现CheckBox全选反选功能
今天一群里有人问到这个功能,其实应该挺简单,但提问题的人问题的出发点并没有描述清楚.因此,一个简简单单的需求,就引起了群内热烈的讨论.下面看看这个功能如何去实现,先上效果: 下面直接上代码,请不要在意 ...
- 高性能mysql主存架构
原文:高性能mysql主存架构 MySQL Replication(Master与Slave基本原理及配置) 主从mysql工作原理: 1:过程: (1)Mysql的复制(replication)是一 ...
- MapReduce 规划 系列十 采用HashPartitioner调整Reducer计算负荷
example4它演示了如何指定Reducer号码,本节演示如何使用HashPartitioner将Mapper根据该输出key分组后Reducer为了应对. 合理的分组策略会尽一切Reducer不能 ...