[关闭]
@kiraSally 2018-03-12T18:43:28.000000Z 字数 19254 阅读 3674

集合番@TreeMap一文通(1.7版)

JAVA COLLECTIONS 源码 1.7版本


1.什么是TreeMap

2.TreeMap的数据结构

2.1 类定义

  1. public class TreeMap<K,V>
  2. extends AbstractMap<K,V>
  3. implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  • 继承 AbstractMap,本质是个Map,即一个key-value集合
  • 实现 NavigableMap 接口,支持一系列的导航方法,如返回有序的key集合
  • 实现 Cloneable 接口,重写 clone(),能被克隆(浅拷贝)
  • 实现 java.io.Serializable 接口,支持序列化

2.2 重要全局变量

  1. /**
  2. * The comparator used to maintain order in this tree map, or
  3. * null if it uses the natural ordering of its keys.
  4. * 比较器 用于指定排序规则:若该排序对象为null,默认使用自然排序
  5. * @serial
  6. */
  7. private final Comparator<? super K> comparator;
  8. /**
  9. * 根节点 -即红黑树的根节点
  10. */
  11. private transient Entry<K,V> root = null;
  12. /**
  13. * The number of entries in the tree
  14. * 拥有元素数量
  15. */
  16. private transient int size = 0;
  17. /**
  18. * The number of structural modifications to the tree.
  19. */
  20. private transient int modCount = 0;
  21. // Red-black mechanics 由于非黑即红,所有选择Boolean类型来表示红黑(而不是字符串)
  22. private static final boolean RED = false;
  23. private static final boolean BLACK = true;

2.3 构造器

  1. /**
  2. * Constructs a new, empty tree map, using the natural ordering of its keys.
  3. * All keys inserted into the map must implement the {@link Comparable} interface.
  4. * Furthermore, all such keys must be <em>mutually comparable</em>: {@code k1.compareTo(k2)}
  5. * must not throw a {@code ClassCastException} for any keys {@code k1} and {@code k2} in the map.
  6. * If the user attempts to put a key into the map that violates this constraint (for example, the
  7. * user attempts to put a string key into a map whose keys are integers) ,
  8. * the {@code put(Object key, Object value)} call will throw a {@code ClassCastException}.
  9. * 1.默认构造器,默认使用自然排序;
  10. * 2.需要插入的键对象必须实现Comparable接口;
  11. * 3.同时每个键必须可比较(必须同类型),否则抛`ClassCastException`异常(类型转换错误)
  12. * eg:当插入其他类型对象时(调用put方法),将会抛`ClassCastException`异常
  13. */
  14. public TreeMap() {
  15. comparator = null;
  16. }
  17. /**
  18. * Constructs a new, empty tree map, ordered according to the given comparator.
  19. * All keys inserted into the map must be <em>mutually comparable</em> by the given comparator:
  20. * {@code comparator.compare(k1,k2)} must not throw a {@code ClassCastException} for any keys
  21. * {@code k1} and {@code k2} in the map.If the user attempts to put a key into the map that
  22. * violates this constraint, the {@code put(Object key, Object value)} call will throw a
  23. * {@code ClassCastException}.
  24. * 自定义排序规则:不负责规则将抛出`ClassCastException`异常
  25. * @param comparator the comparator that will be used to order this map.
  26. * If {@code null}, the {@linkplain Comparable natural ordering} of the keys will be used.
  27. */
  28. public TreeMap(Comparator<? super K> comparator) {
  29. this.comparator = comparator;
  30. }
  31. /**
  32. * Constructs a new tree map containing the same mappings as the given map,
  33. * ordered according to the <em>natural ordering</em> of its keys.
  34. * All keys inserted into the new map must implement the {@link Comparable} interface.
  35. * Furthermore, all such keys must be <em>mutually comparable</em>:
  36. * {@code k1.compareTo(k2)} must not throw a {@code ClassCastException} for any keys
  37. * {@code k1} and {@code k2} in the map. This method runs in n*log(n) time.
  38. * 1.批量插入Map,默认自然排序(注意此构造器不支持自定义排序规则)
  39. * 2.不负责规则将抛出`ClassCastException`异常
  40. * 3.该方法时间复杂度为O(n*log(n))
  41. * @param m the map whose mappings are to be placed in this map
  42. * @throws ClassCastException if the keys in m are not {@link Comparable},
  43. * or are not mutually comparable
  44. * @throws NullPointerException if the specified map is null
  45. */
  46. public TreeMap(Map<? extends K, ? extends V> m) {
  47. comparator = null;
  48. putAll(m);
  49. }
  50. /**
  51. * Constructs a new tree map containing the same mappings and using the same ordering
  52. * as the specified sorted map. This method runs in linear time.
  53. * 1.批量插入有序Map,排序规则与原有序Map的排序规则保持一致
  54. * 2.该方法时间复杂度呈线性增长,即O(n)
  55. * @param m the sorted map whose mappings are to be placed in this map,
  56. * and whose comparator is to be used to sort this map
  57. * @throws NullPointerException if the specified map is null
  58. */
  59. public TreeMap(SortedMap<K, ? extends V> m) {
  60. comparator = m.comparator();//使用原排序Map的排序规则
  61. try {
  62. buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
  63. } catch (java.io.IOException cannotHappen) {
  64. } catch (ClassNotFoundException cannotHappen) {
  65. }
  66. }

