深入Java集合学习系列:TreeMap实现

时间:2022-10-08 17:17:01

http://blog.csdn.net/tobeandnottobe/article/details/7232664


TreeMap使用红黑二叉树实现。

红黑二叉树:

a.        根节点是黑色的;

b.        红色节点的儿子节点是黑色的;

c.        任何一个节点到空节点的所有路径上必包含相同数据的黑色节点;

d.          叶子节点的子节点是黑色节点 


假设一颗红黑树的黑色节点个数为R,那么这棵树的最短高度为R,最大长度为2R

所以h<2R,R的最大值为log(n+1),所以红黑树的高度<2log(n+1)

先看插入:

第一步,先找到新节点在红黑二叉排序树中的位置,并插入:

[java]  view plain copy print ?
  1. int cmp;  
  2.       Entry<K,V> parent;  
  3.       // split comparator and comparable paths  
  4.       Comparator<? super K> cpr = comparator;  
  5.       if (cpr != null) {  
  6.           do {  
  7.               parent = t;  
  8.               cmp = cpr.compare(key, t.key);  
  9.               if (cmp < 0)  
  10.                   t = t.left;  
  11.               else if (cmp > 0)  
  12.                   t = t.right;  
  13.               else  
  14.                   return t.setValue(value);  
  15.           } while (t != null);  
  16.       }  
  17.       else {  
  18.           if (key == null)  
  19.               throw new NullPointerException();  
  20.           Comparable<? super K> k = (Comparable<? super K>) key;  
  21.           do {  
  22.               parent = t;  
  23.               cmp = k.compareTo(t.key);  
  24.               if (cmp < 0)  
  25.                   t = t.left;  
  26.               else if (cmp > 0)  
  27.                   t = t.right;  
  28.               else  
  29.                   return t.setValue(value);  
  30.           } while (t != null);  
  31.       }  
  32.       Entry<K,V> e = new Entry<K,V>(key, value, parent);  
  33.       if (cmp < 0)  
  34.           parent.left = e;  
  35.       else  
  36.           parent.right = e;  
上述代码无需多做分析,new TreeMap的时候可以指定具体的Comparator或者使用系统默认的Comparable接口,然后按照key从根节点开始找,直到具体可以插入的接口,插入即可;

第二步,因为二叉树中新增了一个节点,如果红黑树的平衡性受了影响,就需要进行调整:

      新插入的节点先标成红色,如果该插入的节点不是根节点而且新节点的父节点是黑色的话,那么新加节点后,没有违法上述abcd四点,红黑二叉排序树就不需要再做任何调整。所以只有当新插入节点非root节点或者其对应的父节点是红色节点的时候才需要重新调整:

[java]  view plain copy print ?
  1. while (x != null && x != root && x.parent.color == RED)   
如果新节点X对应的父节点是个左孩子节点的话,会进行以下调整:

    取到新节点父节点的兄弟节点y,如果兄弟节点是红色,那么就将x和y标记为黑色,x父节点和其兄弟节点y对应的父节点标记为红色,此时因为他们的父节点的父节点也有可能是红色的,所以需要将父节点赋值给x,重新对x向上进行调整:

[java]  view plain copy print ?
  1. Entry<K,V> y = rightOf(parentOf(parentOf(x)));  
  2.                 if (colorOf(y) == RED) {  
  3.                     setColor(parentOf(x), BLACK);  
  4.                     setColor(y, BLACK);  
  5.                     setColor(parentOf(parentOf(x)), RED);  
  6.                     x = parentOf(parentOf(x));  
  7.                 }  
否则如果兄弟节点y是黑色的:

如果此时x是左节点的话,就要将x父节点设置为黑色,x父节点的父节点设置为红色,然后对x父节点的父节点右旋即可,即:将x父节点的父节点极其孩子节点设置为x父节点的右子树,如果x是右节点的话,要先将x对应的父节点设置为x的左孩子,此时问题就和x是左节点是一样的了,只是此时的x已经是x的父节点而已,其代码如下

[java]  view plain copy print ?
  1. else {  
  2.                    if (x == rightOf(parentOf(x))) {//如果x是右节点的话,要先将x对应的父节点设置为x的左孩子  
  3.                        x = parentOf(x);  
  4.                        rotateLeft(x);  
  5.                    }  
  6.                    setColor(parentOf(x), BLACK);//x父节点设置为黑色  
  7.                    setColor(parentOf(parentOf(x)), RED);//x父节点的父节点设置为红色  
  8.                    rotateRight(parentOf(parentOf(x)));//x父节点的父节点右旋成为x父节点的右孩子  
  9.                }  

如果新节点x对应的父节点是右孩子节点的话,其调整规则和上述是雷同的,在此不再累述,见详细代码:

[java]  view plain copy print ?
  1. else {  
  2.                 Entry<K,V> y = leftOf(parentOf(parentOf(x)));  
  3.                 if (colorOf(y) == RED) {  
  4.                     setColor(parentOf(x), BLACK);  
  5.                     setColor(y, BLACK);  
  6.                     setColor(parentOf(parentOf(x)), RED);  
  7.                     x = parentOf(parentOf(x));  
  8.                 } else {  
  9.                     if (x == leftOf(parentOf(x))) {  
  10.                         x = parentOf(x);  
  11.                         rotateRight(x);  
  12.                     }  
  13.                     setColor(parentOf(x), BLACK);  
  14.                     setColor(parentOf(parentOf(x)), RED);  
  15.                     rotateLeft(parentOf(parentOf(x)));  
  16.                 }  
  17.             }  
