TreeMap
目的:
通过对JDK源码的分析,进一步了解红黑树。
目录:
1:TreeMap介绍
2:红黑树介绍
3:红黑树插入及TreeMap插入实现
4:红黑树删除及TreeMap删除实现
1:TreeMap介绍
TreeMap和HashMap同样继承于Map接口,前者是在基于红黑树,后者是基于散列。
红黑树,是一种平衡的二叉搜索树。但是它并非是严格的平衡树,而是追求局部的平衡,从而保证树的高度在lgn和2lgn之间。
所谓平衡,实际是为了解决二叉搜索树的两种极端情况即所有结点的值本来是有序,则在建立二叉搜索树的时候,该树实际变成了一个链表,它的时间复杂度是o(n)。当通过旋转操作使之称为平衡二叉树后,树的高度是lgn,则其搜索的时间复杂度是o(lgn)。
2:红黑树介绍
红黑树:红黑树首先是一个二叉搜索树.树中每个节点的值,都大于或等于在它的左子树中的所有节点的值,并且小于或等于在它的右子树中的所有节点的值。
除此之外,红黑树还有如下性质:
1:每个结点都有颜色,不是红色就是黑色
2:根节点必须是黑色
3:所有的叶子结点都是空结点(null),颜色是黑色
4:红色结点的父结点必须是黑色
5:从任意一个结点到其子树中的叶子结点的路径中包含相同数目的黑色节点。
3:红黑树插入及TreeMap插入实现
http://wlh0706-163-com.iteye.com/blog/1851697
4:红黑树删除及TreeMap删除实现
删除结点和修复结点有如下4种情况,我们分为3类
第一类:情况1
情况:如果被删除的结点D有两个非空孩子。
策略:选择按照中序遍历时该结点下一个结点H,只把D的值改为H的值,但是并不改变D的颜色。然后将D指向H位置。(H结点是第二类中的一种情况)
第二类:情况2,3
情况:当删除的结点是第一类的时候,经过上述分析实际上是转化为了第二类中某一种情况。
策略:我们直接用D的儿子代替D,我们称之为N。但是这并没有完,直接替代有可能会违反红黑树的一些性质,需要详细讨论。
针对第二类详细讨论:
1:如果D是红色,结束。
2:如果D是黑色,此时需要根据N的父亲P、兄弟S、S的左孩子SL和右孩子SR的颜色进行判断。
下面对D是黑色,分情况讨论:
情况1:如果D没有父节点,即为root,则结束。
情况2:如果D有父节点,S的颜色为红色。
结果:
此时,原本经过P和S的路线的黑色结点个数并没有变化;原本经过P和N的路线改成经过S、P和N,虽然结点增多,但是仍旧没有改变本条线路由于删除D后减少一个黑色结点的事实,但是N的情况已经改变,可以转换为下面的情况来分析。
情况3:如果D有父节点P,P的颜色是黑色,S结点是黑色,同时它的两个儿子SL和SR也是黑色。
结果:
此时,原本经过P和S的路线的黑色结点个数由于S变成了红色减少1;原本经过P和N的路线仍旧因删除了D减少1个黑色结点并没有改变;但是这个时候经过P的两条线路的黑色结点个数相同了(当然仍旧改变不了都减少1个黑节点的事实),这个时候相当于N指向了P,需要从情况1开始进行分析。
情况4:如果D有父节点P,P的颜色是红色,S结点是黑色,S的儿子SL为黑色和SR为黑色
结果:
此时,原本经过P和S的路线的黑色结点个数并没有发生改变;当时原本经过P和N的线路由于P的颜色的改变,弥补了删除D后黑色结点个数的损失,结束!
所以说,第4种情况是继第1种情况后,又一个简单的情况。
情况5:如果D有父节点P,P的颜色不确定,S结点是黑色,S的儿子SR为黑色和SL为红色。
结果:
N和SL成为了兄弟结点,原本经过P和S的线路,虽然调换了S和SL的颜色并进行旋转,此时黑色结点并没有改变;原本经过P和N的线路并没有做出任何改变,因此此线路仍旧由于删除了D结点后黑色结点还是少1.此时我们已经将情况5做出了一点改变即:如果D有父节点P,P的颜色不确定,S结点是黑色,(到这还没有改变),S的儿子SR为红色(情况改变)。实际这就是情况6.情况6就是另外一个结束的大门.
情况6:如果D有父节点P,P的颜色不确定,S结点是黑色, S的儿子SR为红色。
结果:
原本经过P和S的线路虽然由于P和S交换,并且P叛变为另外一条线路,但是此时SR及时改变颜色,使得经过该线路的黑色结点个数没变;原本经过P和N的线路,此时多了一个结点P弥补了删除D使得黑色结点少1的情况,结束!(当然当前经过P的线路仍旧满足黑色结点个数相同如经过P到3和经过P、N到1和2处)
上述6种共有3个出口,即情况1、4和6.
上述6种情况都是假设N是P的左节点,当然如果N是右节点情况是一样的。
第三类:情况4
实际上第三类和第二类处理方法是相同的,但是处理的顺序是不同的。
由于D没有子孩子,修复的办法是:用D充当第二类中的N,带修复完毕之后,再设置父子节点的关系。
代码实现:
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 第一类:被删除节点的左子树、右子树都不为空 if (p.left != null && p.right != null) { // 用 p 节点的中序后继节点代替 p 节点 Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } //第二类:如果左节点不为空则选取左节点,否则选取右节点 Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; // 如果 p 没有父节点即root if (p.parent == null) root = replacement; // 如果 p 节点是其父节点的左子节点 else if (p == p.parent.left) p.parent.left = replacement; // 如果 p 节点是其父节点的右子节点 else p.parent.right = replacement; p.left = p.right = p.parent = null; // 修复 if (p.color == BLACK) fixAfterDeletion(replacement); } // 如果 p 节点没有父节点 else if (p.parent == null) { root = null; } else { //第三类:先根据p修复,再设置父子关系 if (p.color == BLACK) // 修复红黑树 fixAfterDeletion(p); if (p.parent != null) { //设置父子关系 if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
修复函数
// 修复红黑树 private void fixAfterDeletion(Entry<K,V> x) { //情况2-5: 直到 x 不是根节点,且 x 的颜色是黑色 while (x != root && colorOf(x) == BLACK) { // 左子节点 if (x == leftOf(parentOf(x))) { // 获取 x 节点的兄弟节点 Entry<K,V> sib = rightOf(parentOf(x)); //情况2:如果 sib 节点是红色 if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); //左旋 // 再次将 sib 设为 x 的父节点的右子节点 sib = rightOf(parentOf(x)); } //情况3和4:如果 sib 的两个子节点都是黑色 //这里有个不同的地方:针对情况4,我们分析的时候它是一个出口 //这里和情况3一样只是设置x为它的父节点,而情况4中父节点是红色 //直接跳出循环,执行最后一句<span style="font-size: 1em; line-height: 1.5;">setColor(x, BLACK);效果一样</span> if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { //情况5:如果 sib 的孩子只有右子节点是黑色 if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } //情况6:如果sib的孩子的右节点是红色 setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } //另外一种: 如果 x 是其父节点的右子节点 else { Entry<K,V> sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK); }
可以看的出来,删除结点的复杂性要远远比插入复杂,需要考虑的情况也多。
想要看明白红黑树确实需要自己动手画些图,即便是看不懂,可以按照插入的例子运行一次,就能对其过程有很大的了解,画的多了很多就自然明白,至于作者当时是如何想到此种数据结构以及进行修复,就难以用自己的思维逻辑去推到了。
TreeMap主要的步骤就在插入和删除(当然还有其他的,此处没有分析),红黑树分析到此为止。
相关推荐
JDK源码剖析之红黑树TreeMap,偶然看见,传上来分享一下
"Android-jdk源码阅读"这个主题聚焦于分析Java标准库中的`TreeMap`类,这是一个基于红黑树数据结构实现的有序映射。在这个主题中,我们将探讨`TreeMap`的内部工作原理,特别是它如何利用红黑树来实现高效的插入、...
而`TreeMap`则引入了红黑树算法,保证了插入、删除和查找的时间复杂度为O(log n),提高了性能。 在类库方面,JDK1.8新增了`java.time`包,替代了之前的`java.util.Date`和`java.util.Calendar`,提供了更加直观和...
下面是JDK源码中的一段代码,用于fixAfterInsertion操作: ``` private void fixAfterInsertion(Entry, V> x) { // 新加入红黑树的默认节点就是红色 x.color = RED; // ... } ``` 这段代码主要用于处理新加入红黑...
`TreeMap`使用红黑树保持键的排序,而`LinkedHashMap`则维护插入顺序或访问顺序。 在JDK 1.8中,集合框架引入了一些优化。例如,`HashMap`在容量达到一定阈值时会进行扩容,新的容量是原容量的1.5倍,这个设计是...
相比于AVL树,红黑树的平衡要求较为宽松,插入和删除操作时的旋转次数较少,因此在插入和删除频繁的场景下,红黑树性能更优。而AVL树的平衡更加严格,查找效率高,但在插入和删除操作时需要进行多次旋转,可能导致...
TreeMap使用红黑树,保证了键的排序。 总的来说,理解这些集合的底层实现对于优化代码性能和正确使用它们至关重要。通过对源码的深入分析,我们可以更好地掌握Java集合框架的工作原理,并根据实际需求选择最适合的...
- `HashMap`与`TreeMap`:基于散列和红黑树的两种哈希表实现,它们在性能和排序特性上各有优势。 3. **多线程编程**: - `Thread`类:代表程序中的执行线程,理解`run()`, `start()`, `sleep()`, `join()`等方法...
因为JDK源码的命名过于简略, 不容易被理解, 但是通过我所实现的这些集合就能知道JDK中大概是怎么实现的了, 如果笔记做得有错误, 大家可以给我提issuses =,= 注意点一 本仓库为Java集合类进行源代码的分析笔记, 同时...
在JDK 1.8中,`HashMap`引入了红黑树数据结构,当哈希冲突达到一定比例时,会自动转换为红黑树,提高了查找、插入和删除的性能。 `TreeSet`和`TreeMap`是基于红黑树实现的集合,它们保证了元素的排序性。`TreeSet`...
TreeSet使用红黑树数据结构,而TreeMap则使用了自平衡二叉查找树,提供了按照自然顺序或自定义比较器的排序功能。 源码分析通常包括以下几个方面: 1. **数据结构**:了解集合类底层使用的数据结构,如数组、链表...
总之,"collectionJava源码-javacollection:学习jdk1.8采集框架源代码"为我们提供了一个宝贵的资源,深入研究这些源码,不仅能增强我们对Java集合框架的理解,也能提升我们的编程技巧和解决问题的能力。
`TreeMap`则基于红黑树,元素按键的自然排序或自定义比较器排序。 在JDK 1.8中,引入了`Stream` API,它可以与集合类一起使用,提供一种声明式处理数据的方式。例如,`stream()`方法用于创建一个流,`filter...
通过本篇内容的学习,我们不仅了解了数据结构的基础知识和Java集合框架的核心概念,还探讨了如何结合JDK源码来深入理解这些知识点。掌握好这些内容对于提高编程技能、优化代码质量和提升软件工程能力具有重要意义。...
- **Map**:HashMap和TreeMap,分别基于哈希表和红黑树,存储键值对。 - **接口与实现**:如Iterable、Iterator、ListIterator等接口,以及AbstractList、AbstractSet等抽象类。 3. **多线程**: - **Thread类**...
5. **树结构**:TreeSet和TreeMap使用红黑树实现,提供O(log n)的时间复杂度,支持高效的插入、删除和查找操作。 6. **位运算**:在`java.util.BitSet`类中,数据以位的形式存储,适合处理大量布尔值或进行位操作。...
Java集合框架是Java编程语言中一个非常重要的组成部分,它提供了数据结构和算法的抽象,使得开发者...在阅读《分布式Java应用:基础与实践》或其他相关资料时,结合JDK源码进行深入学习,可以进一步提升你的编程能力。
在JDK 1.8及以后的版本中,当链表长度达到一定阈值时,链表会转换为红黑树,进一步提高查找效率,从O(n)降低到O(logn)。 面试中,面试官可能会询问如何解决哈希碰撞问题。HashMap通过两种方式来处理:一是通过优秀...
- 学习TreeSet、TreeMap的内部实现原理,包括红黑树数据结构。 - 掌握并发容器,如ConcurrentHashMap和CopyOnWriteArrayList。 这份资源中,"Java基础上.md"和"Java基础下.md"两个文件分别涵盖了以上知识点的详细...
2. TreeMap:线程非同步,基于红黑树数据结构,按键的自然顺序或比较器排序,插入和查找操作的时间复杂度为O(logn)。 3. LinkedHashMap:线程非同步,保留插入顺序或访问顺序,继承自HashMap,同时提供有序性。 4. ...