2.4 Entry

  1. static final class Entry<K,V> implements Map.Entry<K,V> {
  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. /**
  9. * Make a new cell with given key, value, and parent, and with
  10. * {@code null} child links, and BLACK color.
  11. * 注意:左右子节点以及红黑属性的设置不在构造器阶段完成,而是在旋转和着色阶段完成
  12. */
  13. Entry(K key, V value, Entry<K,V> parent) {
  14. this.key = key;
  15. this.value = value;
  16. this.parent = parent;
  17. }
  18. ......
  19. }

2.5 SortedMap接口实现(红框重点)

SortedMap接口实现

3.红黑树简述

3.1 二叉查找树

3.1.2 二叉查找树的性质定义

  • (1)若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • (2)若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • (3)任意节点的左、右子树也分别为二叉查找树;
  • (4)没有键值相等的节点,充分借用二分算法。

3.1.2 二叉查找树的结论

  • 好处:一棵由n个节点,随机构造的二叉查找树的高度为lgn,一般操作的执行时间为O(lgn)
  • 隐患:二叉树若退化成了一棵具有n个节点的线性链后,则此些操作最坏情况运行时间为O(n)

3.2 红黑树

3.2.1 红黑树的性质定义

  • (1)每个节点非黑即红;
  • (2)根节点是黑的;
  • (3)每个叶子节点(叶子节点即指树尾端NIL指针或NULL节点)是黑的;
  • (4)如果一个节点是红色的,则它的子节点必须是黑色的;
  • (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点(重点)。

3.2.2 红黑树的补充描述

  • 定理:一棵含有n个节点的红黑树的高度至多为2log(n+1)
  • 根据性质(5)可知,确保没有一条路径会比其他路径长出两倍 --> 红黑树是相对是接近平衡的二叉树

3.2.3 红黑树的结论

  • 红黑树,本质上来说就是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(lgn)

3.2.4 红黑树的实例

1.红黑树

3.2.5 红黑树的左旋与外旋

270024492492764.gif-243.6kB270024492492764.gif-243.6kB
读者注意 between E and S 位置的变动即可
图片来源于http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html

3.2.6 红黑树 VS B树

4.TreeMap的存储

4.1 put方法

  1. /**
  2. * Associates the specified value with the specified key in this map.
  3. * If the map previously contained a mapping for the key, the old value is replaced.
  4. * 存储key-value键值对,主要有5个步骤:
  5. * 1.若Map为空,第一次需要设置根节点
  6. * 2.选择排序比较规则,若没有自定义排序比较器,默认使用自然排序
  7. * 3.对key进行二分比较,从根节点开始,遍历左(右)子树(根据非空左子节点<父节点<非空右子节点的规律)
  8. * 4.若之前的key已经存在,当找到位置时,value值就被替换,并返回原值
  9. * 5.若是新增key,需要通过旋转(左旋或右旋)以及着色 重新维护红黑树
  10. * @param key key with which the specified value is to be associated
  11. * @param value value to be associated with the specified key
  12. * @return the previous value associated with {@code key}, or {@code null} if there was no
  13. * mapping for {@code key}. (A {@code null} return can also indicate that the map
  14. * previously associated {@code null} with {@code key}.)
  15. * value允许为null
  16. * @throws ClassCastException if the specified key cannot be compared with the keys currently in
  17. * the map 同一个Map只允许存储相同类型的键值对,否则抛`ClassCastException`类型转换错误异常
  18. * @throws NullPointerException if the specified key is null and this map uses natural ordering,
  19. * or its comparator does not permit null keys
  20. * 不满足以下两种情况将抛`NullPointerException` 空指针异常
  21. * 1.使用自然排序时,key不为null
  22. * 2.自定义排序规则时,该比较器不允许key为null
  23. */
  24. public V put(K key, V value) {
  25. Entry<K,V> t = root; //根节点
  26. if (t == null) {//若根节点不存在(即Map为空时),设置为根节点
  27. compare(key, key); // type (and possibly null) check key的类型校验
  28. //这里隐藏了一点:根节点是黑色属性(性质2)是由构造器默认实现的
  29. root = new Entry<>(key, value, null);//设置为根节点,注意parent=null
  30. size = 1;
  31. modCount++;//新增属于结构性变动,modCount计数+1
  32. return null;//默认返回null
  33. }
  34. int cmp;
  35. Entry<K,V> parent;
  36. // split comparator and comparable paths
  37. Comparator<? super K> cpr = comparator;//获取比较器
  38. if (cpr != null) {//当拥有自定义比较器时,使用自定义比较器
  39. //根据二分法遍历树
  40. do {
  41. parent = t;//获取父节点(从根节点开始)
  42. cmp = cpr.compare(key, t.key);//使用自定义比较规则获取比较结果
  43. //二分法查找到当前key所在位置
  44. if (cmp < 0) //当前父节点的key小于当前key,往左子树方向继续向下找
  45. t = t.left;
  46. else if (cmp > 0) //当前父节点的key小于当前key,往右子树方向继续向下找
  47. t = t.right;
  48. else
  49. return t.setValue(value); //当前父节点的key==当前key,说明就是当前位置,值覆盖
  50. } while (t != null);
  51. }
  52. else {//当没有自定义比较器时,默认使用自然排序,此时key不允许为null
  53. if (key == null)
  54. throw new NullPointerException();//当key为null时,抛空指针异常
  55. Comparable<? super K> k = (Comparable<? super K>) key;
  56. //查找规则与 自定义比较器基本一致
  57. do {
  58. parent = t;
  59. cmp = k.compareTo(t.key);//唯一区别是使用了自定义的比较规则,而不是自然排序
  60. if (cmp < 0)
  61. t = t.left;
  62. else if (cmp > 0)
  63. t = t.right;
  64. else
  65. return t.setValue(value);//若key已存在,返回原值
  66. } while (t != null);
  67. }
  68. //若是新增元素,需要先查找到指定位置,并通过旋转和重新着色维护红黑树性质
  69. Entry<K,V> e = new Entry<>(key, value, parent);
  70. //将新增节点当做parent的子节点
  71. if (cmp < 0)
  72. parent.left = e;//当前父节点的key小于当前key,当前key设置为左子节点
  73. else
  74. parent.right = e;//当前父节点的key大于等于当前key,当前key设置为右子节点(注意:包含等于)
  75. fixAfterInsertion(e);//通过旋转和重新着色维护红黑树性质
  76. size++;
  77. modCount++;
  78. return null;//新增元素默认返回null
  79. }

4.2 compare方法

  1. /**
  2. * Compares two keys using the correct comparison method for this TreeMap.
  3. * 比较两个key的类型是否一致:TreeMap只允许相同类型的key存在
  4. */
  5. final int compare(Object k1, Object k2) {
  6. //当比较器为null时,key必须实现Comparable接口,且两者自然排序必须可比较
  7. //当有自定义比较器时,key必须通过comparator可比较
  8. return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
  9. : comparator.compare((K)k1, (K)k2);
  10. }

QQ截图20170728115226.png-20.3kB

4.3 插入情况

  • 情况1:插入的是根节点,由于原树是空树
    • 策略:直接把此节点涂为黑色
  • 情况2:如果插入的节点的父节点是黑色
    • 策略:由于此不会违反性质2和性质4,红黑树没有被破坏,所以此时什么也不做
  • 情况3:有两个子节点
    • 情况3.1:当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色
      • 策略:将父节点和叔叔节点涂黑,祖父节点涂红同时指向当前节点
    • 情况3.2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子
      • 策略:当前节点的父节点做为新的当前节点,以新当前节点为支点重新旋转
    • 情况3.3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子
      • 策略:父节点变为黑色,祖父节点变为红色,以祖父节点为支点重新旋转

4.4 fixAfterInsertion方法

  1. /**
  2. * 对于新节点的插入有如下三个关键地方:
  3. * 1、插入新节点总是红色节点;
  4. * 2、如果插入节点的父节点是黑色, 能保持性质(性质4);
  5. * 3、如果插入节点的父节点是红色, 性质被破坏,通过旋转和重新着色维护红黑树性质。
  6. * 4、换言之,性质1-3都好实现,复杂的是要实现性质4-5(删除操作一样)
  7. */
  8. private void fixAfterInsertion(Entry<K,V> x) {
  9. x.color = RED;//新增元素默认红色,后面根据所在位置重新着色(比较有趣的是Entry构造器默认是BLACK)
  10. //循环 直到 x不是根节点,且x的父节点不为红色
  11. while (x != null && x != root && x.parent.color == RED) {
  12. if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//父节点为祖父节点左子
  13. Entry<K,V> y = rightOf(parentOf(parentOf(x)));//叔叔节点(右节点)
  14. /*
  15. * 情况3.1:父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色
  16. * 策略:将父节点和叔叔节点涂黑,祖父节点涂红同时指向当前节点
  17. */
  18. if (colorOf(y) == RED) {
  19. setColor(parentOf(x), BLACK);
  20. setColor(y, BLACK);
  21. setColor(parentOf(parentOf(x)), RED);
  22. x = parentOf(parentOf(x));
  23. } else {
  24. /*
  25. * 情况3.2:父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子
  26. * 策略:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋
  27. * 这时情况会转变为3.2(当前节点是其父节点的左子)
  28. */
  29. if (x == rightOf(parentOf(x))) {
  30. x = parentOf(x);
  31. rotateLeft(x);
  32. }
  33. /*
  34. * 情况3.3:父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子
  35. * 策略:将父节点和叔叔节点涂黑,祖父节点涂红,以祖父节点右旋
  36. */
  37. setColor(parentOf(x), BLACK);
  38. setColor(parentOf(parentOf(x)), RED);
  39. rotateRight(parentOf(parentOf(x)));
  40. }
  41. } else {
  42. //父节点为祖父节点右子 操作从左换成右
  43. Entry<K,V> y = leftOf(parentOf(parentOf(x)));
  44. if (colorOf(y) == RED) {
  45. setColor(parentOf(x), BLACK);
  46. setColor(y, BLACK);
  47. setColor(parentOf(parentOf(x)), RED);
  48. x = parentOf(parentOf(x));
  49. } else {
  50. if (x == leftOf(parentOf(x))) {
  51. x = parentOf(x);
  52. rotateRight(x);
  53. }
  54. setColor(parentOf(x), BLACK);
  55. setColor(parentOf(parentOf(x)), RED);
  56. rotateLeft(parentOf(parentOf(x)));
  57. }
  58. }
  59. }
  60. root.color = BLACK;//红黑树性质(2):根节点是黑色
  61. }

