事先声明如下代码基于JDK1.8版本
html
大部分图片引自https://www.jianshu.com/p/e136ec79235c侵删java
http://www.javashuo.com/article/p-gciojcqo-mx.htmlnode
http://www.javashuo.com/article/p-pwmenxbf-gg.html数据结构
http://www.javashuo.com/article/p-orraekjs-n.html函数
TreeMap这样一种数据结构给了咱们什么:性能
Collections.synchronizedSortedMap()
若是你有上述的需求,其实也就是快速搜索,能够考虑使用它this
TreeMap是经过什么样的数据结构来存储数据的呢?红黑树3d
那为何二叉搜索树又不行呢?code
在必定的状况下,二叉搜索树会退化成链表,失去了log(n)的搜索时间复杂度orm
以下图所示,若是按照1,2,3,4这样的顺序去构成二叉搜索树的话,它就会退化成一个链表,这样的性能是无法接收的
那么AVL树又如何呢?为何就选择红黑树呢
红黑树因为其特性,它并不追求彻底的平衡,更低的平衡要求对应着插入、删除效率的提升(可能提升并不明显甚至不如AVL,由于插入、删除的第一步就是定位),搜索的效率下降,AVL相对红黑树而言,要求彻底的平衡,天然插入、删除须要进行的操做更多,效率更低,而因为彻底的平衡,搜索的效率就更高,综合而言,选择了实现起来更为简单,各方面更为均衡的红黑树,就是如此
推论
红黑树为了维持上述5个性质,须要作出一些努力,这些努力就是旋转与着色
左旋示意图:
语言总结一下就是:
左旋,右孩子成为旋转中心的父亲节点,旋转中心成为右孩子的左孩子,此时旋转中心的右孩子空缺,右孩子的左子树失去了父亲,把右孩子的左子树置为旋转中心的右孩子
对应的TreeMap的rotateLeft方法以下
private void rotateLeft(Entry<K,V> p) { if (p != null) { Entry<K,V> r = p.right; //p的右孩子->r的左子树 p.right = r.left; if (r.left != null) //右孩子指向p.parent r.left.parent = p; r.parent = p.parent; //判断p是否为root以及p在parent中的位置 if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; //p成为r的左孩子,r成为p的父亲 r.left = p; p.parent = r; } }
右旋示意图
TreeMap中的右旋代码
private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
着色
Entry的color成员变量以下
boolean color = BLACK; //上色方法以下 private static <K,V> void setColor(Entry<K,V> p, boolean c) { if (p != null) p.color = c; }
红黑树本质仍是一颗二叉搜索树,天然与二叉搜索树的查找方式相差无几
TreeMap中的实现以下:
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") //若是有,使用自定义的排序规则 Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = 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; }
因为性质5的存在,插入节点黑色势必致使红黑树的性质5的不知足,而插入红色并不必定须要调整树的结构
4.2与4.3彻底对称式的操做,以4.2为例
操做以下
为何如此操做?
当前节点为红,父亲为红,违背性质4,修改父亲为黑,此时通过父亲节点的路径的黑色节点数量大于通过叔叔节点的路径黑色节点数目,那就再把叔叔染黑,那么此时父辈平衡,可是通过祖父的这条分支黑色节点数目多了一个,那么再把祖父染红便可,而后以祖父为新插入的节点再插入便可
效果如图:
操做以下
效果如图:
为何这么操做?
不知足性质4,修改父亲为黑色,此时父亲这边的路径黑色数量多1,那么再修改祖父为红色,但此时通过叔叔的路径黑色数量少1,此时以祖父为中心,右旋,让刚刚染色为黑色的节点成为父亲,父亲成为叔叔的父亲,便可知足平衡
操做以下:
效果如图:
为何这么操做?
就是为了变成4.2.1,而后按照4.2.1的操做进行便可
4.3是4.2的镜像版本,对称过去就行了
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check //1 若是root为null,插入并返回便可 root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //2 若是找到了,直接修改value return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") 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<>(key, value, parent); //3 直接插入节点,默认color为黑色 if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
private void fixAfterInsertion(Entry<K,V> x) { //把color着色为红 x.color = RED; while (x != null && x != root && x.parent.color == RED) { //父亲为祖父的左孩子 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //y-叔叔节点 Entry<K,V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { //叔叔为红--4.1,修改父亲和叔叔的节点为黑,祖父为红 setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); //将祖父设置为当前节点 x = parentOf(parentOf(x)); } else { //插入节点为右孩子,则先左旋一下,从4.2.2 --> 4.2.1 if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } //4.2.1,修改父亲为黑色,祖父为红色,以祖父为中心右旋 setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { //对称的,父亲为祖父的右孩子 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))); } } } //若是x为root,直接着色便可 root.color = BLACK; }
后继节点
后继节点的选取,选投影到x轴上离它最近的两个节点(确定有两个)中的任意一个均可以,这里选择第一个比删除节点大的节点,对应到树中的位置以下
若是删除节点有右子树,则对应着右子树的最左节点
若是没有则对应着第一个向右走的节点,如图所示
后继节点必定是树末尾的节点,由于任何一个左右孩子非空的节点都不可能成为最终的后继节点,它递归下去最终到树梢的位置
删除过程对应以下:
如今完成了第一步,定位真正要删除的节点,下面就是删除这个节点以后如何平衡二叉树?
只讨论1,2的状况,3与2对称操做
红色节点死有余辜!!无需处理,删除便可
操做以下
效果如图:
why
假设R删除了,那么为了达到平衡,P应该帮忙补一个黑色回来,那么出问题了,任何包含P和S的路径黑色就多出来1了,把S置为红色便可达到局部的平衡,把须要补黑色的需求向上传递
操做以下:
效果如图:
why
向2.2/2.3/2.4靠拢
操做以下:
效果如图:
why
咱们的最终目的就是为包含R的路径补一个黑色节点
修改P为黑色(不必定能保证补回一个黑色,有可能P就是黑色的)
以P为中心左旋,让S成为包含R路径的新root,那么包含S的路径黑色节点没变,包含R的路径的黑色节点因为S的存在+1
左旋以后,P与SL为父子关系,而P与SL又都为任意颜色,不必定能保证性质4,因此在左旋以前将P与S颜色互换,可是此时包含SR(红色)的右子树黑色节点少1,将SR置为黑色便可
操做以下:
效果如图:
删除节点为黑色,且为父亲节点的右孩子节点的操做与上述操做对称
public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; }
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // If strictly internal, copy successor's element to p and then make p // point to successor. //left与right都非空,即寻找后继节点 if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); //将s的值赋值给p,而后让p指向s p.key = s.key; p.value = s.value; p = s; } // p has 2 children // Start fixup at replacement node, if it exists. //left!=null则为left,right!=null则为right Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent //把replace连接到parent上 replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; // Null out links so they are OK to use by fixAfterDeletion. //for GC p.left = p.right = p.parent = null; // Fix replacement //若是删除节点为黑色,则调整 if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. //惟一节点,root节点 root = null; } else { // No children. Use self as phantom replacement and unlink. //左右孩子均为null if (p.color == BLACK) fixAfterDeletion(p); //断开p与parent的链接 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; } } }
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; } }
private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { //待删除节点为父亲节点的左孩子且为黑色 if (x == leftOf(parentOf(x))) { //sib--兄弟节点 Entry<K,V> sib = rightOf(parentOf(x)); //2.1-->2.2/2.3/2.4 if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } //2.2 设置兄弟节点为红色,x修改成parent if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { //2.3-->2.4 if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } //2.4 setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric 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; } } } //若是x为red,直接设置为x,便可弥补回丢失的黑色节点,若是是黑色,无法经过setColor来补回黑色,经过平衡的方式来补回黑色 setColor(x, BLACK); }
总结来讲,不管是插入也好,删除也罢,红黑树保持平衡的策略是自底向下追求红黑树的平衡,把矛盾交给上层的节点解决。
TreeMap红黑树的代码已经混杂在上面,再也不进行梳理
public NavigableSet<K> navigableKeySet() { KeySet<K> nks = navigableKeySet; //若是已经建立了nks,直接返回,不然new一个内部类KeySet return (nks != null) ? nks : (navigableKeySet = new KeySet<>(this)); } //成员变量navigableKeySet private transient KeySet<K> navigableKeySet;
//成员变量m private final NavigableMap<E, ?> m; //构造方法,其实就把TreeMap传入了 KeySet(NavigableMap<E,?> map) { m = map; } //简单看几个方法,发现都是调用m的方法 public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } public boolean contains(Object o) { return m.containsKey(o); } public void clear() { m.clear(); } //包括iterator也是经过m访问另外一个内部类 public Iterator<E> iterator() { if (m instanceof TreeMap) return ((TreeMap<E,?>)m).keyIterator(); else return ((TreeMap.NavigableSubMap<E,?>)m).keyIterator(); }
EntrySet、Values的实现也都相差无几,有点相似于外观类,提供受限的方法调用
看一下TreeSet的构造函数便可
public TreeSet() { this(new TreeMap<E,Object>()); } TreeSet(NavigableMap<E,Object> m) { this.m = m; } private transient NavigableMap<E,Object> m;
add()
private static final Object PRESENT = new Object(); public boolean add(E e) { return m.put(e, PRESENT)==null; }
显然TreeSet基于TreeMap实现它的基本功能,能够说它就是一个TreeSet,只不过每次add的value是一个final的obj