Treap树
2016-03-04 09:24
211 查看
1:定义
节点里面定义了一个priority作为“堆定义”的旋转因子,因子采用“随机数“。
2:添加
首先我们知道各个节点的“优先级”是采用随机数的方法,那么就存在一个问题,当我们插入一个节点后,优先级不满足“堆定义"的
时候我们该怎么办,前辈说此时需要旋转,直到满足堆定义为止。
旋转有两种方式,如果大家玩转了AVL,那么对Treap中的旋转的理解轻而易举。
①: 左左情况旋转
从图中可以看出,当我们插入“节点12”的时候,此时“堆性质”遭到破坏,必须进行旋转,我们发现优先级是6<9,所以就要进行
左左情况旋转,最终也就形成了我们需要的结果。
②: 右右情况旋转
既然理解了”左左情况旋转“,右右情况也是同样的道理,优先级中发现“6<9",进行”右右旋转“最终达到我们要的效果。
3:删除
跟普通的二叉查找树一样,删除结点存在三种情况。
①:叶子结点
跟普通查找树一样,直接释放本节点即可。
②:单孩子结点
跟普通查找树一样操作。
③:满孩子结点
其实在treap中删除满孩子结点有两种方式。
第一种:跟普通的二叉查找树一样,找到“右子树”的最左结点(15),拷贝元素的值,但不拷贝元素的优先级,然后在右子树中
删除“结点15”即可,最终效果如下图。
第二种:将”结点下旋“,直到该节点不是”满孩子的情况“,该赋null的赋null,该将孩子结点顶上的就顶上,如下图:
当然从理论上来说,第二种删除方法更合理,这里我写的就是第二种情况的代码。
1 #region Treap树节点 2 /// <summary> 3 /// Treap树 4 /// </summary> 5 /// <typeparam name="K"></typeparam> 6 /// <typeparam name="V"></typeparam> 7 public class TreapNode<K, V> 8 { 9 /// <summary> 10 /// 节点元素 11 /// </summary> 12 public K key; 13 14 /// <summary> 15 /// 优先级(采用随机数) 16 /// </summary> 17 public int priority; 18 19 /// <summary> 20 /// 节点中的附加值 21 /// </summary> 22 public HashSet<V> attach = new HashSet<V>(); 23 24 /// <summary> 25 /// 左节点 26 /// </summary> 27 public TreapNode<K, V> left; 28 29 /// <summary> 30 /// 右节点 31 /// </summary> 32 public TreapNode<K, V> right; 33 34 public TreapNode() { } 35 36 public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right) 37 { 38 //KV键值对 39 this.key = key; 40 this.priority = new Random(DateTime.Now.Millisecond).Next(0,int.MaxValue); 41 this.attach.Add(value); 42 43 this.left = left; 44 this.right = right; 45 } 46 }
节点里面定义了一个priority作为“堆定义”的旋转因子,因子采用“随机数“。
2:添加
首先我们知道各个节点的“优先级”是采用随机数的方法,那么就存在一个问题,当我们插入一个节点后,优先级不满足“堆定义"的
时候我们该怎么办,前辈说此时需要旋转,直到满足堆定义为止。
旋转有两种方式,如果大家玩转了AVL,那么对Treap中的旋转的理解轻而易举。
①: 左左情况旋转
从图中可以看出,当我们插入“节点12”的时候,此时“堆性质”遭到破坏,必须进行旋转,我们发现优先级是6<9,所以就要进行
左左情况旋转,最终也就形成了我们需要的结果。
②: 右右情况旋转
既然理解了”左左情况旋转“,右右情况也是同样的道理,优先级中发现“6<9",进行”右右旋转“最终达到我们要的效果。
1 #region 添加操作 2 /// <summary> 3 /// 添加操作 4 /// </summary> 5 /// <param name="key"></param> 6 /// <param name="value"></param> 7 public void Add(K key, V value) 8 { 9 node = Add(key, value, node); 10 } 11 #endregion 12 13 #region 添加操作 14 /// <summary> 15 /// 添加操作 16 /// </summary> 17 /// <param name="key"></param> 18 /// <param name="value"></param> 19 /// <param name="tree"></param> 20 /// <returns></returns> 21 public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree) 22 { 23 if (tree == null) 24 tree = new TreapNode<K, V>(key, value, null, null); 25 26 //左子树 27 if (key.CompareTo(tree.key) < 0) 28 { 29 tree.left = Add(key, value, tree.left); 30 31 //根据小根堆性质,需要”左左情况旋转” 32 if (tree.left.priority < tree.priority) 33 { 34 tree = RotateLL(tree); 35 } 36 } 37 38 //右子树 39 if (key.CompareTo(tree.key) > 0) 40 { 41 tree.right = Add(key, value, tree.right); 42 43 //根据小根堆性质,需要”右右情况旋转” 44 if (tree.right.priority < tree.priority) 45 { 46 tree = RotateRR(tree); 47 } 48 } 49 50 //将value追加到附加值中(也可对应重复元素) 51 if (key.CompareTo(tree.key) == 0) 52 tree.attach.Add(value); 53 54 return tree; 55 }
3:删除
跟普通的二叉查找树一样,删除结点存在三种情况。
①:叶子结点
跟普通查找树一样,直接释放本节点即可。
②:单孩子结点
跟普通查找树一样操作。
③:满孩子结点
其实在treap中删除满孩子结点有两种方式。
第一种:跟普通的二叉查找树一样,找到“右子树”的最左结点(15),拷贝元素的值,但不拷贝元素的优先级,然后在右子树中
删除“结点15”即可,最终效果如下图。
第二种:将”结点下旋“,直到该节点不是”满孩子的情况“,该赋null的赋null,该将孩子结点顶上的就顶上,如下图:
当然从理论上来说,第二种删除方法更合理,这里我写的就是第二种情况的代码。
1 #region 删除当前树中的节点 2 /// <summary> 3 /// 删除当前树中的节点 4 /// </summary> 5 /// <param name="key"></param> 6 /// <returns></returns> 7 public void Remove(K key, V value) 8 { 9 node = Remove(key, value, node); 10 } 11 #endregion 12 13 #region 删除当前树中的节点 14 /// <summary> 15 /// 删除当前树中的节点 16 /// </summary> 17 /// <param name="key"></param> 18 /// <param name="tree"></param> 19 /// <returns></returns> 20 public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree) 21 { 22 if (tree == null) 23 return null; 24 25 //左子树 26 if (key.CompareTo(tree.key) < 0) 27 { 28 tree.left = Remove(key, value, tree.left); 29 } 30 //右子树 31 if (key.CompareTo(tree.key) > 0) 32 { 33 tree.right = Remove(key, value, tree.right); 34 } 35 /*相等的情况*/ 36 if (key.CompareTo(tree.key) == 0) 37 { 38 //判断里面的HashSet是否有多值 39 if (tree.attach.Count > 1) 40 { 41 //实现惰性删除 42 tree.attach.Remove(value); 43 } 44 else 45 { 46 //有两个孩子的情况 47 if (tree.left != null && tree.right != null) 48 { 49 //如果左孩子的优先级低就需要“左旋” 50 if (tree.left.priority < tree.right.priority) 51 { 52 tree = RotateLL(tree); 53 } 54 else 55 { 56 //否则“右旋” 57 tree = RotateRR(tree); 58 } 59 60 //继续旋转 61 tree = Remove(key, value, tree); 62 } 63 else 64 { 65 //如果旋转后已经变成了叶子节点则直接删除 66 if (tree == null) 67 return null; 68 69 //最后就是单支树 70 tree = tree.left == null ? tree.right : tree.left; 71 } 72 } 73 } 74 75 return tree; 76 }
相关文章推荐
- 早晨突然想到的几句话
- 简明 Vim 练级攻略
- 结构体定义别名时定义为*~~的情况
- python-技巧
- Android启动活动的最佳写法
- Android keystore 密码忘记了的找回办法
- .NET面试题解析(03)-string与字符串操作
- oracle 两个日期相差得月数
- 输入格式CombineFileInput
- Unity之计算环境反射WorldRefl
- 蓝桥杯 字符串转整数
- 软件测试(一) 近期的一次debug的经历
- 进程间通信
- MacOS安装与运行MongoDB
- Linux下查看文件内容的命令
- 【redis基础篇三】set集合常用命令
- 怎样利用细碎时间达到整体学习的效果
- Linux 添加开机启动项的两种方法
- iOS - App内使用代码退出程序
- LeetCode题解:Maximum Subarray