5.TreeMap的移除

5.1 remove方法

  1. /**
  2. * Removes the mapping for this key from this TreeMap if present.
  3. * 移除key-value键值对,主要有3个步骤:
  4. * 1.选择排序比较器,若没有自定义排序比较器,默认使用自然排序(此时key不能为null)
  5. * 2.根据比较器对key进行二分比较,从根节点开始,遍历左(右)子树,直到找到该key所在节点
  6. * 3.移除成功,返回原值,并通过旋转(左旋或右旋)以及着色 重新维护红黑树
  7. * @param key key for which mapping should be removed
  8. * @return the previous value associated with {@code key}, or {@code null} if there was no
  9. * mapping for {@code key}. (A {@code null} return can also indicate that the map
  10. * previously associated {@code null} with {@code key}.)
  11. * 若key存在,返回原值;若key不存在,返回null
  12. * @throws ClassCastException if the specified key cannot be compared with the keys currently in
  13. * the map 同一个Map只允许存储相同类型的键值对,否则抛`ClassCastException`类型转换错误异常
  14. * @throws NullPointerException if the specified key is null and this map uses natural ordering
  15. * , or its comparator does not permit null keys
  16. * 不满足以下两种情况将抛`NullPointerException` 空指针异常
  17. * 1.使用自然排序时,key不为null
  18. * 2.自定义排序规则时,该比较器不允许key为null
  19. */
  20. public V remove(Object key) {
  21. Entry<K,V> p = getEntry(key);//查找节点元素
  22. if (p == null)
  23. return null;
  24. V oldValue = p.value;
  25. deleteEntry(p);//删除节点元素
  26. return oldValue;//返回原值
  27. }

