标签:左右 分支 start 而且 属性 new info class 转换
TreeMap实现了SotredMap接口,它是有序的集合。而且是一个红黑树结构,每个key-value都作为一个红黑树的节点。如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序,如果指定了比较器则按照比较器来进行排序。
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable {}
Serializable, Cloneable, Map<K,V>, NavigableMap<K,V>, SortedMap<K,V>
private final Comparator<? super K> comparator; //比较器,是自然排序,还是定制排序 ,使用final修饰,表明一旦赋值便不允许改变 private transient Entry<K,V> root = null; //红黑树的根节点 private transient int size = 0; //TreeMap中存放的键值对的数量 private transient int modCount = 0; //修改的次数
由于TreeMap中源码较长,接下来将分段解析部分源码。既然是红黑树存储,肯定要有数据结构(Node)节点的。看一下TreeMap中关于节点的定义部分。
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 ,parent初始化,color默认为黑色 Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } //返回key public K getKey() { return key; } //返回该节点对应的value public V getValue() { return value; } //替换节点的值,并返回旧值 public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } //重写equals()方法 public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; //两个节点的key相等,value相等,这两个节点才相等 return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); } //重写hashCode()方法 public int hashCode() { int keyHash = (key==null ? 0 : key.hashCode()); int valueHash = (value==null ? 0 : value.hashCode()); //key和vale hash值得异或运算,相同则为零,不同则为1 return keyHash ^ valueHash; } //重写toString()方法 public String toString() { return key + "=" + value; } }
//构造方法,comparator用键的顺序做比较 public TreeMap() { comparator = null; } //构造方法,提供比较器,用指定比较器排序 public TreeMap(Comparator<? super K> comparator) { his.comparator = comparator; } //将m中的元素转化daoTreeMap中,按照键的顺序做比较排序 public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } //构造方法,指定的参数为SortedMap //采用m的比较器排序 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提供了四个构造方法,实现了方法的重载。无参构造方法中比较器的值为null,采用自然排序的方法,如果指定了比较器则称之为定制排序.
对于Map来说,使用的最多的就是put()/get()/remove()等方法,下面依次进行分析
public V put(K key, V value) { Entry<K,V> t = root; //红黑树的根节点 if (t == null) { //红黑树是否为空 compare(key, key); // type (and possibly null) check //构造根节点,因为根节点没有父节点,传入null值。 root = new Entry<>(key, value, null); size = 1; //size值加1 modCount++; //改变修改的次数 return null; //返回null } int cmp; Entry<K,V> parent; //定义节点 // split comparator and comparable paths Comparator<? super K> cpr = comparator; //获取比较器 if (cpr != null) { //如果定义了比较器,采用自定义比较器进行比较 do { parent = t; //将红黑树根节点赋值给parent cmp = cpr.compare(key, t.key); //比较key, 与根节点的大小 if (cmp < 0) //如果key < t.key , 指向左子树 t = t.left; //t = t.left , t == 它的做孩子节点 else if (cmp > 0) t = t.right; //如果key > t.key , 指向它的右孩子节点 else return t.setValue(value); //如果它们相等,替换key的值 } while (t != null); //循环遍历 } else { //自然排序方式,没有指定比较器 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) // key < t.key t = t.left; //左孩子 else if (cmp > 0) // key > t.key t = t.right; //右孩子 else return t.setValue(value); //t == t.key , 替换value值 } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); //创建新节点,并制定父节点 //根据比较结果,决定新节点为父节点的左孩子或者右孩子 if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); //新插入节点后重新调整红黑树 size++; modCount++; return null; } //比较方法,如果comparator==null ,采用comparable.compartTo进行比较,否则采用指定比较器比较大小 final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); } private void fixAfterInsertion(Entry<K,V> x) { //插入的节点默认的颜色为红色 x.color = RED; // //情形1: 新节点x 是树的根节点,没有父节点不需要任何操作 //情形2: 新节点x 的父节点颜色是黑色的,也不需要任何操作 while (x != null && x != root && x.parent.color == RED) { //情形3:新节点x的父节点颜色是红色的 //判断x的节点的父节点位置,是否属于左孩子 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //获取x节点的父节点的兄弟节点,上面语句已经判断出x节点的父节点为左孩子,所以直接取右孩子 Entry<K,V> y = rightOf(parentOf(parentOf(x))); //判断是否x节点的父节点的兄弟节点为红色。 if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); // x节点的父节点设置为黑色 setColor(y, BLACK); // y节点的颜色设置为黑色 setColor(parentOf(parentOf(x)), RED); // x.parent.parent设置为红色 x = parentOf(parentOf(x)); // x == x.parent.parent ,进行遍历。 } else { //x的父节点的兄弟节点是黑色或者缺少的 if (x == rightOf(parentOf(x))) { //判断x节点是否为父节点的右孩子 x = parentOf(x); //x == 父节点 rotateLeft(x); //左旋转操作 } //x节点是其父的左孩子 setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); //上面两句将x.parent 和x.parent.parent的颜色做调换 rotateRight(parentOf(parentOf(x))); //进行右旋转 } } else { Entry<K,V> y = leftOf(parentOf(parentOf(x))); //y 是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 是其父亲的左孩子 x = parentOf(x); rotateRight(x); //以父节点为旋转点,进行右旋操作 } setColor(parentOf(x), BLACK); //父节点为设置为黑色 setColor(parentOf(parentOf(x)), RED); //祖父节点设置为红色 rotateLeft(parentOf(parentOf(x))); //以父节点为旋转点,进行左旋操作 } } } root.color = BLACK; //通过节点位置的调整,最终将红色的节点条调换到了根节点的位置,根节点重新设置为黑色 }
红黑树是一个更高效的检索二叉树,有如下特点:
上面的代码,详细的标注了每条语句的作用,但是我相信,如果你没有一定的功力,即使注释已经很详细了,你也会是一脸懵逼 ,二脸懵逼,全脑懵逼中,下面配合图片来梳理一下代码所表示的含义:
当一个默认为红色的节点插入树中,其实对应的是7中可能发生的情况,分别进行叙述:
情形1:新插入的节点时红黑树的根节点,没有父节点,无需任何的操作,直接将颜色设置为黑色就可以了
情形2:新节点的父节点颜色是黑色的,新插入的节点是红色的。也无需任何的操作。因为新节点的插入并没有影响到红黑书的特点
情形3:新节点的父节点(左孩子节点)颜色是红色的,而父节点的兄弟节点颜色也是红色的。那么情况就出现了,此时插入的节点就违反了红黑树的特点4 ,需要对红黑树进行调整。 操作看下图:将父节点和父节点的兄弟节点,都修改为红色,然后将祖父节点修改为红色,因为修改了祖父节点的颜色,祖父节点可能会发生颜色的冲突,所以将新插入的节点修改为祖父节点,在进行调整。
情形4:父节点(左孩子节点)的颜色为红色,父节点的兄弟节点的颜色为黑色或者为null,新插入的节点为父节点的右孩子节点。如下图:此时以父节点为旋转点,就新插入的节点进行左旋操作。便变成了情形5对应的情况,将执行情形5的操作。
情形5:父节点(左孩子节点)的颜色为红色,父节点的兄弟节点颜色为黑色或者null,新插入节点为父亲的左孩子节点。如下图:
情形6 和情形7的操作与情形4和情形5的操作相同,它们之前的区别是父节点为有孩子节点,再次不再赘述。
public V remove(Object key) { Entry<K,V> p = getEntry(key); //根据key查找节点,并返回该节点 if (p == null) return null; V oldValue = p.value; //获取key对应的值 deleteEntry(p); //删除节点 return oldValue; //返回key对应的值 } final Entry<K,V> getEntry(Object key) { //根据键寻找节点,有非为两种方式,如果定制了比较器,采用定制排序方式,否则使用自然排序 if (comparator != null) return getEntryUsingComparator(key); //循环遍历树,寻找和key相等的节点 if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { //循环遍历树,寻找和key相等的节点 int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } //删除节点 private void deleteEntry(Entry<K,V> p) { modCount++; //记录修改的次数 size--; //数量减1 //当前节点的两个孩子都不为空 if (p.left != null && p.right != null) { //寻找继承者,继承者为当前节点的右孩子节点或者右孩子节点的最小左孩子 Entry<K,V> s = successor(p); p.key = s.key; //key - value 的替换 ,并没有替换颜色 p.value = s.value; p = s; //指向继承者 } // p has 2 children // Start fixup at replacement node, if it exists. //开始修复树结构,继承者的左孩子不为空,返回左孩子,否则返回右孩子 //不可能存在左右两个孩子都存在的情况,successor寻找的就是最小节点,它的左孩子节点为null Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent //已经被选为继承者,当前拥有的一切放弃,所以将孩子交给爷爷抚养 replacement.parent = p.parent; //p节点没有父节点,则指向根节点 if (p.parent == null) root = replacement; //如果p为左孩子,如果p为左孩子,则将p.parent.left = p.left else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; //删除p节点到左右分支,和父节点的引用 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 = null; } else { // No children. Use self as phantom replacement and unlink. if (p.color == BLACK) fixAfterDeletion(p); 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; } } } private void fixAfterDeletion(Entry<K,V> x) { //不是根节点,颜色为黑色,调整结构 while (x != root && colorOf(x) == BLACK) { //判断x是否为左孩子 if (x == leftOf(parentOf(x))) { //x的兄弟节点 Entry<K,V> sib = rightOf(parentOf(x)); //若兄弟节点是红色 if (colorOf(sib) == RED) { setColor(sib, BLACK); //设置兄弟节点变为黑色 setColor(parentOf(x), RED); //父节点设置为红色 rotateLeft(parentOf(x)); //左旋父节点 sib = rightOf(parentOf(x)); //重新设置x的兄弟节点 } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); //兄弟节点的两个孩子都是黑色的重新设置兄弟节点的颜色,修改为红色 x = parentOf(x); //将x定位到父节点 } else { if (colorOf(rightOf(sib)) == BLACK) { //兄弟节点的右孩子是黑色的,左孩子是红色的 setColor(leftOf(sib), BLACK); //设置左孩子节点为黑色 setColor(sib, RED); //兄弟节点为红色 rotateRight(sib); //右旋 sib = rightOf(parentOf(x)); //右旋后重新设置兄弟节点 } setColor(sib, colorOf(parentOf(x))); //兄弟节点颜色设置和父节点的颜色相同 setColor(parentOf(x), BLACK); //父节点设置为黑色 setColor(rightOf(sib), BLACK); //将兄弟节点的有孩子设置为黑色 rotateLeft(parentOf(x)); //左旋 x = root; //设置x为根节点 } } 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); }
删除红黑树的操作比插入操作要稍微麻烦一点,分为两步:
下面来分情形讨论一下可能发生的情况:
情形2:被删除节点为黑色,兄弟节点为红色,如下图:
若删除上图中的x节点,将缺少一个黑节点,与红黑树的性质冲突,所以修改sib颜色为黑色,设置p节点为红色,并进行左旋操作。在进行后续的处理。
情形3:被删除节点为黑色,x节点的兄弟节点的子节点都是黑色,如下图:
x节点是黑色的,兄弟节点(黑色的)的子节点也是黑色的,p节点的颜色无法确定,有可能是红色的,也有可能是黑色的。如果是红色的直接设置为黑色即可,如果为黑色的,则需要将x定位的p节点,在进行处理。
情形4:被删除节点为黑色,x的兄弟节点的右自子节点为黑色。如下图:
情形4的调整为了转变成情形5的情况,来进行处理。
情形5:被删除节点为黑色,x的兄弟节点右子节点为红色。如下图:
sib的左子节点的颜色不确定,可能是红色也可能是黑色,但是对它并没有什么影响,因为变换前后它的上层分支的黑色节点数并没有改变。
上面的情形只是针对删除的节点是左孩子的情况,进行的分析,被删除的节点也可能是右分支。情况完全相同只不过左右顺序发生了颠倒,不再进行复述。
至此TreeMap中实现的最重要已经说完了。
下面简单说一下一些方法的作用
若要查看查看源码全文解释,请查看博客:TreeMap源码实现类中文全解析
标签:左右 分支 start 而且 属性 new info class 转换
原文地址:https://www.cnblogs.com/jing99/p/11736005.html