右旋转的代码如下:

[java]  view plain copy print ?
  1. private void rotateRight(Entry<K,V> p) {  
  2.        if (p != null) {  
  3.            Entry<K,V> l = p.left;  
  4.            p.left = l.right;  
  5.            if (l.right != null) l.right.parent = p;  
  6.            l.parent = p.parent;  
  7.            if (p.parent == null)  
  8.                root = l;  
  9.            else if (p.parent.right == p)  
  10.                p.parent.right = l;  
  11.            else p.parent.left = l;  
  12.            l.right = p;  
  13.            p.parent = l;  
  14.        }  
  15.    }  

效果就是将当前节点的设置为其子节点的右节点,并将子节点节点原来的右节点设置为当前节点的左子节点;

左旋转的效果就是将当前节点设置为右子节点的左节点,右子节点原来的左子节点设置为当前节点的右子节点,代码如下:

[java]  view plain copy print ?
  1. private void rotateLeft(Entry<K,V> p) {  
  2.        if (p != null) {  
  3.            Entry<K,V> r = p.right;  
  4.            p.right = r.left;  
  5.            if (r.left != null)  
  6.                r.left.parent = p;  
  7.            r.parent = p.parent;  
  8.            if (p.parent == null)  
  9.                root = r;  
  10.            else if (p.parent.left == p)  
  11.                p.parent.left = r;  
  12.            else  
  13.                p.parent.right = r;  
  14.            r.left = p;  
  15.            p.parent = r;  
  16.        }  
  17.    }  

TreeMap删除元素:

和新增元素一样,删除元素也可能会破坏二叉树的平衡性,所以也可能需要做调整,查找过程就不说了,看看删除的时候我们如何调整来维护其平衡性的:


假如当前要被删除节点有两个孩子节点,那么只需要将其后继节点查找出来,将其内容换成后继节点的内容,然后问题就转变成删除后继节点的问题.这里是找到当前节点的右子树中最小的那个节点的内容替代之,

[java]  view plain copy print ?
  1. Entry<K,V> p = t.right;  
  2.           while (p.left != null)  
  3.               p = p.left;  
  4.           return p;  

这样删除有两个孩子节点的节点问题就转换成删除带有一个子节点或者删除叶子节点的问题。接下来一段代码是:

[java]  view plain copy print ?
  1. // Start fixup at replacement node, if it exists.  
  2.       Entry<K,V> replacement = (p.left != null ? p.left : p.right);  
假如这里的p是上述后继节点得来,因为此时p不可能有左孩子,那替代者就是p.right,此时可能为null,或者是被删除节点的子节点少于两个的情况,可能是p.left,也可能是p.right.如果不是null的话就要将后继节点指向到p的parent节点,用于删除当前节点:

[java]  view plain copy print ?
  1. replacement.parent = p.parent;  
  2.             if (p.parent == null)  
  3.                 root = replacement;  
  4.             else if (p == p.parent.left)  
  5.                 p.parent.left  = replacement;  
  6.             else  
  7.                 p.parent.right = replacement;  
此时p被删除,假如p是红色的,那就不会影响红黑树,但是如果被删除的节点是黑色的,此时因为删掉了一个黑色节点,所以需要对原来的树进行调整:

[java]  view plain copy print ?
  1. while (x != root && colorOf(x) == BLACK) {  
  2.          if (x == leftOf(parentOf(x))) {//如果x是左子节点  
  3.              Entry<K,V> sib = rightOf(parentOf(x));  
  4.   
  5.              if (colorOf(sib) == RED) {//如果其兄弟节点为红色  
  6.                  setColor(sib, BLACK);//兄弟节点设为黑色  
  7.                  setColor(parentOf(x), RED);//父节点设置为红色  
  8.                  rotateLeft(parentOf(x));//父节点左旋  
  9.                  sib = rightOf(parentOf(x));  
  10.              }  
  11.   
  12.              if (colorOf(leftOf(sib))  == BLACK &&  
  13.                  colorOf(rightOf(sib)) == BLACK) {//如果兄弟节点的两个儿子都是黑色  
  14.                  setColor(sib, RED);//兄弟节点设置为红色  
  15.                  x = parentOf(x);  
  16.              } else {  
  17.                  if (colorOf(rightOf(sib)) == BLACK) {  
  18.                      setColor(leftOf(sib), BLACK);  
  19.                      setColor(sib, RED);  
  20.                      rotateRight(sib);  
  21.                      sib = rightOf(parentOf(x));  
  22.                  }  
  23.                  setColor(sib, colorOf(parentOf(x)));  
  24.                  setColor(parentOf(x), BLACK);  
  25.                  setColor(rightOf(sib), BLACK);  
  26.                  rotateLeft(parentOf(x));  
  27.                  x = root;  
  28.              }  
  29.          }   
  30.      setColor(x, BLACK);  


另外,如果replacement值是空的,就说明被删除节点没有子节点,如果是黑色节点需要进行调整再删除,如果不是就直接删除即可

[java]  view plain copy print ?
  1. if (p.color == BLACK)  
  2.                fixAfterDeletion(p);  
  3.   
  4.            if (p.parent != null) {  
  5.                if (p == p.parent.left)  
  6.                    p.parent.left = null;  
  7.                else if (p == p.parent.right)  
  8.                    p.parent.right = null;  
  9.                p.parent = null;  
  10.            }  



因为TreeMap的查找算法是很简单的,所以在此不再累述。另外,TreeMap的一个特殊用途是它可以实现数据的区间范围查询