5.2 getEntry方法

  1. /**
  2. * Returns this map's entry for the given key, or {@code null} if the map
  3. * does not contain an entry for the key.
  4. * 该值存在,返回原值;否则返回null
  5. * @return this map's entry for the given key, or {@code null} if the map
  6. * does not contain an entry for the key
  7. * @throws ClassCastException if the specified key cannot be compared
  8. * with the keys currently in the map
  9. * @throws NullPointerException if the specified key is null and this map uses natural ordering,
  10. * or its comparator does not permit null keys
  11. */
  12. final Entry<K,V> getEntry(Object key) {
  13. // Offload comparator-based version for sake of performance
  14. if (comparator != null)//若有自定义选择器,使用自定义选择器查找;否则按照自然排序查找
  15. return getEntryUsingComparator(key);
  16. if (key == null) //自然排序情况下,不允许key为null
  17. throw new NullPointerException();
  18. Comparable<? super K> k = (Comparable<? super K>) key;
  19. Entry<K,V> p = root;//从根节点开始遍历左右子树,直到找到该元素
  20. while (p != null) {
  21. int cmp = k.compareTo(p.key);
  22. if (cmp < 0)
  23. p = p.left;
  24. else if (cmp > 0)
  25. p = p.right;
  26. else
  27. return p;
  28. }
  29. return null;
  30. }

