本文转载自:http://blog.csdn.net/chenssy/article/details/26668941点击打开链接

原文出自:http://cmsblogs.com/?p=1013。尊重作者的成果,转载请注明出处!

个人站点:http://cmsblogs.com

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致还是叫做TreeMap比较好。通过这篇博文你可以获得如下知识点:

1、红黑树的基本概念。

2、红黑树增加节点、删除节点的实现过程。

3、红黑树左旋转、右旋转的复杂过程。

4、Java 中TreeMap是如何通过put、deleteEntry两个来实现红黑树增加、删除节点的。

我想通过这篇博文你对TreeMap一定有了更深的认识。好了,下面先简单普及红黑树知识。

一、红黑树简介

红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树所有的特性。同时红黑树更是一颗自平衡的排序二叉树。

我们知道一颗基本的二叉树他们都需要满足一个基本性质--即树中的任何节点的值大于它的左子节点,且小于它的右子节点。按照这个基本性质使得树的检索效率大大提高。我们知道在生成二叉树的过程是非常容易失衡的,最坏的情况就是一边倒(只有右/左子树),这样势必会导致二叉树的检索效率大大降低(O(n)),所以为了维持二叉树的平衡,大牛们提出了各种实现的算法,如:AVL,SBT,伸展树,TREAP ,红黑树等等。

平衡二叉树必须具备如下特性:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个等等子节点,其左右子树的高度都相近。

红黑树顾名思义就是节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。对于一棵有效的红黑树二叉树而言我们必须增加如下规则:

       1、每个节点都只能是红色或者黑色

       2、根节点是黑色

       3、每个叶节点(NIL节点,空节点)是黑色的。

       4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

       5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

       这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这棵树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。所以红黑树它是复杂而高效的,其检索效率O(log n)。下图为一颗典型的红黑二叉树。

       对于红黑二叉树而言它主要包括三大基本操作:左旋、右旋、着色。

左旋                                   右旋

(图片来自:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html)


       本节参考文献:http://baike.baidu.com/view/133754.htm?fr=aladdin-----百度百科

       注:由于本文主要是讲解Java中TreeMap,所以并没有对红黑树进行非常深入的了解和研究,如果诸位想对其进行更加深入的研究Lz提供几篇较好的博文:

       1、红黑树系列集锦

       2、红黑树数据结构剖析

       3、红黑树 


       二、TreeMap数据结构

       >>>>>>回归主角:TreeMap<<<<<<

       TreeMap的定义如下:

public class TreeMap<K,V>extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, java.io.Serializable

       TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口。其中AbstractMap表明TreeMap为一个Map即支持key-value的集合, NavigableMap(更多)则意味着它支持一系列的导航方法,具备针对给定搜索目标返回最接近匹配项的导航方法 。

       TreeMap中同时也包含了如下几个重要的属性:

//比较器,因为TreeMap是有序的,通过comparator接口我们可以对TreeMap的内部排序进行精密的控制private final Comparator<? super K> comparator;//TreeMap红-黑节点,为TreeMap的内部类private transient Entry<K,V> root = null;//容器大小private transient int size = 0;//TreeMap修改次数private transient int modCount = 0;//红黑树的节点颜色--红色private static final boolean RED = false;//红黑树的节点颜色--黑色private static final boolean BLACK = true;

       对于叶子节点Entry是TreeMap的内部类,它有几个重要的属性:

//键
        K key;//值
        V value;//左孩子Entry<K,V> left = null;//右孩子Entry<K,V> right = null;//父亲Entry<K,V> parent;//颜色boolean color = BLACK;

       注:前面只是开胃菜,下面是本篇博文的重中之重,在下面两节我将重点讲解treeMap的put()、delete()方法。通过这两个方法我们会了解红黑树增加、删除节点的核心算法。

       三、TreeMap put()方法

       在了解TreeMap的put()方法之前,我们先了解红黑树增加节点的算法。

       红黑树增加节点

       红黑树在新增节点过程中比较复杂,复杂归复杂它同样必须要依据上面提到的五点规范,同时由于规则1、2、3基本都会满足,下面我们主要讨论规则4、5。假设我们这里有一棵最简单的树,我们规定新增的节点为N、它的父节点为P、P的兄弟节点为U、P的父节点为G。

       对于新节点的插入有如下三个关键地方:

       1、插入新节点总是红色节点 。

       2、如果插入节点的父节点是黑色, 能维持性质 。

       3、如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质 。

       为了保证下面的阐述更加清晰和根据便于参考,我这里将红黑树的五点规定再贴一遍:

1、每个节点都只能是红色或者黑色

2、根节点是黑色

3、每个叶节点(NIL节点,空节点)是黑色的。

4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。


  •        一、为跟节点
  •        若新插入的节点N没有父节点,则直接当做根据节点插入即可,同时将颜色设置为黑色。(如图一(1))

           二、父节点为黑色

           这种情况新节点N同样是直接插入,同时颜色为红色,由于根据规则四它会存在两个黑色的叶子节点,值为null。同时由于新增节点N为红色,所以通过它的子节点的路径依然会保存着相同的黑色节点数,同样满足规则5。(如图一(2))

    (图一)

           三、若父节点P和P的兄弟节点U都为红色

           对于这种情况若直接插入肯定会出现不平衡现象。怎么处理?P、U节点变黑、G节点变红。这时由于经过节点P、U的路径都必须经过G所以在这些路径上面的黑节点数目还是相同的。但是经过上面的处理,可能G节点的父节点也是红色,这个时候我们需要将G节点当做新增节点递归处理。

           四、若父节点P为红色,叔父节点U为黑色或者缺少,且新增节点N为P节点的右孩子

           对于这种情况我们对新增节点N、P进行一次左旋转。这里所产生的结果其实并没有完成,还不是平衡的(违反了规则四),这是我们需要进行情况5的操作。

  •        五、父节点P为红色,叔父节点U为黑色或者缺少,新增节点N为父节点P左孩子

  •        这种情况有可能是由于情况四而产生的,也有可能不是。对于这种情况先已P节点为中心进行右旋转,在旋转后产生的树中,节点P是节点N、G的父节点。但是这棵树并不规范,它违反了规则4,所以我们将P、G节点的颜色进行交换,使之其满足规范。开始时所有的路径都需要经过G其他们的黑色节点数一样,但是现在所有的路径改为经过P,且P为整棵树的唯一黑色节点,所以调整后的树同样满足规范5。

  •        上面展示了红黑树新增节点的五种情况,这五种情况涵盖了所有的新增可能,不管这棵红黑树多么复杂,都可以根据这五种情况来进行生成。下面就来分析Java中的TreeMap是如何来实现红黑树的。

  •        TreeMap put()方法实现分析

           在TreeMap的put()的实现方法中主要分为两个步骤,第一:构建排序二叉树,第二:平衡二叉树。

           对于排序二叉树的创建,其添加节点的过程如下:

  •        1、以根节点为初始节点进行检索。

  •        2、与当前节点进行比对,若新增节点值较大,则以当前节点的右子节点作为新的当前节点。否则以当前节点的左子节点作为新的当前节点。

  •        3、循环递归2步骤知道检索出合适的叶子节点为止。

  •        4、将新增节点与3步骤中找到的节点进行比对,如果新增节点较大,则添加为右子节点;否则添加为左子节点。

  •        按照这个步骤我们就可以将一个新增节点添加到排序二叉树中合适的位置。如下:


  • [html] view plaincopy print?
    1. public V put(K key, V value) {
    2. //用t表示二叉树的当前节点
    3. Entry<K,V> t = root;
    4. //t为null表示一个空树,即TreeMap中没有任何元素,直接插入
    5. if (t == null) {
    6. //比较key值,个人觉得这句代码没有任何意义,空树还需要比较、排序?
    7. compare(key, key); // type (and possibly null) check
    8. //将新的key-value键值对创建为一个Entry节点,并将该节点赋予给root
    9. root = new Entry<>(key, value, null);
    10. //容器的size = 1,表示TreeMap集合中存在一个元素
    11. size = 1;
    12. //修改次数 + 1
    13. modCount++;
    14. return null;
    15. }
    16. int cmp;     //cmp表示key排序的返回结果
    17. Entry<K,V> parent;   //父节点
    18. // split comparator and comparable paths
    19. Comparator<? super K> cpr = comparator;    //指定的排序算法
    20. //如果cpr不为空,则采用既定的排序算法进行创建TreeMap集合
    21. if (cpr != null) {
    22. do {
    23. parent = t;      //parent指向上次循环后的t
    24. //比较新增节点的key和当前节点key的大小
    25. cmp = cpr.compare(key, t.key);
    26. //cmp返回值小于0,表示新增节点的key小于当前节点的key,则以当前节点的左子节点作为新的当前节点
    27. if (cmp < 0)
    28. t = t.left;
    29. //cmp返回值大于0,表示新增节点的key大于当前节点的key,则以当前节点的右子节点作为新的当前节点
    30. else if (cmp > 0)
    31. t = t.right;
    32. //cmp返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值
    33. else
    34. return t.setValue(value);
    35. } while (t != null);
    36. }
    37. //如果cpr为空,则采用默认的排序算法进行创建TreeMap集合
    38. else {
    39. if (key == null)     //key值为空抛出异常
    40. throw new NullPointerException();
    41. /* 下面处理过程和上面一样 */
    42. Comparable<? super K> k = (Comparable<? super K>) key;
    43. do {
    44. parent = t;
    45. cmp = k.compareTo(t.key);
    46. if (cmp < 0)
    47. t = t.left;
    48. else if (cmp > 0)
    49. t = t.right;
    50. else
    51. return t.setValue(value);
    52. } while (t != null);
    53. }
    54. //将新增节点当做parent的子节点
    55. Entry<K,V> e = new Entry<>(key, value, parent);
    56. //如果新增节点的key小于parent的key,则当做左子节点
    57. if (cmp < 0)
    58. parent.left = e;
    59. //如果新增节点的key大于parent的key,则当做右子节点
    60. else
    61. parent.right = e;
    62. /*
    63. *  上面已经完成了排序二叉树的的构建,将新增节点插入该树中的合适位置
    64. *  下面fixAfterInsertion()方法就是对这棵树进行调整、平衡,具体过程参考上面的五种情况
    65. */
    66. fixAfterInsertion(e);
    67. //TreeMap元素数量 + 1
    68. size++;
    69. //TreeMap容器修改次数 + 1
    70. modCount++;
    71. return null;
    72. }
  • 上面代码中do{}代码块是实现排序二叉树的核心算法,通过该算法我们可以确认新增节点在该树的正确位置。找到正确位置后将插入即可,这样做了其实还没有完成,因为我知道TreeMap的底层实现是红黑树,红黑树是一棵平衡排序二叉树,普通的排序二叉树可能会出现失衡的情况,所以下一步就是要进行调整。fixAfterInsertion(e); 调整的过程务必会涉及到红黑树的左旋、右旋、着色三个基本操作。代码如下:
  • [java] view plaincopy print?
    1. /**
    2. * 新增节点后的修复操作
    3. * x 表示新增节点
    4. */
    5. private void fixAfterInsertion(Entry<K,V> x) {
    6. x.color = RED;    //新增节点的颜色为红色
    7. //循环 直到 x不是根节点,且x的父节点不为红色
    8. while (x != null && x != root && x.parent.color == RED) {
    9. //如果X的父节点(P)是其父节点的父节点(G)的左节点
    10. if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
    11. //获取X的叔节点(U)
    12. Entry<K,V> y = rightOf(parentOf(parentOf(x)));
    13. //如果X的叔节点(U) 为红色(情况三)
    14. if (colorOf(y) == RED) {
    15. //将X的父节点(P)设置为黑色
    16. setColor(parentOf(x), BLACK);
    17. //将X的叔节点(U)设置为黑色
    18. setColor(y, BLACK);
    19. //将X的父节点的父节点(G)设置红色
    20. setColor(parentOf(parentOf(x)), RED);
    21. x = parentOf(parentOf(x));
    22. }
    23. //如果X的叔节点(U为黑色);这里会存在两种情况(情况四、情况五)
    24. else {
    25. //如果X节点为其父节点(P)的右子树,则进行左旋转(情况四)
    26. if (x == rightOf(parentOf(x))) {
    27. //将X的父节点作为X
    28. x = parentOf(x);
    29. //右旋转
    30. rotateLeft(x);
    31. }
    32. //(情况五)
    33. //将X的父节点(P)设置为黑色
    34. setColor(parentOf(x), BLACK);
    35. //将X的父节点的父节点(G)设置红色
    36. setColor(parentOf(parentOf(x)), RED);
    37. //以X的父节点的父节点(G)为中心右旋转
    38. rotateRight(parentOf(parentOf(x)));
    39. }
    40. }
    41. //如果X的父节点(P)是其父节点的父节点(G)的右节点
    42. else {
    43. //获取X的叔节点(U)
    44. Entry<K,V> y = leftOf(parentOf(parentOf(x)));
    45. //如果X的叔节点(U) 为红色(情况三)
    46. if (colorOf(y) == RED) {
    47. //将X的父节点(P)设置为黑色
    48. setColor(parentOf(x), BLACK);
    49. //将X的叔节点(U)设置为黑色
    50. setColor(y, BLACK);
    51. //将X的父节点的父节点(G)设置红色
    52. setColor(parentOf(parentOf(x)), RED);
    53. x = parentOf(parentOf(x));
    54. }
    55. //如果X的叔节点(U为黑色);这里会存在两种情况(情况四、情况五)
    56. else {
    57. //如果X节点为其父节点(P)的右子树,则进行左旋转(情况四)
    58. if (x == leftOf(parentOf(x))) {
    59. //将X的父节点作为X
    60. x = parentOf(x);
    61. //右旋转
    62. rotateRight(x);
    63. }
    64. //(情况五)
    65. //将X的父节点(P)设置为黑色
    66. setColor(parentOf(x), BLACK);
    67. //将X的父节点的父节点(G)设置红色
    68. setColor(parentOf(parentOf(x)), RED);
    69. //以X的父节点的父节点(G)为中心右旋转
    70. rotateLeft(parentOf(parentOf(x)));
    71. }
    72. }
    73. }
    74. //将根节点G强制设置为黑色
    75. root.color = BLACK;
    76. }
  •        对这段代码的研究我们发现,其处理过程完全符合红黑树新增节点的处理过程。所以在看这段代码的过程一定要对红黑树的新增节点过程有了解。在这个代码中还包含几个重要的操作。左旋(rotateLeft())、右旋(rotateRight())、着色(setColor())。

    左旋:rotateLeft()

  •        所谓左旋转,就是将新增节点(N)当做其父节点(P),将其父节点P当做新增节点(N)的左子节点。即:G.left ---> N ,N.left ---> P。

  •        红-黑二叉树删除节点,最大的麻烦是要保持 各分支黑色节点数目相等。 因为是删除,所以不用担心存在颜色冲突问题——插入才会引起颜色冲突。

           红黑树删除节点同样会分成几种情况,这里是按照待删除节点有几个儿子的情况来进行分类:

           1、没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。

           2、只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。

           3、有两个儿子。这种情况比较复杂,但还是比较简单。上面提到过用子节点C替代代替待删除节点D,然后删除子节点C即可。

           下面就论各种删除情况来进行图例讲解,但是在讲解之前请允许我再次啰嗦一句,请时刻牢记红黑树的5点规定:

           1、每个节点都只能是红色或者黑色

           2、根节点是黑色

           3、每个叶节点(NIL节点,空节点)是黑色的。

           4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

           5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

           (注:已经讲三遍了,再不记住我就怀疑你是否适合搞IT了 O(∩_∩)O~)

           诚然,既然删除节点比较复杂,那么在这里我们就约定一下规则:

           1、下面要讲解的删除节点一定是实际要删除节点的后继节点(N),如前面提到的C。

           2、下面提到的删除节点的树都是如下结构,该结构所选取的节点是待删除节点的右树的最左边子节点。这里我们规定真实删除节点为N、父节点为P、兄弟节点为W兄弟节点的两个子节点为X1、X2。如下图(2.1)。

  •        现在我们就上面提到的三种情况进行分析、处理。

           情况一、无子节点(红色节点)

           这种情况对该节点直接删除即可,不会影响树的结构。因为该节点为叶子节点它不可能存在子节点-----如子节点为黑,则违反黑节点数原则(规定5),为红,则违反“颜色”原则(规定4)。 如上图(2.2)。

           情况二、有一个子节点

           这种情况处理也是非常简单的,用子节点替代待删除节点,然后删除子节点即可。如上图(2.3)

           情况三、有两个子节点

           这种情况可能会稍微有点儿复杂。它需要找到一个替代待删除节点(N)来替代它,然后删除N即可。它主要分为四种情况。

           1、N的兄弟节点W为红色

           2、N的兄弟w是黑色的,且w的俩个孩子都是黑色的。

           3、N的兄弟w是黑色的,w的左孩子是红色,w的右孩子是黑色。

           4、N的兄弟w是黑色的,且w的右孩子时红色的。

           情况3.1、N的兄弟节点W为红色

           W为红色,那么其子节点X1、X2必定全部为黑色,父节点P也为黑色。处理策略是:改变W、P的颜色,然后进行一次左旋转。这样处理就可以使得红黑性质得以继续保持。N的新兄弟new w是旋转之前w的某个孩子,为黑色。这样处理后将情况3.1、转变为3.2、3.3、3.4中的一种。如下:

  •        情况3.2、N的兄弟w是黑色的,且w的俩个孩子都是黑色的。

  •        这种情况其父节点可红可黑,由于W为黑色,这样导致N子树相对于其兄弟W子树少一个黑色节点,这时我们可以将W置为红色。这样,N子树与W子树黑色节点一致,保持了平衡。如下

  •        将W由黑转变为红,这样就会导致新节点new N相对于它的兄弟节点会少一个黑色节点。但是如果new x为红色,我们直接将new x转变为黑色,保持整棵树的平衡。否则情况3.2 会转变为情况3.1、3.3、3.4中的一种。

  •        情况3.3、N的兄弟w是黑色的,w的左孩子是红色,w的右孩子是黑色。

  •        针对这种情况是将节点W和其左子节点进行颜色交换,然后对W进行右旋转处理。

  •        此时N的新兄弟X1(new w)是一个有红色右孩子的黑结点,于是将情况3转化为情况4.

  •        情况3.4、N的兄弟w是黑色的,且w的右孩子时红色的。

  •        交换W和父节点P的颜色,同时对P进行左旋转操作。这样就把左边缺失的黑色节点给补回来了。同时将W的右子节点X2置黑。这样左右都达到了平衡。

  •        总结

  •        个人认为这四种情况比较难理解,首先他们都不是单一的某种情况,他们之间是可以进行互转的。相对于其他的几种情况,情况3.2比较好理解,仅仅只是一个颜色的转变,通过减少右子树的一个黑色节点使之保持平衡,同时将不平衡点上移至N与W的父节点,然后进行下一轮迭代。情况3.1,是将W旋转将其转成情况2、3、4情况进行处理。而情况3.3通过转变后可以化成情况3.4来进行处理,从这里可以看出情况3.4应该最终结。情况3.4、右子节点为红色节点,那么将缺失的黑色节点交由给右子节点,通过旋转达到平衡。

  •        通过上面的分析,我们已经初步了解了红黑树的删除节点情况,相对于增加节点而言它确实是选的较为复杂。下面我将看到在Java TreeMap中是如何实现红黑树删除的。

  •        TreeMap deleteEntry()方法实现分析

  •        通过上面的分析我们确认删除节点的步骤是:找到一个替代子节点C来替代P,然后直接删除C,最后调整这棵红黑树。下面代码是寻找替代节点、删除替代节点。

  • [java] view plaincopy print?
    1. private void deleteEntry(Entry<K,V> p) {
    2. modCount++;      //修改次数 +1
    3. size--;          //元素个数 -1
    4. /*
    5. * 被删除节点的左子树和右子树都不为空,那么就用 p节点的中序后继节点代替 p 节点
    6. * successor(P)方法为寻找P的替代节点。规则是右分支最左边,或者 左分支最右边的节点
    7. * ---------------------(1)
    8. */
    9. if (p.left != null && p.right != null) {
    10. Entry<K,V> s = successor(p);
    11. p.key = s.key;
    12. p.value = s.value;
    13. p = s;
    14. }
    15. //replacement为替代节点,如果P的左子树存在那么就用左子树替代,否则用右子树替代
    16. Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    17. /*
    18. * 删除节点,分为上面提到的三种情况
    19. * -----------------------(2)
    20. */
    21. //如果替代节点不为空
    22. if (replacement != null) {
    23. replacement.parent = p.parent;
    24. /*
    25. *replacement来替代P节点
    26. */
    27. //若P没有父节点,则跟节点直接变成replacement
    28. if (p.parent == null)
    29. root = replacement;
    30. //如果P为左节点,则用replacement来替代为左节点
    31. else if (p == p.parent.left)
    32. p.parent.left  = replacement;
    33. //如果P为右节点,则用replacement来替代为右节点
    34. else
    35. p.parent.right = replacement;
    36. //同时将P节点从这棵树中剔除掉
    37. p.left = p.right = p.parent = null;
    38. /*
    39. * 若P为红色直接删除,红黑树保持平衡
    40. * 但是若P为黑色,则需要调整红黑树使其保持平衡
    41. */
    42. if (p.color == BLACK)
    43. fixAfterDeletion(replacement);
    44. } else if (p.parent == null) {     //p没有父节点,表示为P根节点,直接删除即可
    45. root = null;
    46. } else {      //P节点不存在子节点,直接删除即可
    47. if (p.color == BLACK)         //如果P节点的颜色为黑色,对红黑树进行调整
    48. fixAfterDeletion(p);
    49. //删除P节点
    50. if (p.parent != null) {
    51. if (p == p.parent.left)
    52. p.parent.left = null;
    53. else if (p == p.parent.right)
    54. p.parent.right = null;
    55. p.parent = null;
    56. }
    57. }
    58. }

           (1)除是寻找替代节点replacement,其实现方法为successor()。如下:

    [java] view plaincopy print?
    1. static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    2. if (t == null)
    3. return null;
    4. /*
    5. * 寻找右子树的最左子树
    6. */
    7. else if (t.right != null) {
    8. Entry<K,V> p = t.right;
    9. while (p.left != null)
    10. p = p.left;
    11. return p;
    12. }
    13. /*
    14. * 选择左子树的最右子树
    15. */
    16. else {
    17. Entry<K,V> p = t.parent;
    18. Entry<K,V> ch = t;
    19. while (p != null && ch == p.right) {
    20. ch = p;
    21. p = p.parent;
    22. }
    23. return p;
    24. }
    25. }

           (2)处是删除该节点过程。它主要分为上面提到的三种情况,它与上面的if…else if… else一一对应 。如下:

           1、有两个儿子。这种情况比较复杂,但还是比较简单。上面提到过用子节点C替代代替待删除节点D,然后删除子节点C即可。

           2、没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。

           3、只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。

           删除完节点后,就要根据情况来对红黑树进行复杂的调整:fixAfterDeletion()。

    [java] view plaincopy print?
    1. private void fixAfterDeletion(Entry<K,V> x) {
    2. // 删除节点需要一直迭代,知道 直到 x 不是根节点,且 x 的颜色是黑色
    3. while (x != root && colorOf(x) == BLACK) {
    4. if (x == leftOf(parentOf(x))) {      //若X节点为左节点
    5. //获取其兄弟节点
    6. Entry<K,V> sib = rightOf(parentOf(x));
    7. /*
    8. * 如果兄弟节点为红色----(情况3.1)
    9. * 策略:改变W、P的颜色,然后进行一次左旋转
    10. */
    11. if (colorOf(sib) == RED) {
    12. setColor(sib, BLACK);
    13. setColor(parentOf(x), RED);
    14. rotateLeft(parentOf(x));
    15. sib = rightOf(parentOf(x));
    16. }
    17. /*
    18. * 若兄弟节点的两个子节点都为黑色----(情况3.2)
    19. * 策略:将兄弟节点编程红色
    20. */
    21. if (colorOf(leftOf(sib))  == BLACK &&
    22. colorOf(rightOf(sib)) == BLACK) {
    23. setColor(sib, RED);
    24. x = parentOf(x);
    25. }
    26. else {
    27. /*
    28. * 如果兄弟节点只有右子树为黑色----(情况3.3)
    29. * 策略:将兄弟节点与其左子树进行颜色互换然后进行右转
    30. * 这时情况会转变为3.4
    31. */
    32. if (colorOf(rightOf(sib)) == BLACK) {
    33. setColor(leftOf(sib), BLACK);
    34. setColor(sib, RED);
    35. rotateRight(sib);
    36. sib = rightOf(parentOf(x));
    37. }
    38. /*
    39. *----情况3.4
    40. *策略:交换兄弟节点和父节点的颜色,
    41. *同时将兄弟节点右子树设置为黑色,最后左旋转
    42. */
    43. setColor(sib, colorOf(parentOf(x)));
    44. setColor(parentOf(x), BLACK);
    45. setColor(rightOf(sib), BLACK);
    46. rotateLeft(parentOf(x));
    47. x = root;
    48. }
    49. }
    50. /**
    51. * X节点为右节点与其为做节点处理过程差不多,这里就不在累述了
    52. */
    53. else {
    54. Entry<K,V> sib = leftOf(parentOf(x));
    55. if (colorOf(sib) == RED) {
    56. setColor(sib, BLACK);
    57. setColor(parentOf(x), RED);
    58. rotateRight(parentOf(x));
    59. sib = leftOf(parentOf(x));
    60. }
    61. if (colorOf(rightOf(sib)) == BLACK &&
    62. colorOf(leftOf(sib)) == BLACK) {
    63. setColor(sib, RED);
    64. x = parentOf(x);
    65. } else {
    66. if (colorOf(leftOf(sib)) == BLACK) {
    67. setColor(rightOf(sib), BLACK);
    68. setColor(sib, RED);
    69. rotateLeft(sib);
    70. sib = leftOf(parentOf(x));
    71. }
    72. setColor(sib, colorOf(parentOf(x)));
    73. setColor(parentOf(x), BLACK);
    74. setColor(leftOf(sib), BLACK);
    75. rotateRight(parentOf(x));
    76. x = root;
    77. }
    78. }
    79. }
    80. setColor(x, BLACK);
    81. }

           这是红黑树在删除节点后,对树的平衡性进行调整的过程,其实现过程与上面四种复杂的情况一一对应,所以在这个源码的时候一定要对着上面提到的四种情况看。

  •        五、写在最后

           这篇博文确实是有点儿长,在这里非常感谢各位看客能够静下心来读完,我想你通过读完这篇博文一定收获不小。同时这篇博文很大篇幅都在阐述红黑树的实现过程,对Java 的TreeMap聊的比较少,但是我认为如果理解了红黑树的实现过程,对TreeMap那是手到擒来,小菜一碟。

           同时这篇博文我写了四天,看了、参考了大量的博文。同时不免会有些地方存在借鉴之处,在这里对其表示感谢。LZ大二开始学习数据结构,自认为学的不错,现在发现数据结构我还有太多的地方需要学习了,同时也再一次体味了算法的魅力!!!!


    参考资料:

    1、红黑树数据结构剖析:http://www.cnblogs.com/fanzhidongyzby/p/3187912.html

    2、红黑二叉树详解及理论分析 :http://blog.csdn.net/kartorz/article/details/8865997

    3、教你透彻了解红黑树 blog.csdn.net/v_july_v/article/details/6105630

    4、经典算法研究系列:五、红黑树算法的实现与剖析 :http://blog.csdn.net/v_JULY_v/article/details/6109153

    5、示例,红黑树插入和删除过程:http://saturnman.blog.163.com/blog/static/557611201097221570/

    6、红黑二叉树详解及理论分析 :http://blog.csdn.net/kartorz/article/details/8865997

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    原文出自:http://cmsblogs.com/?p=1013。尊重作者的成果,转载请注明出处!

    个人站点:http://cmsblogs.com

