《算法》中的红黑树实现
有别于上一篇文章介绍的红黑树,在《算法<第四版>》一书中用另一套规则实现了红黑树,主要手段是递归,实现思路来自于2-3树,这本书中有详细的解读,在这里我谈谈自己对它的理解。
首先,在之前文章中介绍的红黑树,我们把节点看成红,黑两色,而这里红节点指的是它指向父亲的链接是红色的,有什么不同?当我们介绍左旋,右旋你就会看到。
来看看这套定义红黑树的规则:
- 红链接均为左链接
- 没有任何一个节点同时与两个红链接相连
- 该树是完美的黑平衡,即任何空节点到根节点的路径上的黑链接数目相同
与之前红黑树的不同:之前的红节点在左在右都行。
先来看看关于Node的定义:
private class Node {private int size;private Key key;private Value value;private Node left;private Node right;private boolean color;public Node(Key key, Value value, boolean color, int size) {this.color = color;this.key = key;this.value = value;this.size = size;}}
只有左右两个指针没了之前的parent指针,加了个size用于记录节点个数,规则是x.size = size(x.left) + size(x.right) + 1;
再来介绍三板斧:rotateLeft, rotateRight, flipColors 这三板斧在变换过程中不会破坏黑平衡
rotateLeft
看到区别没有,原来的黑节点变成了红,红链接被转过来了;还有一个要注意的地方就是它的返回,接下来的操作我们会经常看到这样 x = rotateLeft(x); 这个x实际上在变换后变成了他原来的右子节点,是因为没有指向父节点的指针所以用这种方法来替代。 最重要的是这里的旋转不会破坏树的黑平衡
rotateRight
flipColors
// flip the colors of a node and its two childrenprivate void flipColors(Node h) {// h must have opposite color of its two children// assert (h != null) && (h.left != null) && (h.right != null);// assert (!isRed(h) && isRed(h.left) && isRed(h.right))// || (isRed(h) && !isRed(h.left) && !isRed(h.right));h.color = !h.color;h.left.color = !h.left.color;h.right.color = !h.right.color;}
flipColor函数不是能够随便调用的,一定要满足以下条件: x及其左右子树都不为null && 左右子树颜色相同,x颜色与他们相反。原因是保证黑平衡不被破坏。
put
插入操作就用到了上面这三板斧,我们将在插入操作中见识到这套规则的魅力
public void put(Key key, Value value) {if (key == null) throw new IllegalArgumentException("first argument to put() is null");if (value == null) {delete(key);}root = put(root, key, value);root.color = BLACK; //根节点为黑}private Node put(Node x, Key key, Value value) {if (x == null) return new Node(key, value, RED, 1);int cmp = key.compareTo(x.key);if (cmp < 0) x.left = put(x.left, key, value);else if (cmp > 0) x.right = put(x.right, key, value);else x.value = value;if (isRed(x.right) && !isRed(x.left)) x = rotateLeft(x);if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);if (isRed(x.left) && isRed(x.right)) flipColors(x);x.size = size(x.left) + size(x.right) + 1;return x;}
就是二叉查找树的插入操作+重平衡递归式的二产查找树实现
递归添加新节点,完成后在回溯过程中对树进行检查修复。修复三种情况:
1. x左黑右红,左旋;
2. x左红,左左红,右旋;
3. x左右都红,flipColor
这样就能够在递归回溯过程中保证一层层往上直到根节点都符合规则,最后要保证根节点为黑。
deleteMin
public void deleteMin() {if (isEmpty()) throw new NoSuchElementException("the tree is empty");
//为嘛要有这步操作?因为若root的左儿子与左孙子节点皆为黑,则不符合moveRedLeft调用规则。从代码意图上来说:
通过自上而下的将红色传给最小节点,那么在一开始若root.left为黑的情况下,便将root改为红,
这种改动防止了上述的错误情况,最后仍会回复root为黑if (!isRed(root.left) && !isRed(root.right))root.color = RED;root = deleteMin(root);if (!isEmpty()) root.color = BLACK; //根节点为黑}private Node deleteMin(Node x) {
//为什么返回null?它要是有右子树呢?它不可能有右子树,因为我们定义的红黑树,最小节点一定没有子节点
而moveRedLeft()中如果冲突修复操作也不会添加给x右子树if (x.left == null)return null;
//为什么判断条件是左儿左孙都为黑才调用moveRedLeft?因为该方法本意就是自上而下传递红色,从而将最
小节点变为红色,这样删除就不会破坏红黑平衡,所以当左儿子或左孙子有一个为红时,就可以跳到那里从那开
始往下传递。moveRedLeft利用三板斧来传递当前节点的红色给左子节点,过程中红黑平衡并未破坏,只是会出
现右链接为红的情况,所以需要在回溯过程中自下而上修复这一情况,即balance()
就管自己的这一亩三分地,然后从上往下递归传递红链接,若存在左儿或左孙为红,那就跳到那去传递它的红链接if (!isRed(x.left) && !isRed(x.left.left))x = moveRedLeft(x);x.left = deleteMin(x.left);
//在我们从上往下改变红黑树的过程中,会出现右链接为红还有两左红链接连一起的情况,
这就需要在递归从下往上过程中进行修复,因为三板斧所以不用担心会破坏平衡return balance(x);}//凡是调用该方法的节点一定是红,且其左右子节点皆为黑。
// 该方法目的是将当前节点的红色传递给左子节点。private Node moveRedLeft(Node x) {// assert (h != null);// assert isRed(h) && !isRed(h.left) && !isRed(h.left.left);flipColors(x);// flipColors后x为黑,左右子节点为红,若x的右节点的左节点也为红,// 则需进行平衡操作if(isRed(x.right.left)) {x.right = rotateRight(x.right);x = rotateLeft(x);flipColors(x);}return x; //注意这里返回值,若进行过平衡操作则不再是原先的节点。}// 重新平衡以x为根的子树,该方法在递归回溯过程中不断自下而上修复整个树private Node balance(Node x) {if (isRed(x.right) &&!isRed(x.left)) x = rotateLeft(x);if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);if (isRed(x.left) && isRed(x.right)) flipColors(x);x.size = size(x.left) + size(x.right) + 1;return x;}//空节点为黑private boolean isRed(Node x) {if (x == null) return false;return x.color == RED;}
如果以之前的红黑树来思考:如果x为黑,它可能有一个右红子节点,他的父亲(p)的情况,他父亲的兄弟(sib)的情况,还有sib两子节点情况,以及sib左子节点的两个子节点情况。我们需要把每种情况都考虑到,很复杂。不过一切的根源就在于x为黑,那x要是红直接删除就可以了,有没有一种办法保证x一定为红?这就是上面代码做的事,让被删除节点为红。
现在来想想怎么把它变红?
我们之前说过flipColor有它的使用前提来保证不会破坏平衡,旋转操作也不会破坏平衡,那moveRedLeft是干嘛用的?人如其名,就是要把红链接往下传的,但是又不能破坏平衡,于是用三板斧来操作,如上述代码,moveRedLeft用三板斧将当前的红链接转到了他的儿子,这样从上往下到最后最小节点一定为红无论他原来是什么(这里有个前提是空连接视为黑),从上而下改变树,这从上而下的变换结果就会让最小节点是一个红的且没有任何子节点。最后在从下往上回溯中修复树,即balance函数。
deleteMax
思想与deleteMin一致
public void deleteMax() {if (isEmpty()) throw new NoSuchElementException("tree is null");if (!isRed(root.left) && !isRed(root.right))root.color = RED;root = deleteMax(root);if (!isEmpty()) root.color = BLACK;}private Node deleteMax(Node x) {
//如果x左子节点为红,直接右旋,将红色传到右侧if (isRed(x.left))rotateRight(x);
// 最大节点最多有一个左红子节点,这种情况该左子节点会经过上面的右旋操作,使得最后最大节点一定没有
子节点if (x.right == null)return null;
// 这种情况下需要传递红色if (!isRed(x.right) && !isRed(x.right.left))x = moveRedRight(x);x.right = deleteMax(x.right);
// 回溯中自下而上修复树return balance(x);}// 该方法的调用需要满足条件:h为红,其右子树即右子树的左子树皆为黑(空节点颜色为黑)
方法目的是在上述情况下,将h的红色传递给右儿子private Node moveRedRight(Node h) {// assert (h != null);// assert isRed(h) && !isRed(h.right) && !isRed(h.right.left);flipColors(h);// 若左孙子也为红,则需要解决冲突。处理后h的右子树为黑,右孙子为红if (isRed(h.left.left)) { h = rotateRight(h); // 注意h被重新赋值flipColors(h);}return h;}
主要思想与deleteMin相同,不同在处理手段上。
这里的主要操作就是从根节点开始通过右旋或moveRedRight在从上往下递归过程中不断将红链接传下去(我们定义的红黑树它的最大节点的左右子树情况:最多只可能有一个左红节点),最后最大节点一定是个无子节点的红节点。再通过从下往上的递归过程中balance()红黑树。
delete
之所以上面分析deleteMIn与deleteMax是因为它们会在删除操作中使用到。
public void delete(Key key) {if (key == null) throw new IllegalArgumentException("argument to delete is null");if (!contains(key)) return;
// 原因上面分析过if (!isRed(root.left) && !isRed(root.right))root.color = RED;root = delete(root, key);if (!isEmpty()) root.color = BLACK;}private Node delete(Node x, Key key) {
// 在当前节点左侧就将颜色向左传递if (key.compareTo(x.key) < 0) {if (!isRed(x.left) && !isRed(x.left.left))x = moveRedLeft(x);x.left = delete(x.left, key);}
// 下面的逻辑是deleteMax()的逻辑加上相等时的操作:即用该节点的右子树的最小节点替换该节点
然后删除该右子树的最小节点else {if (isRed(x.left))x = rotateRight(x);// 若找到该节点且其右子树为空(表明该节点没有子节点,因为上面的左旋操作),删除if (key.compareTo(x.key)==0 && x.right == null) return null;if (!isRed(x.right) && !isRed(x.right.left)) x = moveRedRight(x);if (key.compareTo(x.key) == 0) {Node min = min(x.right);x.key = min.key;x.value = min.value;x.right = deleteMin(x.right);}else x.right = delete(x.right, key);}return balance(x);}public Node min(Node x) {//assert x!=nullif (x.left == null) return x;else return min(x.left);}
这里的处理方式就是deleteMin对左链接和deleteMax对右链接的处理方式的综合
删除操作最终会转化为删除右子树中最小节点操作(例外是右子树为null,那么直接返回null,因为经过从上到下的变换,右子树为null的节点一定是无子节点且颜色为红);我们从根节点开始,到这右子树的最小节点这一路径上不断变换向下传递红节点,期间并没有影响到树的原有黑平衡(除了一开始人为设置根节点为红,前面说过)。最后再从下自上修复。
总结
这种红黑树的特点:rotateLeft,rotateRight,flipColor这三个操作不会破坏树的黑平衡,于是以此为基础我们就可以来借助递归从上至下的传递红链接,即moveRedLeft,moveRedRight。最后在递归从下至上过程中修复树,即balance。它有别于之前那种红黑树,之前的是先找到目标节点然后综合考虑各种情况以达到黑平衡,是自下而上的处理。
《算法》中的红黑树实现相关推荐
- 【数据结构和算法05】 红-黑树(转发)
2019独角兽企业重金招聘Python工程师标准>>> [数据结构和算法05] 红-黑树(看完包懂~) 置顶 2016年04月13日 15:50:25 eson_15 阅读数:526 ...
- 算法导论 之 红黑树 - 添加[C语言]
作者:邹奇峰 邮箱:Qifeng.zou.job@hotmail.com 博客:http://blog.csdn.net/qifengzou 日期:2013.12.24 21:00 转载请注明来自&q ...
- 《算法导论》红黑树详解(一):概念
在学习红黑树之前,读者应先掌握二叉查找树的相关知识.学习红黑树或者二叉查找树,推荐大家看<算法导论>.<算法导论>原书第3版 高清PDF 带详细书签目录下载 密码:acis & ...
- [Java 8 HashMap 详解系列]7.HashMap 中的红黑树原理
[Java 8 HashMap 详解系列] 文章目录 1.HashMap 的存储数据结构 2.HashMap 中 Key 的 index 是怎样计算的? 3.HashMap 的 put() 方法执行原 ...
- HashMap中的红黑树
转载自:http://blog.csdn.net/u011240877/article/details/53358305 张拭心 读完本文你将了解到: 点击查看 Java 集合框架深入理解 系列 - ...
- 面试题——轻松搞定面试中的红黑树问题
版权所有,转载请注明出处,谢谢! http://blog.csdn.net/silangquan/article/details/18655795 连续两次面试都问到了红黑树,关键两次都没有答好,这次 ...
- 轻松搞定面试中的红黑树问题
连续两次面试都问到了红黑树,关键两次都没有答好,这次就完整地来学习整理一下. 没有学习过红黑树的同学请参考: <<Introduction to Algorithms>> ...
- 算法-查找(红黑树)
查找 符号表 最主要的目的是将一个键和一个值联系起来.用例能够将一个键值对插入符号表并希望在之后能够从符号表的所有键值对中按照键直接找到对应的值,即以键值对为单元的数据结构. 无序链表顺序查找 性能: ...
- 剑指XX游戏(六) - 轻松搞定面试中的红黑树问题
版权所有,转载请注明出处,谢谢! http://blog.csdn.net/silangquan/article/details/18655795 连续两次面试都问到了红黑树,关键两次都没有答好,这次 ...
最新文章
- Android自定义RulerView
- 雅思作文未来计算机的应用,9分考官级雅思大作文范文之电脑技术的忧虑
- 问题引发由于与GI相关的python脚本中的错误,Gnome终端无法启动
- 使用windows的小技巧
- 仅使用numpy从头开始实现神经网络,包括反向传播公式推导过程
- BP神经网络基础知识(前向传播和后向传播)
- [已解决] org.hibernate.AnnotationException:未为实体类指定标识符
- JavaScript杂谈(顺便也当知识积累)
- 通信原理 第7版 樊昌信版
- hook技术截取服务器信息,Windows Hook技术
- electron之坑addon
- 如何测量两组汇编指令的执行效率
- python中的坐标轴的画法
- artemis服务_Artemis
- Springboot——mybatis配置
- 科学绘图经典--Grapher
- go并发日记·避免goroutine泄漏/实现协程可控
- NekoHtml解析 html 文件
- python语言编写的modbus协议_基于Python的ModbusTCP客户端实现
- linux下查看java 占用cpu使用情况