5.3 getEntryUsingComparator方法

  1. /**
  2. * Version of getEntry using comparator. Split off from getEntry for performance.
  3. * (This is not worth doing for most methods,that are less dependent on comparator performance,
  4. * but is worthwhile here.)
  5. * 使用自定义排序比较规则进行比较:对于大多数方法而言,这并不适用,因为其性能会受到比较器效率的影响
  6. * 当然,如果自定义比较器能够高效的比较的话,是非常有价值的
  7. */
  8. final Entry<K,V> getEntryUsingComparator(Object key) {
  9. K k = (K) key;
  10. Comparator<? super K> cpr = comparator;
  11. if (cpr != null) {
  12. Entry<K,V> p = root;//从根节点开始遍历左右子树,直到找到该元素
  13. while (p != null) {
  14. int cmp = cpr.compare(k, p.key);//自定义比较
  15. if (cmp < 0)
  16. p = p.left;
  17. else if (cmp > 0)
  18. p = p.right;
  19. else
  20. return p;
  21. }
  22. }
  23. return null;
  24. }

5.4 deleteEntry方法

  1. /**
  2. * Delete node p, and then rebalance the tree.
  3. * 删除节点,并再平衡红黑树
  4. */
  5. private void deleteEntry(Entry<K,V> p) {
  6. modCount++;//删除操作属于结构性变动,modCount计数+1
  7. size--;
  8. // If strictly internal, copy successor's element to p and then make p point to successor.
  9. //当左节点和右节点都非空时,可以通过子树找到该节点的替换中继节点
  10. if (p.left != null && p.right != null) {
  11. //找到中继节点,用于替换当前待删除节点的位置;规则是右分支最左边,或者左分支最右边的节点
  12. Entry<K,V> s = successor(p);
  13. p.key = s.key;
  14. p.value = s.value;
  15. p = s;
  16. } // p has 2 children
  17. // Start fixup at replacement node, if it exists.
  18. //replacement为替代节点,如果p的左子树存在那么就用左子树替代,否则用右子树替代
  19. Entry<K,V> replacement = (p.left != null ? p.left : p.right);
  20. if (replacement != null) {
  21. // Link replacement to parent
  22. replacement.parent = p.parent;
  23. if (p.parent == null)//当前节点的parent为空,说明此时只剩下replacement一个节点
  24. root = replacement;//设置根节点
  25. else if (p == p.parent.left)//如果p为左节点,则用replacement来替代为左节点
  26. p.parent.left = replacement;
  27. else //如果p为右节点,则用replacement来替代为右节点
  28. p.parent.right = replacement;
  29. // Null out links so they are OK to use by fixAfterDeletion.
  30. // 将p节点从这棵树中剔除掉,help gc
  31. p.left = p.right = p.parent = null;
  32. // Fix replacement
  33. /*
  34. * 重点:
  35. * 1.若p为红色直接删除,红黑树保持平衡
  36. * 2.若p为黑色,则需要调整红黑树使其保持平衡
  37. */
  38. if (p.color == BLACK)
  39. fixAfterDeletion(replacement);
  40. } else if (p.parent == null) { // return if we are the only node. 清空最后一个元素
  41. root = null;//若p没有父节点,表示为p根节点,直接删除
  42. } else { // No children. Use self as phantom replacement and unlink.
  43. //当自身没有子节点(即自身就是叶子节点)
  44. /*
  45. * 重点:
  46. * 1.若p为红色直接删除,红黑树保持平衡
  47. * 2.若p为黑色,则需要调整红黑树使其保持平衡
  48. */
  49. if (p.color == BLACK)
  50. fixAfterDeletion(p);
  51. //删除p节点
  52. if (p.parent != null) {
  53. if (p == p.parent.left)
  54. p.parent.left = null;
  55. else if (p == p.parent.right)
  56. p.parent.right = null;
  57. p.parent = null;
  58. }
  59. }
  60. }