TreeMap的讲解相关推荐

  1. java中treemap_Java中TreeMap集合讲解

    1.TreeSet介绍 TreeSet是一个有序集合,可以以任意顺序将元素插入到集合中,在对集合进行遍历的时候,每个元素将自动按照排序后的顺序呈现.底层使用的是二叉树(更具体点是红黑树)实现,对于元素 ...

  2. Java集合框架源码剖析:LinkedHashSet 和 LinkedHashMap

    Java LinkedHashMap和HashMap有什么区别和联系?为什么LinkedHashMap会有着更快的迭代速度?LinkedHashSet跟LinkedHashMap有着怎样的内在联系?本 ...

  3. Java双列集合之Map以及斗地主案列

    Map集合 知识点-- 概述 讲解 图文演示 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Java提供了专门的 ...

  4. java集合set初始化_Java集合--Set(基础)

    1.Set 上一篇,我们介绍Java中的List集合.本篇,让我们继续学习,来了解下Set集合: Set继承于Collection接口,是一个不允许出现重复元素,并且无序的集合,主要有HashSet和 ...

  5. java8 treemap 排序_Java中TreeMap按照KEY排序实现讲解

    TreeMap 是一个有序的key-value集合,它是通过红黑树实现的,该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法.继承于A ...

  6. java map 红黑树_Java集合-TreeMap和红黑树

    TreeMap是一种通过实现了红黑树数据结构的Map集合. [图片有英文注释的均摘抄于国外文章] 首先,先来看一些基础概念. 1. 二叉排序树 二叉排序树的定义和性质: (1)若左子树不空,则左子树上 ...

  7. java集合(6):TreeMap源码分析(jdk1.8)

    前言 TreeMap的基本概念: TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现.该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根 ...

  8. HashSet、TreeSet、TreeMap实现原理

    一.HashSet底层实现 HashSet实现了Set接口,不允许有重复元素,因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值 ...

  9. 第八节:详细讲解Java中的异常处理情况与I/O流的介绍以及类集合框架

    前言 大家好,给大家带来详细讲解Java中的异常处理情况与I/O流的介绍以及类集合框架的概述,希望你们喜欢 JAVA 异常 try...catch...finally结构的使用方法 class Tes ...

