本篇是 TreeMap 和红黑树源码分析的最后一篇了,此次会结合 TreeMap 的源码教你们红黑树删除节点的算法。红黑树的删除算法要比插入更为复杂些,可是也没必要担忧,本文会用简单明了的解释,并结合 JDK 的源码让你了解红黑树的删除算法。java
在正文开始以前,还请你们确保本身理解以前两篇文章中讲述的知识点,若是有些遗忘也不妨再次快速的复习一下。node
Java Collections Framework 源码分析(5.1 - Map, TreeMap, 红黑树)算法
Java Collections Framework 源码分析(5.2 - TreeMap, 红黑树的插入)segmentfault
Map
上的 remove
方法的做用是从容器内移除键值对,咱们先看一下 TreeMap
上的实现:微信
public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; }
经过 getEntry
方法从红黑树中获取对应的 Entry
对象,若是对应 key 的 Entry
不存在则直接返回 null。不然会执行 deleteEntry
方法,并将返回旧的值。让咱们继续往下看 deleteEntry
方法的实现。数据结构
从 deleteEntry
的代码来看主要分为两个部分:源码分析
让咱们先了解一下平衡二叉树删除节点的算法,具体算法其实很简单,须要区分两种不一样的状态。咱们先说简单的,若是当前删除的节点只有一个非空子节点,那么只须要直接删除就好了,即把本身子节点和父节点创建链接关系便可。而当本身有两个非空子节点时,则须要按照下面的顺序进行删除操做:学习
在详细解释以前,咱们先说前驱和后继的概念。由于平衡二叉树实质上是一种排序的数据结构,若是把它拉成一条直线,其实就是一个链表。而前驱的意思就是小于且最接近当前节点的节点,相应的后继就是大于且最接近的节点,具体能够看下面的图:spa
假设图中40(红色)的节点为当前节点,那么35(蓝色)节点为前驱节点,45(绿色)节点为后继节点。操作系统
了解了这些背景知识以后,能够看一下 deleteEntry
的代码了。
// If strictly internal, copy successor's element to p and then make p // point to successor. if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; } // p has 2 children
这部分代码按照咱们以前所说的算法,先执行了有两个非空子节点状况下的逻辑,同时这里选择的是使用 successor 后继节点进行替换。很容易看出在使用 successor
得到当前节点的后继节点后,将后继节点的值复制给了当前节点,而后将须要删除节点的引用指向了后继节点。
successor
方法我就不在解释了,我建议你能够去看一下,看看是否符合咱们以前的定义。相应的,在 TreeMap
的源码中还有一个 predecessor
方法是获取当前节点前驱节点,也值得看一下。
而后咱们接着往下看:
// Start fixup at replacement node, if it exists. Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; // Null out links so they are OK to use by fixAfterDeletion. p.left = p.right = p.parent = null; // Fix replacement if (p.color == BLACK) fixAfterDeletion(replacement); }
这里是进行节点删除的具体代码,此时须要删除的 P 节点有可能已是指向后继节点了,可是不管如何,删除的逻辑都是同样的,都是从新创建父节点与子节点之间的关联,并移除与要删除节点间的关联。至此第一步删除节点的操做已经完成了,接下来就是要对树进行从新平衡,以符合红黑树的要求。
从上面代码片断中能够看出,在进行节点删除以后,调用了 fixAfterDeletion
方法,还记得上一篇中有个相似的 fixAfterInsertion
吗?不难猜出,这个 fixAfterDeletion
就是在删除节点后对二叉树从新平衡的方法,让咱们先参考 wiki 上的算法定义。
相对插入而言删除的算法稍微更复杂些,须要执行 6 步操做。但也不用慌,耐心往下看(算法描述依然采用以前的 N,P 等缩写)。
S 节点,若是 S 节点颜色为红色:
N 是否为 P 的左节点
执行第 3 步操做
P,S,S.left,S.right 这些节点的颜色都为黑色:
不然执行第 4 步
P 为红色,S,S.left,S.right 都为黑色:
不然执行第 5 步
S 的颜色为黑色
N 为 P 的左节点,S.right 为黑色,S.left 为红色
N 为 P 的右节点,S.right 为红色,S.left 为黑色
执行第 6 步操做
P 改成黑色
1. N 为 P 的左节点 1. S.right 改成黑色 2. 将 P 左转 2. N 为 P 的右节点 1. S.left 改成黑色 2. 将 P 右转
上手一看算法逻辑可能很繁琐,其实仔细看一下,不少都是对称的,你只须要记住一半就好了。如今结合 TreeMap
的代码看一下:
private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { Entry<K,V> sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } ......
能够看到 fixAfterDeletetion
方法的逻辑与咱们描述的算法在顺序上稍有不一样,它在一开始的分支条件是区分当前节点是左节点仍是右节点。紧接着的:
if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); }
这部分能够看到对应咱们算法的第 2 步。须要注意的是 sib = rightOf(parentOf(x));
,那是由于发生旋转后,sib 也发生了变化,须要从新获取。接下来的:
if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); }
也能看到是对应算法的第 3 步,将 x 设为了 parent,而后从新开始 while 循环。以后的分支也均可以对应到算法的剩余步骤,我把这部分代码解读的工做流给你了,不妨本身拿笔和纸出来,将代码和我所描述的算法一一对应,看看能不能对上。
至此,TreeMap
和红黑树的全部代码都分析完了。最后这篇节点删除算法拖了好久,期间有人也私信问我,为何要学习红黑树?为何要学习数据结构?咱们工做中就是 CRUD ,什么排序,什么查找,都是用现成的呀,有问题吗?这是个颇有趣的问题,在我工做过程当中有不少人问过我相似的问题,能够把数据结构和红黑树替换成其余的许多名词,例如操做系统,编译原理,JVM 等等,等等。我想后面会花时间用一篇单独的文章来回答这个,而在这里我只想说对于这些底层知识的掌握,决定了你能力的上限,换而言之也决定了你能作什么和不能作什么 。
接下来应该是 Java Collections Framework 最后一部分了,也就是 HashMap
的源码解析,但愿你不要错过。
欢迎关注个人微信号「且把金针度与人」,获取更多高质量文章