5.5 successor方法

  1. /**
  2. * Returns the successor of the specified Entry, or null if no such.
  3. * 返回节点t的后继节点,没有就是null
  4. * 类似的predecessor()返回前继节点,没有就是null
  5. * 此操作相当于树的中序遍历(LDR - 左根右),主要用于保证其迭代输出是有序
  6. * (在it.next()的调用中会使用nextEntry调用successor)
  7. * 判断步骤:
  8. * 1.空节点,中继为null
  9. * 2.若右子树非空,找寻找右子树的最左子树
  10. * 3.若右子树为空,找左子树第一个向右走的祖先
  11. */
  12. static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
  13. if (t == null) //1.空节点,没有后继
  14. return null;
  15. //2.有右子树的节点,后继节点就是右子树的“最左节点”,因为“最左子树”是右子树的最小节点
  16. else if (t.right != null) {//若右孩子非空,获取右子树最小的一个值(即比t大的最小值)
  17. Entry<K,V> p = t.right;
  18. while (p.left != null)//遍历右孩子的左子树,直到找到最后一个非空的值(最靠近t的比t大的最小值)
  19. p = p.left;
  20. return p;
  21. } else {
  22. //3.如果右子树为空,则寻找当前节点所在左子树的第一个祖先节点
  23. Entry<K,V> p = t.parent;
  24. Entry<K,V> ch = t;
  25. while (p != null && ch == p.right) {
  26. ch = p;
  27. p = p.parent;
  28. }
  29. return p;
  30. }
  31. }

5.6 二叉树排序补充(题外话)

  • 前序遍历:根节点->左子树->右子树 (子树内部查找规则是从左往右)
  • 中序遍历:左子树->根节点->右子树 (子树内部查找规则是先左后右)
  • 后序遍历:左子树->右子树->根节点 (子树内部查找规则是从右往左)

QQ截图20170728160846.png-7.9kB

5.7 删除情况

  • 难点:最大的麻烦是要保持 各分支黑色节点数目相等
  • 情况1:无子节点(红色节点)
    • 策略:直接把父节点的对应儿子指针设为NULL,删除子节点
  • 情况2:有一个子节点
    • 策略把父节点的相应儿子指针指向儿子的独生子,删除子节点
  • 情况3:有两个子节点
    • 情况3.1:当前节点是黑色且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑色)
      • 策略:把父节点染成红色,把兄弟节点染成黑色,然后以父节点为支点重新旋转
      • 此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况
    • 情况3.2:当前节点是黑色且兄弟是黑色且兄弟节点的两个子节点全为黑色
      • 策略:将兄弟节点变成红色,把父节点当成新的当前节点
    • 情况3.3:当前节点是黑色,兄弟节点是黑色,兄弟的左子是红色,右子是黑色
      • 策略:将兄弟节点与其左子树进行颜色互换然后进行重新旋转
    • 情况3.4:当前节点是黑色,兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意
      • 策略:兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点重新旋转

