JDK源码研究TreeMap(红黑树)上篇

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插入实现

红黑树在插入新的结点的时候,规定新结点的颜色是红色。和普通二叉搜索树一样,首先找到插入的位置,之后红黑树需要维持自己的5个性质,因此需要进行修正。

当插入结点N的父节点是黑色结点的时候,显然根本不需要任何修正,上述5个性质仍旧成立。所以只需要考虑插入结点N的父节点同样是红色的情况,一共有6种情况,实际上是3种情况,因为有3种情况是对称的。

情况1:N是其父节点的左孩子,N的父节点是红色,父节点的兄弟结点是红色。

情况2:N是其父节点的左孩子,N的父节点是红色,父节点的兄弟结点是黑色。但父节点是爷爷结点的右孩子。

情况3:N是其父节点的左孩子,N的父节点是红色,父节点的兄弟结点是黑色。父节点是爷爷结点的左孩子。

情况4、5和6只是把N换成是其父节点的右孩子。

可以看出情况1并不关心N、父节点和爷爷结点的关系。但是情况2和3是关心的。

下面给出情况1、2和3的调整办法:

情况1,调整N的父节点和N的父节点的兄弟结点(称叔叔结点)的颜色为黑色,爷爷结点的颜色为红色,并设爷爷结点为新的N。这样N相当于向上调整了两层。之后再根据N所处位置判断是哪种情况继续调整。

(G代表爷爷结点Grandfather,P代表父节点Father,N代表新结点New)

情况2:

此时,违规的结点变成了P结点,相当于新的N,实际上这就转化为了情况3.

情况3:

下面以一个具体的例子介绍:

 

当前该红黑树满足其5个性质,现在我们需要插入一个新得结点15.

 

可以看出上述满足情况1.于是我们进行了调整,之后变成了右边的情形。此时违规的是结点10.

结点10违规,检查之后发现属于情况2.此时需要右旋转。一旦实行右旋转就转化为了情况3,情况3也意味着结束。

当然,以上这些结点可能只是整个树的一部分,但是整个过程就是这样。

代码实现:

Java代码
  1. //结点Entry结构
  2.       K key;
  3.       V value;
  4.       Entry<K,V> left = null;
  5.       Entry<K,V> right = null;
  6.       Entry<K,V> parent;
  7.       boolean color = BLACK;
  8. /put方法
  9. public V put(K key, V value) {
  10.      //根节点
  11.       Entry<K,V> t = root;
  12.       //如果根节点为空
  13.       if (t == null) {
  14.           root = new Entry<K,V>(key, value, null);
  15.           size = 1;
  16.           modCount++;
  17.           return null;
  18.       }
  19.      //以下是查找插入的位置X
  20.       int cmp;
  21.       Entry<K,V> parent;
  22.       Comparator<? super K> cpr = comparator;
  23.       if (cpr != null) {//如果用户设定比较器
  24.           do {
  25.              //记录最终位置X的父节点
  26.               parent = t;
  27.               cmp = cpr.compare(key, t.key);
  28.              //根据比较选择
  29.               if (cmp < 0)
  30.                   t = t.left;
  31.               else if (cmp > 0)
  32.                   t = t.right;
  33.               else
  34.                   return t.setValue(value);
  35.           } while (t != null);//直到叶子结点
  36.       }
  37.       else {//如果用户没有设定比较器
  38.           if (key == null)
  39.               throw new NullPointerException();
  40.           Comparable<? super K> k = (Comparable<? super K>) key;
  41.           do {
  42.               parent = t;
  43.               cmp = k.compareTo(t.key);
  44.               if (cmp < 0)
  45.                   t = t.left;
  46.               else if (cmp > 0)
  47.                   t = t.right;
  48.               else
  49.                   return t.setValue(value);
  50.           } while (t != null);
  51.       }
  52.       //初始化需要插入的结点
  53.       Entry<K,V> e = new Entry<K,V>(key, value, parent);
  54.       //设定父节点和新插入结点的关系
  55.       if (cmp < 0)
  56.           parent.left = e;
  57.       else
  58.           parent.right = e;
  59.       //修复
  60.       fixAfterInsertion(e);
  61.       size++;
  62.       modCount++;
  63.       return null;
  64.   }

实际上面除了修复函数外,和普通的二叉搜索树并无多大差别,关键在于维护红黑树的性质

Java代码
  1. private void fixAfterInsertion(Entry<K,V> x) {
  2.        //新结点默认为红色
  3.        x.color = RED;
  4.       //如果父节点为红色
  5.        while (x != null && x != root && x.parent.color == RED) {
  6.            //如果父节点是爷爷结点的左结点
  7.            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
  8.               //叔叔结点y
  9.                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
  10.               //如果叔叔结点是红色 (情况1)
  11.                if (colorOf(y) == RED) {
  12.                    setColor(parentOf(x), BLACK);
  13.                    setColor(y, BLACK);
  14.                    setColor(parentOf(parentOf(x)), RED);
  15.                   //x重新赋值
  16.                    x = parentOf(parentOf(x));
  17.                } else {//叔叔结点是黑色的
  18.                    //x是父节点的右结点 (情况2)
  19.                    if (x == rightOf(parentOf(x))) {
  20.                        x = parentOf(x);
  21.                        //左旋
  22.                        rotateLeft(x);
  23.                    }
  24.                    //此处已经转化为(情况3)
  25.                    setColor(parentOf(x), BLACK);
  26.                    setColor(parentOf(parentOf(x)), RED);
  27.                    rotateRight(parentOf(parentOf(x)));
  28.                }
  29.            } else {//如果父节点是爷爷结点的右结点  (对称)
  30.                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
  31.                if (colorOf(y) == RED) {
  32.                    setColor(parentOf(x), BLACK);
  33.                    setColor(y, BLACK);
  34.                    setColor(parentOf(parentOf(x)), RED);
  35.                    x = parentOf(parentOf(x));
  36.                } else {
  37.                    if (x == leftOf(parentOf(x))) {
  38.                        x = parentOf(x);
  39.                        rotateRight(x);
  40.                    }
  41.                    setColor(parentOf(x), BLACK);
  42.                    setColor(parentOf(parentOf(x)), RED);
  43.                    rotateLeft(parentOf(parentOf(x)));
  44.                }
  45.            }
  46.        }
  47.        root.color = BLACK;
  48.    }

实际上插入和插入后的修复相对比较简单,删除会比较复杂些,上篇完。

标签