最新文章

  1. 2018年自媒体人的出路在哪?
  2. 直接导入用户信息到discuz ucenter.
  3. Spring Boot(五):spring data jpa的使用
  4. linux可以运行常规软件吗,Linux安装一些常规软件(浅显)
  5. Netty 采用NIO 而非AIO 的理由
  6. mac 连接hbase的图形化界面_MAC安装Hbase以及Hbase基本命令
  7. 天玑800处理器支持鸿蒙系统吗,骁龙750g和天玑800u哪个性能好 骁龙750g性能参数介绍...
  8. 5-3 面向可维护性的构造技术
  9. oracle性能优化总结1
  10. wepy一些问题和解决方案
  11. iOS 上的相机捕捉 swift
  12. Quartz的job中注入的services接口为空的解决办法
  13. 中文系统使用日文键盘-转
  14. sql server的标识种子列
  15. Keil uVison4下载、安装、使用说明
  16. matlab 排序 cell,MATLAB中对cell数组排序
  17. 智能家居生活之视频监控
  18. android 市场自动安装软件,烦人 安卓手机总是自动装应用 不怕 应用市场加把锁轻松搞定...
  19. android bugly qq,Android如何快速集成腾讯Bugly
  20. cocos 躲避球游戏(3) --群组碰撞

热门文章

  1. 前端学习(2124):双向绑定的案例图示分析
  2. 前端学习(1816):前端面试题之作用域和值类型传递的参数1
  3. oracle之基本的sql_select语句之课后练习
  4. 前端学习(1357) :模板配置
  5. mybatis学习(36):动态sql-set
  6. java学习(15):巩固练习
  7. 小程序tabbar文字在服务器上不显示,小程序的tabbar不显示
  8. 1-3docker commit定制镜像
  9. 2019长安大学ACM校赛网络同步赛 L XOR
  10. Java文件读写操作指定编码方式防乱码