5.7 fixAfterDeletion方法

  1. private void fixAfterDeletion(Entry<K,V> x) {
  2. // 循环,直到 x 不是根节点,且 x 的颜色是黑色
  3. while (x != root && colorOf(x) == BLACK) {
  4. if (x == leftOf(parentOf(x))) { //当前节点为父节点的左子节点
  5. Entry<K,V> sib = rightOf(parentOf(x));//兄弟节点为父节点的右子节点
  6. /*
  7. * 情况3.1:当前节点是黑色且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)
  8. * 策略:把父节点染成红色,把兄弟节点染成黑色,然后以父节点为支点左旋
  9. * 这时情况会转变为3.2(兄弟节点是黑色且兄弟节点的两个子节点全为黑)
  10. */
  11. if (colorOf(sib) == RED) {
  12. setColor(sib, BLACK);
  13. setColor(parentOf(x), RED);
  14. rotateLeft(parentOf(x));
  15. sib = rightOf(parentOf(x));
  16. }
  17. /*
  18. * 情况3.2:当前节点是黑色且兄弟节点是黑色且兄弟节点的两个子节点全为黑色
  19. * 策略:将兄弟节点变成红色,把父节点当成新的当前节点
  20. */
  21. if (colorOf(leftOf(sib)) == BLACK &&
  22. colorOf(rightOf(sib)) == BLACK) {
  23. setColor(sib, RED);
  24. x = parentOf(x);
  25. } else {
  26. /*
  27. * 情况3.3:当前节点颜色是黑色,兄弟节点是黑色,兄弟的左子是红色,右子是黑色
  28. * 策略:将兄弟节点与其左子树进行颜色互换然后以兄弟节点为支点右转
  29. * 这时情况会转变为3.4 (兄弟节点的右子是红色)
  30. */
  31. if (colorOf(rightOf(sib)) == BLACK) {
  32. setColor(leftOf(sib), BLACK);
  33. setColor(sib, RED);
  34. rotateRight(sib);
  35. sib = rightOf(parentOf(x));
  36. }
  37. /*
  38. * 情况3.4 :当前节点颜色是黑色,兄弟节点是黑色,但是兄弟节点的右子是红色,
  39. * 兄弟节点左子的颜色任意
  40. * 策略:交换兄弟节点和父节点的颜色,同时将兄弟节点右子树设置为黑色,最后以父节点为支点左旋
  41. */
  42. setColor(sib, colorOf(parentOf(x)));
  43. setColor(parentOf(x), BLACK);
  44. setColor(rightOf(sib), BLACK);
  45. rotateLeft(parentOf(x));
  46. x = root;
  47. }
  48. } else { // 当前节点为父节点的右子节点 ,然后左改成了右,左旋改成右旋
  49. Entry<K,V> sib = leftOf(parentOf(x));//兄弟节点为父节点的左子节点
  50. if (colorOf(sib) == RED) {
  51. setColor(sib, BLACK);
  52. setColor(parentOf(x), RED);
  53. rotateRight(parentOf(x));
  54. sib = leftOf(parentOf(x));
  55. }
  56. if (colorOf(rightOf(sib)) == BLACK &&
  57. colorOf(leftOf(sib)) == BLACK) {
  58. setColor(sib, RED);
  59. x = parentOf(x);
  60. } else {
  61. if (colorOf(leftOf(sib)) == BLACK) {
  62. setColor(rightOf(sib), BLACK);
  63. setColor(sib, RED);
  64. rotateLeft(sib);
  65. sib = leftOf(parentOf(x));
  66. }
  67. setColor(sib, colorOf(parentOf(x)));
  68. setColor(parentOf(x), BLACK);
  69. setColor(leftOf(sib), BLACK);
  70. rotateRight(parentOf(x));
  71. x = root;
  72. }
  73. }
  74. }
  75. setColor(x, BLACK);
  76. }

6.TreeMap的旋转和着色

