TreeMap
是基于红黑树结构实现的一种Map
,要分析TreeMap
的实现首先就要对红黑树有所了解。
要了解什么是红黑树,就要了解它的存在主要是为了解决什么问题,对比其他数据结构比如数组,链表,Hash表等树这种结构又有什么优点。
简单总结一下数组,链表,Hash表以及树的优缺点:
上面说的话比较啰嗦,再精炼一下:
数组查询好、插入和删除差且浪费内存;
链表插入和删除好、查询差;
Hash
表查询好、插入和删除也不错但是浪费内存。
也就是说,查询好的插入和删除就差,插入和删除好的查询就差,好不容易有一个查询、插入和删除都不错的,但是却又浪费内存。那么能不能做到查询、插入、删除效率都很高,又不浪费内存呢。答案当然是不能!但是可以做到查询、插入、删除效率比较高,又不浪费内存。这就是二叉查询树(又叫二叉排序树,又叫二叉搜索树)。但是算法复杂。
那么什么是二叉查找树呢,它又有哪些特点呢?(以下见于百度百科)
按照二叉查找树存储的数据,对元素的搜索效率是非常高的。理论上,一颗平衡的二叉查找树的任意节点平均查找效率为树的高度h
,即O(lgn)
。但是如果二叉查找树的失去平衡(元素全在一侧),搜索效率就退化为O(n)
,因此二叉查找树的平衡是搜索效率的关键所在。而红黑树就是靠红黑规则来维持二叉查找树的平衡性。
红黑树的红黑规则到底是什么呢?
但是在在添加或删除节点后,红黑树就发生了变化,可能不再满足上面的5个特性,为了保持红黑树的以上特性,就有了三个动作:左旋、右旋、着色。
二叉树基本概念理解
首先来看下TreeMap
的定义:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
可以看到TreeMap
继承了AbstractMap
抽象类,并实现NavigableMap、Cloneable、Serializable
接口。NavigableMap
接口扩展了SortedMap
,主要是提供了给定搜索目标返回最接近匹配项的导航方法
下面再看下TreeMap
的底层存储相关定义:
// 比较器 private final Comparator<? super K> comparator; // 红黑树根节点 private transient Entry<K,V> root = null; // 集合元素数量 private transient int size = 0; // "fail-fast"集合修改记录 private transient int modCount = 0;
这里的Comparator
是一个比较器,这里只要明白,一个类实现了Comparator
接口并重写其compare
方法,就能进行比较大小。Entry
是树的节点类,我们来看一下Entry
的定义:
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; // 左孩子节点 Entry<K,V> left = null; // 右孩子节点 Entry<K,V> right = null; // 父节点 Entry<K,V> parent; // 红黑树用来表示节点颜色的属性,默认为黑色 boolean color = BLACK; /** * 用key,value和父节点构造一个Entry,默认为黑色 */ Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } public K getKey() { return key ; } public V getValue() { return value ; } public V setValue(V value) { V oldValue = this.value ; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return valEquals( key,e.getKey()) && valEquals( value,e.getValue()); } public int hashCode() { int keyHash = (key ==null ? 0 : key.hashCode()); int valueHash = (value ==null ? 0 : value.hashCode()); return keyHash ^ valueHash; } public String toString() { return key + "=" + value; } }
Entry
类理解起来比较简单,主要是定义了树的孩子和父亲节点引用,和红黑颜色属性,并对equals
和hashCode
进行重写,以利于比较是否相等。
接下来看下TreeMap的构造方法:
/** * 默认构造方法,comparator为空,代表使用key的自然顺序来维持TreeMap的顺序,这里要求key必须实现Comparable接口 */ public TreeMap() { comparator = null; } /** * 用指定的比较器构造一个TreeMap */ public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } /** * 构造一个指定map的TreeMap,同样比较器comparator为空,使用key的自然顺序排序 */ public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } /** * 构造一个指定SortedMap的TreeMap,根据SortedMap的比较器来来维持TreeMap的顺序 */ public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } }
从构造方法中可以看出,要创建一个红黑树实现的TreeMap
必须要有一个用于比较大小的比较器,因为只有能够比较大小才能实现红黑树的左孩子<
树根<
右孩子的特点
将一个节点添加到红黑树中,通常需要下面几个步骤:
了解了红黑树左旋和右旋操作,以及新插入节点主要是可能会违背红黑树的规则(4)后,我们来分析下,添加新节点的过程有哪几种情况:
P
和叔叔节点U
都是红色。TreeMap
是怎么实现的呢,我们来看下:public V put(K key, V value) { // 根节点 Entry<K,V> t = root; // 如果根节点为空,则直接创建一个根节点,返回 if (t == null) { // TBD: // 5045147: (coll) Adding null to an empty TreeSet should // throw NullPointerException // // compare(key, key); // type check root = new Entry<K,V>(key, value, null); size = 1; modCount++; return null; } // 记录比较结果 int cmp; Entry<K,V> parent; // split comparator and comparable paths // 当前使用的比较器 Comparator<? super K> cpr = comparator ; // 如果比较器不为空,就是用指定的比较器来维护TreeMap的元素顺序 if (cpr != null) { // do while循环,查找key要插入的位置(也就是新节点的父节点是谁) do { // 记录上次循环的节点t parent = t; // 比较当前节点的key和新插入的key的大小 cmp = cpr.compare(key, t. key); // 新插入的key小的话,则以当前节点的左孩子节点为新的比较节点 if (cmp < 0) t = t. left; // 新插入的key大的话,则以当前节点的右孩子节点为新的比较节点 else if (cmp > 0) t = t. right; else // 如果当前节点的key和新插入的key想的的话,则覆盖map的value,返回 return t.setValue(value); // 只有当t为null,也就是没有要比较节点的时候,代表已经找到新节点要插入的位置 } while (t != null); } else { // 如果比较器为空,则使用key作为比较器进行比较 // 这里要求key不能为空,并且必须实现Comparable接口 if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; // 和上面一样,喜欢查找新节点要插入的位置 do { parent = t; cmp = k.compareTo(t. key); if (cmp < 0) t = t. left; else if (cmp > 0) t = t. right; else return t.setValue(value); } while (t != null); } // 找到新节点的父节点后,创建节点对象 Entry<K,V> e = new Entry<K,V>(key, value, parent); // 如果新节点key的值小于父节点key的值,则插在父节点的左侧 if (cmp < 0) parent. left = e; // 如果新节点key的值大于父节点key的值,则插在父节点的右侧 else parent. right = e; // 插入新的节点后,为了保持红黑树平衡,对红黑树进行调整 fixAfterInsertion(e); // map元素个数+1 size++; modCount++; return null; } /** 新增节点后对红黑树的调整方法 */ private void fixAfterInsertion(Entry<K,V> x) { // 将新插入节点的颜色设置为红色 x. color = RED; // while循环,保证新插入节点x不是根节点或者新插入节点x的父节点不是红色(这两种情况不需要调整) while (x != null && x != root && x. parent.color == RED) { // 如果新插入节点x的父节点是祖父节点的左孩子 if (parentOf(x) == leftOf(parentOf (parentOf(x)))) { // 取得新插入节点x的叔叔节点 Entry<K,V> y = rightOf(parentOf (parentOf(x))); // 如果新插入x的父节点是红色-------------------① if (colorOf(y) == RED) { // 将x的父节点设置为黑色 setColor(parentOf (x), BLACK); // 将x的叔叔节点设置为黑色 setColor(y, BLACK); // 将x的祖父节点设置为红色 setColor(parentOf (parentOf(x)), RED); // 将x指向祖父节点,如果x的祖父节点的父节点是红色,按照上面的步奏继续循环 x = parentOf(parentOf (x)); } else { // 如果新插入x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的右孩子-------------------② if (x == rightOf( parentOf(x))) { // 左旋父节点 x = parentOf(x); rotateLeft(x); } // 如果新插入x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的左孩子-------------------③ // 将x的父节点设置为黑色 setColor(parentOf (x), BLACK); // 将x的祖父节点设置为红色 setColor(parentOf (parentOf(x)), RED); // 右旋x的祖父节点 rotateRight( parentOf(parentOf (x))); } } else { // 如果新插入节点x的父节点是祖父节点的右孩子,下面的步奏和上面的相似,只不过左旋右旋的区分,不在细讲 Entry<K,V> y = leftOf(parentOf (parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf (x), BLACK); setColor(y, BLACK); setColor(parentOf (parentOf(x)), RED); x = parentOf(parentOf (x)); } else { if (x == leftOf( parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf (x), BLACK); setColor(parentOf (parentOf(x)), RED); rotateLeft( parentOf(parentOf (x))); } } } // 最后将根节点设置为黑色,不管当前是不是红色,反正根节点必须是黑色 root.color = BLACK; } /** * 对红黑树的节点(x)进行左旋转 * * 左旋示意图(对节点x进行左旋): * px px * / / * x y * / \ --(左旋)-- / \ * lx y x ry * / \ / \ * ly ry lx ly * */ private void rotateLeft(Entry<K,V> p) { if (p != null) { // 取得要选择节点p的右孩子 Entry<K,V> r = p. right; // "p"和"r的左孩子"的相互指向... // 将"r的左孩子"设为"p的右孩子" p. right = r.left ; // 如果r的左孩子非空,将"p"设为"r的左孩子的父亲" if (r.left != null) r. left.parent = p; // "p的父亲"和"r"的相互指向... // 将"p的父亲"设为"y的父亲" r. parent = p.parent ; // 如果"p的父亲"是空节点,则将r设为根节点 if (p.parent == null) root = r; // 如果p是它父节点的左孩子,则将r设为"p的父节点的左孩子" else if (p.parent. left == p) p. parent.left = r; else // 如果p是它父节点的左孩子,则将r设为"p的父节点的左孩子" p. parent.right = r; // "p"和"r"的相互指向... // 将"p"设为"r的左孩子" r. left = p; // 将"p的父节点"设为"r" p. parent = r; } } /** * 对红黑树的节点进行右旋转 * * 右旋示意图(对节点y进行右旋): * py py * / / * y x * / \ --(右旋)-- / \ * x ry lx y * / \ / \ * lx rx rx ry * */ private void rotateRight(Entry<K,V> p) { if (p != null) { // 取得要选择节点p的左孩子 Entry<K,V> l = p. left; // 将"l的右孩子"设为"p的左孩子" p. left = l.right ; // 如果"l的右孩子"不为空的话,将"p"设为"l的右孩子的父亲" if (l.right != null) l. right.parent = p; // 将"p的父亲"设为"l的父亲" l. parent = p.parent ; // 如果"p的父亲"是空节点,则将l设为根节点 if (p.parent == null) root = l; // 如果p是它父节点的右孩子,则将l设为"p的父节点的右孩子" else if (p.parent. right == p) p. parent.right = l; //如果p是它父节点的左孩子,将l设为"p的父节点的左孩子" else p.parent .left = l; // 将"p"设为"l的右孩子" l. right = p; // 将"l"设为"p父节点" p. parent = l; } }
单纯的看代码和注释,可能看不懂,所以一定要结合上面的图解,不懂了就看看图,然后动手画一下。
相比添加,红黑树的删除显得更加复杂了。看下红黑树的删除需要哪几个步奏:
删除节点的关键是:
红黑树删除节点会有哪几种情况:
public V remove(Object key) { // 根据key查找到对应的节点对象 Entry<K,V> p = getEntry(key); if (p == null) return null; // 记录key对应的value,供返回使用 V oldValue = p. value; // 删除节点 deleteEntry(p); return oldValue; } private void deleteEntry(Entry<K,V> p) { modCount++; // map容器的元素个数减一 size--; // If strictly internal, copy successor's element to p and then make p // point to successor. // 如果被删除的节点p的左孩子和右孩子都不为空,则查找其替代节点-----------这里表示要删除的节点有两个孩子(3) if (p.left != null && p. right != null) { // 查找p的替代节点 Entry<K,V> s = successor (p); p. key = s.key ; p. value = s.value ; // 将p指向替代节点,※※※※※※从此之后的p不再是原先要删除的节点p,而是替代者p(就是图解里面讲到的M) ※※※※※※ p = s; } // p has 2 children // Start fixup at replacement node, if it exists. // replacement为替代节点p的继承者(就是图解里面讲到的N),p的左孩子存在则用p的左孩子替代,否则用p的右孩子 Entry<K,V> replacement = (p. left != null ? p.left : p. right); if (replacement != null) { // 如果上面的if有两个孩子不通过--------------这里表示要删除的节点只有一个孩子(2) // Link replacement to parent // 将p的父节点拷贝给替代节点 replacement. parent = p.parent ; // 如果替代节点p的父节点为空,也就是p为跟节点,则将replacement设置为根节点 if (p.parent == null) root = replacement; // 如果替代节点p是其父节点的左孩子,则将replacement设置为其父节点的左孩子 else if (p == p.parent. left) p. parent.left = replacement; // 如果替代节点p是其父节点的左孩子,则将replacement设置为其父节点的右孩子 else p. parent.right = replacement; // Null out links so they are OK to use by fixAfterDeletion. // 将替代节点p的left、right、parent的指针都指向空,即解除前后引用关系(相当于将p从树种摘除),使得gc可以回收 p. left = p.right = p.parent = null; // Fix replacement // 如果替代节点p的颜色是黑色,则需要调整红黑树以保持其平衡 if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. // 如果要替代节点p没有父节点,代表p为根节点,直接删除即可 root = null; } else { // No children. Use self as phantom replacement and unlink. // 判断进入这里说明替代节点p没有孩子--------------这里表示没有孩子则直接删除(1) // 如果p的颜色是黑色,则调整红黑树 if (p.color == BLACK) fixAfterDeletion(p); // 下面删除替代节点p if (p.parent != null) { // 解除p的父节点对p的引用 if (p == p.parent .left) p. parent.left = null; else if (p == p.parent. right) p. parent.right = null; // 解除p对p父节点的引用 p. parent = null; } } } /** * 查找要删除节点的替代节点 */ static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) { if (t == null) return null; // 查找右子树的最左孩子 else if (t.right != null) { Entry<K,V> p = t. right; while (p.left != null) p = p. left; return p; } else { // 查找左子树的最右孩子 Entry<K,V> p = t. parent; Entry<K,V> ch = t; while (p != null && ch == p. right) { ch = p; p = p. parent; } return p; } } /** From CLR */ private void fixAfterDeletion(Entry<K,V> x) { // while循环,保证要删除节点x不是跟节点,并且是黑色(根节点和红色不需要调整) while (x != root && colorOf (x) == BLACK) { // 如果要删除节点x是其父亲的左孩子 if (x == leftOf( parentOf(x))) { // 取出要删除节点x的兄弟节点 Entry<K,V> sib = rightOf(parentOf (x)); // 如果删除节点x的兄弟节点是红色---------------------------① if (colorOf(sib) == RED) { // 将x的兄弟节点颜色设置为黑色 setColor(sib, BLACK); // 将x的父节点颜色设置为红色 setColor(parentOf (x), RED); // 左旋x的父节点 rotateLeft( parentOf(x)); // 将sib重新指向旋转后x的兄弟节点 ,进入else的步奏③ sib = rightOf(parentOf (x)); } // 如果x的兄弟节点的两个孩子都是黑色-------------------------③ if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf (sib)) == BLACK) { // 将兄弟节点的颜色设置为红色 setColor(sib, RED); // 将x的父节点指向x,如果x的父节点是黑色,需要将x的父节点整天看做一个节点继续调整-------------------------② x = parentOf(x); } else { // 如果x的兄弟节点右孩子是黑色,左孩子是红色-------------------------④ if (colorOf(rightOf(sib)) == BLACK) { // 将x的兄弟节点的左孩子设置为黑色 setColor(leftOf (sib), BLACK); // 将x的兄弟节点设置为红色 setColor(sib, RED); // 右旋x的兄弟节点 rotateRight(sib); // 将sib重新指向旋转后x的兄弟节点,进入步奏⑤ sib = rightOf(parentOf (x)); } // 如果x的兄弟节点右孩子是红色-------------------------⑤ setColor(sib, colorOf (parentOf(x))); // 将x的父节点设置为黑色 setColor(parentOf (x), BLACK); // 将x的兄弟节点的右孩子设置为黑色 setColor(rightOf (sib), BLACK); // 左旋x的父节点 rotateLeft( parentOf(x)); // 达到平衡,将x指向root,退出循环 x = root; } } else { // symmetric // 如果要删除节点x是其父亲的右孩子,和上面情况一样,这里不再细讲 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); }
删除相对来说更加复杂,还是那句话一定要对照着图解看代码,否则是读不懂的
public V get(Object key) { Entry<K,V> p = getEntry(key); return (p==null ? null : p. value); } final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) // 如果比较器为空,只是用key作为比较器查询 return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; // 取得root节点 Entry<K,V> p = root; // 从root节点开始查找,根据比较器判断是在左子树还是右子树 while (p != null) { int cmp = k.compareTo(p.key ); if (cmp < 0) p = p. left; else if (cmp > 0) p = p. right; else return p; } return null; } final Entry<K,V> getEntryUsingComparator(Object key) { K k = (K) key; Comparator<? super K> cpr = comparator ; if (cpr != null) { Entry<K,V> p = root; while (p != null) { int cmp = cpr.compare(k, p.key ); if (cmp < 0) p = p. left; else if (cmp > 0) p = p. right; else return p; } } return null; }
到此TreeMap就分析完了,在数据结构中树是比较难懂的一个,其算法也比较复杂,对于树的理解一定要多看图画图,要明白这么做是为了解决什么问题,这么做又有什么好处,当然看一遍看不懂就要多看几遍了。如果是从事底层开发,比如文件系统,存储系统,缓存等工作必须是需要的。当然就算用不到,理解了也是有益无害的。
参考资料:
http://www.cnblogs.com/daoluanxiaozi/p/3340382.html
http://www.cnblogs.com/skywang12345/p/3245399.html
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
http://www.cnblogs.com/fanzhidongyzby/p/3187912.html