本篇要讲的就是红黑树的删除操做html
红黑树插入操做请参考 数据结构 - 红黑树(Red Black Tree)插入详解与实现(Java)node
红黑树的删除是红黑树操做中比较麻烦且比较有意思的一部分。数据结构
在此以前,重申一遍红黑树的五个定义:ide
1. 红黑树的节点不是黑色的就是红色的学习
2. 红黑树的根节点必定是黑色的this
3. 红黑树的全部叶子节点都是黑色的(注意:红黑树的叶子节点指Nil节点)spa
4. 红黑树任何路径上不容许出现相邻两个红色节点3d
5. 从红黑树的任一节点开始向下到任意叶子节点所通过的黑色节点数目相同code
接着,请你们谨记你操做的对象都是一颗标准的红黑树,因此不要脑补过多不能存在的状况,若是你考虑的状况不在本文的讨论范围以内,能够往上看看是否是你的状况违反了五条规则其中某一条,若还有疑问,欢迎留言讨论。
htm
D(Delete)表示待删除节点
P(Parent)表示待删除节点的父节点
S(Sibling)表示待删除节点的兄弟节点
U(Uncle)表示带删除节点的叔叔节点
GP(Grandparent)表示待删除节点的祖父节点
XL(Left child of X)表示节点X的左子树节点
XR(Right child of X)表示节点X的右子树节点
删除一个新的节点有如下四种状况:
1. 删除的节点是叶子节点(非Nil)
2. 删除的节点只有左子树
3. 删除的节点只有右子树
*4. 删除的节点同时拥有左子树和右子树
其实只有上面前三种状况,对于第四种状况,能够找到待删除节点的直接后继节点,用这个节点的值代替待删除节点,接着状况转变为删除这个直接后继节点,状况也变为前三种之一。
由于有不少状况是不存在,待删除节点是叶子节点(非Nil)的状况稍微复杂一些,
咱们下面先考虑待删除的节点只有左子树或只有右子树的状况。
不存在的状况包括
①
②
☂ (san)
④
⑤
⑥
请读者分析一下上面不可能状况的缘由,不复杂但必定要知道为何。两个节点的颜色红黑状况加上左右子树状况,总共八种状况,上面已经排除了六种,剩下如下两种可能的的状况。
①
DL表示DL节点本来的值
②
DR表示DR节点本来的值
这两种状况的调整操做比较简单,直接用DL/DR的元素值代替D的元素,再把DL/DR直接删去就好,操做事后不违反红黑树定义,删除结束。
删除节点的四种状况已经解决了三种,剩下最后一种了。
待删除的节点是叶子节点的状况:
由于待删除的节点有多是红色也多是黑色。
若是待删除节点是红色的,那直接删去这个节点,删除结束。
若是待删除节点是黑色的,根据父节点P和兄弟节点S的状况,可分为如下五种状况。
状况1:父节点P是红色节点
或者
这两种状况是同样的,咱们讨论第一个图就好,当把D删去后,从P的左子树下来的黑色节点数目少了一,对应的调整作法为,把P染成黑色,此时P左子树的黑色结点数目恢复,但此时右子树黑色结点数目多了一,再把S对应染成红色便可。
图例:
状况2:兄弟节点S是红色节点
或者
只能是这两种情形,作法是把P染成红色,S染成黑色,而后以P为轴作相应的旋转操做(若是D为P的左子树节点则以P为轴作左旋操做,若是D为P的右子树节点则以P为轴作右旋操做)
图例(以第一种情形为例):
到这里就把状况二变成了状况一(父节点为红色)的状况,接着按照状况一的处理方式进行操做。
状况3:结点D的远亲侄子为红色节点的状况
此时父节点P的颜色可红可黑,这种状况的调整作法是,交换P和S的颜色,而后把远侄子节点SR/SL设置为黑色,再以P为轴作相应的旋转操做(若是D为P的左子树则左旋,若是D为P的右子树则右旋)
图例(以第一种情形为例):
调整先后从P点下来的全部路径黑色节点数目没有发生变化,删除节点D后结束。(注意此处S的左子树SL能够为Nil节点或者红色节点,但依然是按照上面的规则进行调整,对结果没有影响)
状况4:节点D的近亲侄子为红色节点的状况
注意此处节点D的远侄子节点必须为Nil节点,不然就变成状况3了。这种状况的调整方式是,把S染成红色,把近侄子节点SR/SL染成黑色,而后以节点S为轴作相应的旋转操做(若是D为P的左子树则以S为轴作右旋操做,若是D为P的右子树则以S为轴作左旋操做)。
图例(以第一种情形为例)
而后就真的变成状况3了......接着按照状况3的处理方式进行处理。
状况5:节点D,P,S均为黑色节点
以第一种情形为例,这种状况删除D以后,从P的左子树下来的黑色节点数目少了一,且没有周围也没有红节点来补全这个黑节点,作法就是把D删去,而后把节点S染成红色,这样一来节点P的左右子树路径的黑色节点路径就同样了,但致使节点P整棵子树的任意路径的黑色节点数比其余路径少了一,此时咱们再从P开始(即把P当成D),但再也不删除P,向上继续调整,直到根节点(一直是状况5)或者遇到状况1~4并调整后结束。
我看过几篇文章,最后一种状况基本讲到我这里就已经结束了,因此我在这种状况上也所以多话了一点时间去理解。若此处有更详细的例子,会更能帮助理解,因此我决定举两个例子,来讲明什么叫从P节点开始向上调整,哪一种状况就是要直到根节点, 哪一种状况就是遇到状况1~4,而后调整后结束。
从节点P往上依然是全黑的状况(父节点,兄弟节点均为黑色)
从节点P往上是其余状况
这里只是举个例子,不管是变成状况1~4的哪一种,通过调整以后都无需再继续上溯,由于此时黑色节点数目已经恢复,且例子里面GP不是根节点,由于根节点不可能为红色。
下面倒序总结一下
待删除的节点是黑色叶子(非Nil)节点的状况
待删除的节点是红色叶子节点的状况
状况6 直接删除该节点
待删除的节点只拥有左子树或只拥有右子树的状况
待删除的节点同时拥有左子树和右子树的状况
状况9 找出直接后继节点并转变为状况1~8
至此,关于红黑树删除的全部状况均讨论完毕,以上的每一个字以及每一个图都是本身写本身画的,花了很多时间,但愿你们多看看,结合图理解比较形象,完全搞懂红黑树的操做,代码倒是次要的,由于同一种思路也有不一样的代码风格和实现方式。同时也但愿这篇文章能对你们有帮助。
下面是删除的代码:
总的公共方法是这样的,找到该元素对应的节点,而后删除该节点:
public boolean delete(int elem) { if (null == this.root) { return false; } else { TreeNode node = this.root; // find out the node need to be deleted while (null != node) { if (node.getElem() == elem) { deleteNode(node); return true; } else if (node.getElem() > elem) { node = node.getLeft(); } else { node = node.getRight(); } } return false; } }
删除节点的方法为私有方法,包含了同时拥有左右子树,只拥有左子树以及只拥有右子树的操做
private void deleteNode(TreeNode node) { if(null == node.getLeft() && null == node.getRight()) { if (node.getColor() == NodeColor.RED) { delete_red_leaf(node, true); } else { delete_black_leaf(node, true); } } else if (null == node.getLeft()) { // the node color must be black and the right child must be red node // replace the element of node with its right child's // cut off the the link between node and its right child node.setElem(node.getRight().getElem()); node.setRight(null); } else if (null == node.getRight()) { node.setElem(node.getLeft().getElem()); node.setLeft(null); } else { // both children are not null TreeNode next = node.getRight(); while (null != next.getLeft()) { next = next.getLeft(); } TreeUtils.swapTreeElem(node, next); deleteNode(next); } }
由大及小,删除的节点是红色叶子节点的状况,注意此处待删除的节点确定不是根节点,因此不须要考虑该节点为根节点的状况
private void delete_red_leaf(TreeNode node, boolean needDel) { TreeNode parent = node.getParent(); if (node == parent.getLeft()) { parent.setLeft(null); } else { parent.setRight(null); } }
最后就是最麻烦的删除的删除黑色叶子(非Nil)节点的状况,找出兄弟节点,找出远侄子节点,找出近侄子节点。
private void delete_black_leaf(TreeNode node, boolean needDel) { TreeNode parent = node.getParent(); if (null != parent) { boolean nodeInLeft = parent.getLeft() == node; TreeNode sibling = nodeInLeft ? parent.getRight() : parent.getLeft(); TreeNode remoteNephew = null == sibling ? null : (nodeInLeft ? sibling.getRight() : sibling.getLeft()); TreeNode nearNephew = null == sibling ? null : (nodeInLeft ? sibling.getLeft() : sibling.getRight()); if (sibling.getColor() == NodeColor.RED) { delete_sibling_red(node); } else if (null != remoteNephew && remoteNephew.getColor() == NodeColor.RED) { delete_remote_nephew_red(node); } else if (null != nearNephew && remoteNephew.getColor() == NodeColor.RED) { delete_near_nephew_red(node); } else { // the sibling is also a leaf if (parent.getColor() == NodeColor.RED) { delete_parent_red(node); } else { sibling.setColor(NodeColor.RED); delete_black_leaf(parent, false); } } } if (needDel) { if (null == parent) { this.root = null; } else if (node.getParent().getLeft() == node) { parent.setLeft(null); } else { parent.setRight(null); } } }
删除叶子节点包含了另一个参数 boolean needDel ,由于上面提到的有些状况须要继续上溯,因此有些节点不能被删除。
红黑树全部操做大功告成,但愿对你们的学习有所帮助。
PS:请问你们有能够画好看的二叉树的软件推荐吗
请尊重知识产权,引用转载请通知做者!