6.1 旋转和着色方法

  1. /**
  2. * 返回父节点,有parent返回parent,否则返回null
  3. */
  4. private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
  5. return (p == null ? null: p.parent);
  6. }
  7. /**
  8. * 返回左子节点,有left返回left,否则返回null
  9. */
  10. private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) {
  11. return (p == null) ? null: p.left;
  12. }
  13. /**
  14. * 返回右子节点,有right返回right,否则返回null
  15. */
  16. private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
  17. return (p == null) ? null: p.right;
  18. }
  19. /**
  20. * 当前节点重新着色
  21. * @param c false:红色,true:黑色
  22. */
  23. private static <K,V> void setColor(Entry<K,V> p, boolean c) {
  24. if (p != null) p.color = c;
  25. }

6.2 rotateLeft方法

  1. /**
  2. * 左旋:以当前节点为支点,和其右子节点的连线为支轴左旋
  3. */
  4. private void rotateLeft(Entry<K,V> p) {
  5. if (p != null) {
  6. Entry<K,V> r = p.right;//右子节点
  7. p.right = r.left;//右子节点的左节点作为当前节点的右子节点
  8. if (r.left != null)
  9. r.left.parent = p;//当前节点重新作为其右子节点的左节点的父节点
  10. r.parent = p.parent;//当前节点的父节点变更为其右子节点的父节点
  11. //支点无父节点,即右子上移做根节点
  12. if (p.parent == null)
  13. root = r;
  14. //若当前节点为其父节点的左子节点,需将其右子节点作为其父节点的左节点
  15. else if (p.parent.left == p)
  16. p.parent.left = r;
  17. //若当前节点为其父节点的右子节点,需将其右子节点作为其父节点的右节点
  18. else
  19. p.parent.right = r;
  20. r.left = p;//当前节点重新作为其右子节点的左子节点
  21. p.parent = r;
  22. }
  23. }

270024492492764.gif-243.6kB E为支点,与其E的右子节点S的连线为支轴左旋

6.3 rotateRight方法

  1. /**
  2. * 右旋:以当前节点为支点,和其左子节点的连线为支轴右旋(类似左旋,区别是左右互换)
  3. */
  4. private void rotateRight(Entry<K,V> p) {
  5. if (p != null) {
  6. Entry<K,V> l = p.left;
  7. p.left = l.right;
  8. if (l.right != null) l.right.parent = p;
  9. l.parent = p.parent;
  10. if (p.parent == null)
  11. root = l;
  12. else if (p.parent.right == p)
  13. p.parent.right = l;
  14. else p.parent.left = l;
  15. l.right = p;
  16. p.parent = l;
  17. }
  18. }

270024492492764.gif-243.6kB S为支点,与其S的左子节点E的连线为支轴右旋

7.TreeMap的遍历方式

7.1 遍历TreeMap的键值对

  1. TreeMap<Integer,Integer> treeMap = new TreeMap<Integer,Integer>();
  2. Iterator iterator = treeMap.entrySet().iterator();
  3. while(iterator.hasNext()) {
  4. Map.Entry entry = (Map.Entry)iterator.next();
  5. Integer key = (Integer)entry.getKey();// 获取key
  6. Integer value = (Integer)entry.getValue();// 获取value
  7. }

7.2 遍历TreeMap的键

  1. TreeMap<Integer,Integer> treeMap = new TreeMap<Integer,Integer>();
  2. Iterator iterator = treeMap.keySet().iterator();
  3. while (iterator.hasNext()) {
  4. Integer key = (Integer)iterator.next();// 获取key
  5. Integer value = (Integer)map.get(key);// 根据key,获取value
  6. }

7.3 遍历TreeMap的值

  1. TreeMap<Integer,Integer> treeMap = new TreeMap<Integer,Integer>();
  2. Collection c = map.values();
  3. Iterator iterator= c.iterator();
  4. while (iterator.hasNext()) {
  5. Integer value = (Integer)iterator.next();
  6. }

8.HashMap vs LinkedHashMap vs TreeMap

  • HashMap存储键值对,可以实现键值对的快速查询,适用于插入、删除和定位元素。
  • TreeMap取出来的是排序后的键值对,适用于需要自然顺序或自定义顺序遍历键的情况。
  • LinkedHashMap 为有序的HashMap,适用于需要输出的顺序和输入的相同或按读取顺序来排列,如连接池。

集合番@TreeMap一文通(1.7版)黄志鹏kira 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议 进行许可。

本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注