《恋上数据结构与算法》笔记(十六):HashMap、HashSet、LinkedHashMap、LinkendHashSet

墨蓝 2022-12-30 13:38 45阅读 0赞

必须要先看上一篇 : 小码哥《恋上数据结构与算法》笔记(十五):哈希表(Hash Table)

  • 以及红黑树的实现, 因为下面的HashMap就是使用Hash表 + 红黑树来实现的

目录

  • 一、哈希表的接口设计(HashMap)
  • 二、哈希表的实现(HashMap)

    • 1、声明节点
    • 2、clean实现
    • 3、put实现
    • 4、get实现
    • 5、remove实现
    • 6、containsValue 和 containsKey实现
    • 7、扩容 resize
    • 8、元素遍历traversal实现
    • 9、equals的优化
  • 三、TreeMap VS HashMap
  • 四、LinkedHashMap实现
  • 五、LinkedHashMap的接口实现
  • 六、使用HashMap和LinkedHashMap实现HashSet和LinkedHashSet
  • 附录、HashMap完整代码

一、哈希表的接口设计(HashMap)

  • 哈希表实现的Map, 称为HashMap
  • 用哈希表中的数组(桶)索引, 存储红黑树的根节点, 每一个哈希表中的桶都是一颗红黑树
    在这里插入图片描述

    /* Description: 使用哈希表来实现一个HashMap @author guizy @date 2020/12/14 22:00 /
    public class HashMap implements Map {

    1. private static final boolean RED = false;
    2. private static final boolean BLACK = true;
    3. private static final int DEFAULT_CAPACITY = 1 << 4;
    4. // 装载因子, 当哈希表容量为table.length用到75%就进行扩容
    5. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    6. private int size; // HashMap的容量(哈希表容量), 用来记录存放多少个entry(键值对)
    7. // 存放 红黑树根节点 的数组(哈希表底层就是数组), 也就是说哈希表中每一个桶中都是一颗红黑树. 桶数组
    8. private Node<K, V>[] table;
    9. public HashMap() {
    10. table = new Node[DEFAULT_CAPACITY];
    11. }

    }

二、哈希表的实现(HashMap)

1、声明节点

  1. protected static class Node<K, V> {
  2. int hash; // 节点的哈希值
  3. K key;
  4. V value;
  5. boolean color = RED;
  6. Node<K, V> left;
  7. Node<K, V> right;
  8. Node<K, V> parent;
  9. public Node(K key, V value, Node<K, V> parent) {
  10. this.key = key;
  11. int hash = key == null ? 0 : key.hashCode();
  12. this.hash = hash ^ (hash >>> 16);
  13. this.value = value;
  14. this.parent = parent;
  15. }
  16. public boolean isLeaf() {
  17. return left == null && right == null;
  18. }
  19. public boolean hasTwoChildren() {
  20. return left != null && right != null;
  21. }
  22. public boolean isLeftChild() {
  23. return parent != null && this == parent.left;
  24. }
  25. public boolean isRightChild() {
  26. return parent != null && this == parent.right;
  27. }
  28. public Node<K, V> sibling() {
  29. if (isLeftChild()) {
  30. return parent.right;
  31. }
  32. if (isRightChild()) {
  33. return parent.left;
  34. }
  35. return null;
  36. }
  37. @Override
  38. public String toString() {
  39. return "Node_" + key + "_" + value;
  40. }
  41. }

注意: 为了节省篇幅, 下面关于修复红黑树性质的代码就不提供了 , 可以参考之前的博客, 红黑树实现, TreeMap实现, 这些方法的代码都是相同的。
在这里插入图片描述
在这里插入图片描述

2、clean实现

  • 遍历哈希表中的桶数组,清空桶中红黑树的根节点。这样每个中的红黑树就都销毁了

    @Override
    public int size() {

    1. return size;

    }

    @Override
    public boolean isEmpty() {

    1. return size == 0;

    }

    @Override
    public void clear() {

    1. // 这里的判断是因为, 如果size==0的时候, 调用clear, 还是会进入下面的循环,进行清空,数组只是开辟了16个空的位置,本来就是空的.
    2. if (size == 0) return;
    3. size = 0;
    4. // 将哈希表中每一个桶(红黑树的根节点)都清空.
    5. for (int i = 0; i < table.length; i++) {
    6. table[i] = null;
    7. }

    }

3、put实现

  • 在进行插入操作时,通过比较keykey.parent哈希值大小,来决定插入到哈希表位置。
  • 如果哈希值相同,则比较equals
  • 如果equals相同,则比较类名
  • 如果类名相同(同一种类型),则查看是否实现comparable,如果实现,则直接通过该函数比较。
  • 如果相同类型,不具有可比较性,或其中一个数据为空,则比较内存地址
  • 直接比较内存地址会导致结果不稳定,应该先扫码树中是否有该值,如果没有,再比较内存地址

上面的操作, 就是为了提高HashMap的性能, 通过各种可以比较的条件, 判断红黑树上的结点添加的位置; 这样可以减少红黑树的性质修复. 提高代码性能

  1. /** * 往哈希表数组中(桶中)添加 * * @param key * @param value * @return 返回是被替代的value */
  2. @Override
  3. public V put(K key, V value) {
  4. resize();
  5. // 哈希表中的索引
  6. int index = index(key);
  7. // 取出index位置(数组中)的红黑树根节点(因为哈希表中存储的就是红黑树的根节点(键值对))
  8. Node<K, V> root = table[index];
  9. if (root == null) {
  10. //root = new Node<>(key, value, null);
  11. root = createNode(key, value, null);
  12. table[index] = root;
  13. size++;
  14. // 修复红黑树性质
  15. afterPut(root);
  16. return null;
  17. }
  18. // 出现hash冲突, 说明table[index]表中的位置不为空,已经有红黑树的根节点了
  19. // 添加的不是第一个节点
  20. Node<K, V> parent = root; // 这个是第一次比较的父节点
  21. Node<K, V> node = root;
  22. int cmp = 0;
  23. K k1 = key;
  24. //int h1 = k1 == null ? 0 : k1.hashCode();
  25. int h1 = hash(k1);
  26. // 搜索结果
  27. Node<K, V> result = null;
  28. // 是否搜索过, 为了防止重复搜索; 提高代码性能
  29. boolean searched = false;
  30. do {
  31. parent = node; // 记录其每一次比较的父节点
  32. K k2 = node.key;
  33. int h2 = node.hash;
  34. // 先比较哈希值
  35. if (h1 > h2) {
  36. cmp = 1;
  37. } else if (h1 < h2) {
  38. cmp = -1;
  39. } else if (Objects.equals(k1, k2)) {
  40. cmp = 0;
  41. } else if (k1 != null && k2 != null
  42. && k1.getClass() == k2.getClass()
  43. && k1 instanceof Comparable
  44. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
  45. // 什么都不做. 直接就会到下下面cmp的比较逻辑
  46. // 这里就不比较了, 因为我们之前说过哈希表的元素可以不具备可比较性.
  47. // 确定两个key是否相同,只能比equals是否相同; 通过compareTo比较相等,也不代表他们就是相等的
  48. // cmp = ((Comparable) k1).compareTo(k2);
  49. } else if (searched) {
  50. // 已经搜索过了
  51. cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
  52. } else { // 先进行扫描, 然后再根据内存地址大小决定,插入到左/右; searched == false 还没有搜索过
  53. if ((node.left != null && (result = node(node.left, k1)) != null)
  54. || (node.right != null && (result = node(node.right, k1)) != null)) {
  55. // 已经存在这个key
  56. node = result;
  57. cmp = 0;
  58. } else {
  59. // 不存在这个key
  60. searched = true;
  61. cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
  62. }
  63. }
  64. if (cmp > 0) {
  65. // 插入的元素大于根节点的元素,插入到根节点的右边
  66. node = node.right;
  67. } else if (cmp < 0) {
  68. // 插入的元素小于根节点的元素,插入到根节点的左边
  69. node = node.left;
  70. } else { // 相等
  71. node.key = key;
  72. V oldValue = node.value;
  73. node.value = value;
  74. // 这里写不写都行,hash本来就等于h1; 因为key是相同(equals相同)的时候,才会来到这里,key相同,hash值也肯定相同
  75. node.hash = h1;
  76. return oldValue; // 返回之前node的value
  77. }
  78. } while (node != null);
  79. // 看看插入到父节点的哪个位置
  80. //Node<K, V> newNode = new Node<>(key, value, parent);
  81. Node<K, V> newNode = createNode(key, value, parent);
  82. if (cmp > 0) {
  83. parent.right = newNode;
  84. } else {
  85. parent.left = newNode;
  86. }
  87. size++;
  88. // 添加节点之后的逻辑
  89. afterPut(newNode);
  90. // 这里的key是第一次加进去的, 之前没有值, 所以返回null
  91. return null;
  92. }
  93. // ------------上面代码中用到的方法 (resize扩容,后面再说)----------------
  94. /** * 计算key的索引(在哈希表(数组)的哪个索引位置) * * @param key * @return */
  95. private int index(K key) {
  96. // if (key == null) return 0; // 如果key为空, 插入到哈希表的0位置
  97. // int hash = key.hashCode();
  98. // // 同Double,Long的hashCode类似实现,因为key.hashCode()是我们自己实现的,在JDK底层又作了一次混合运算
  99. // // 拿到我们自己实现的hash值, 将hash值和hash值无符号右移16位再做一次运算
  100. // hash = hash ^ (hash >>> 16);
  101. // return hash & (table.length - 1);
  102. return hash(key) & (table.length - 1);
  103. }
  104. /** * 为了拓展, HashMap子类可以创建自己的Node结点 * @param key * @param value * @param parent * @return */
  105. protected Node<K, V> createNode(K key, V value, Node<K, V> parent) {
  106. return new Node<>(key, value, parent);
  107. }
  108. /** * 扰动计算哈希值 * * @param key * @return */
  109. private int hash(K key) {
  110. if (key == null) return 0;
  111. int hash = key.hashCode();
  112. return hash ^ (hash >>> 16);
  113. }
  114. /** * 根据传入的node,计算它在数组中的哪个索引位置(即使它不是根节点) * * @param node * @return */
  115. private int index(Node<K, V> node) {
  116. return node.hash & (table.length - 1);
  117. }
  118. /** * 根据一个key, 找到对应的节点 * * @param key * @return */
  119. private Node<K, V> node(K key) {
  120. Node<K, V> root = table[index(key)];
  121. return root == null ? null : node(root, key);
  122. }
  123. private Node<K, V> node(Node<K, V> node, K k1) {
  124. //int h1 = k1 == null ? 0 : k1.hashCode();
  125. int h1 = hash(k1);
  126. // 存储查找结果
  127. Node<K, V> result = null;
  128. int cmp = 0;
  129. // 递归去左右子树查找
  130. while (node != null) {
  131. K k2 = node.key;
  132. int h2 = node.hash;
  133. // 先比较哈希值
  134. if (h1 > h2) {
  135. node = node.right;
  136. } else if (h1 < h2) {
  137. node = node.left;
  138. // 哈希值相同, 看看k1,k2是否相同,如果相同,则表示找到
  139. } else if (Objects.equals(k1, k2)) {
  140. return node;
  141. // 哈希值相同, equals不同, 看看是否具备可比较性
  142. } else if (k1 != null && k2 != null
  143. && k1.getClass() == k2.getClass()
  144. && k1 instanceof Comparable
  145. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
  146. // 具备可比较性
  147. //cmp = ((Comparable) k1).compareTo(k2); // 和put相同,compareTo相同不应该确定key就相同,应该继续向下搜索节点的key
  148. if (cmp > 0) {
  149. node = node.right;
  150. } else if (cmp < 0) {
  151. node = node.left;
  152. }
  153. // else {
  154. // return node;
  155. // }
  156. // 哈希值相同,equals不同, 不具备可比较性
  157. } else if (node.right != null && (result = node(node.right, k1)) != null) {
  158. return result;
  159. } else {
  160. node = node.left; // 优化下面的6行代码, 减少一次递归调用
  161. }
  162. // } else if (node.left != null && (result = node(node.left, k1)) != null) {
  163. // return result;
  164. // } else {
  165. // // 没找到
  166. // return null;
  167. // }
  168. }
  169. return null;
  170. }

4、get实现

  • 首先根据想要获取的元素key看看它是存在于哈希表的哪个桶中, 然后根据该桶中的红黑树根节点, 遍历去寻找该树的结点。

    @Override
    public V get(K key) {

    1. Node<K, V> node = node(key);
    2. return node != null ? node.value : null;

    }

    /* 根据一个key, 找到对应的节点 @param key @return /
    private Node node(K key) {

    1. Node<K, V> root = table[index(key)];
    2. return root == null ? null : node(root, key);

    }

    private Node node(Node node, K k1) {

    1. //int h1 = k1 == null ? 0 : k1.hashCode();
    2. int h1 = hash(k1);
    3. // 存储查找结果
    4. Node<K, V> result = null;
    5. int cmp = 0;
    6. // 递归去左右子树查找
    7. while (node != null) {
    8. K k2 = node.key;
    9. int h2 = node.hash;
    10. // 先比较哈希值
    11. if (h1 > h2) {
    12. node = node.right;
    13. } else if (h1 < h2) {
    14. node = node.left;
    15. // 哈希值相同, 看看k1,k2是否相同,如果相同,则表示找到
    16. } else if (Objects.equals(k1, k2)) {
    17. return node;
    18. // 哈希值相同, equals不同, 看看是否具备可比较性
    19. } else if (k1 != null && k2 != null
    20. && k1.getClass() == k2.getClass()
    21. && k1 instanceof Comparable
    22. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
    23. // 具备可比较性
    24. //cmp = ((Comparable) k1).compareTo(k2); // 和put相同,compareTo相同不应该确定key就相同,应该继续向下搜索节点的key
    25. if (cmp > 0) {
    26. node = node.right;
    27. } else if (cmp < 0) {
    28. node = node.left;
    29. }

    // else {
    // return node;
    // }

    1. // 哈希值相同,equals不同, 不具备可比较性
    2. } else if (node.right != null && (result = node(node.right, k1)) != null) {
    3. return result;
    4. } else {
    5. node = node.left; // 优化下面的6行代码, 减少一次递归调用
    6. }

    // } else if (node.left != null && (result = node(node.left, k1)) != null) {
    // return result;
    // } else {
    // // 没找到
    // return null;
    // }

    1. }
    2. return null;

    }

5、remove实现

  1. @Override
  2. public V remove(K key) {
  3. return remove(node(key));
  4. }
  5. protected V remove(Node<K, V> node) {
  6. if (node == null) return null;
  7. Node<K, V> willNode = node; // 本来要删除的节点,由于红黑树中度为2的节点删除方式, 和链表中的删除方式不同,所以要做交换
  8. // node 不为空, 必然要删除结点, 先size--;
  9. size--;
  10. V oldValue = node.value;
  11. // 删除node是度为2的结点
  12. if (node.hasTwoChildren()) {
  13. /*//1 找到后继(也可以找到前驱) Node<E> successor = successor(node); //2 用后继结点的值覆盖度为2结点的值 node.element = successor.element; //3 删除后继节点 node = successor;*/
  14. //1、找到前驱
  15. Node<K, V> predecessor = predecessor(node);
  16. //2、用前驱节点的值覆盖度为2节点的值
  17. node.key = predecessor.key;
  18. node.value = predecessor.value;
  19. node.hash = predecessor.hash;
  20. //3、删除前驱节点
  21. node = predecessor;
  22. }
  23. // 删除node,即删除后继节点 (node节点必然是度为1或0)
  24. // 因为node只有一个子节点/0个子节点, 如果其left!=null, 则用node.left来替代, node.left==null, 用node.right来替代,
  25. // 若node为叶子节点, 说明, node.left==null, node.right也为null, 则replacement==null;
  26. Node<K, V> replacement = node.left != null ? node.left : node.right;
  27. // 获取要删除节点的索引(就是红黑树所在桶的索引)
  28. int index = index(node);
  29. // 删除node是度为1的结点
  30. if (replacement != null) {
  31. // 更改parent
  32. replacement.parent = node.parent;
  33. // 更改parent的left、right的指向
  34. if (node.parent == null) { // node是度为1且是根节点
  35. table[index] = replacement;
  36. } else if (node == node.parent.left) {
  37. node.parent.left = replacement;
  38. } else if (node == node.parent.right) {
  39. node.parent.right = replacement;
  40. }
  41. // 删除结点之后的处理
  42. afterRemove(node, replacement);
  43. // 删除node是叶子节点, 且是根节点
  44. } else if (node.parent == null) {
  45. table[index] = null;
  46. // 删除结点之后的处理
  47. afterRemove(node, null);
  48. } else { // node是叶子结点, 且不是根节点
  49. if (node == node.parent.left) {
  50. node.parent.left = null;
  51. } else { // node == node.parent.right
  52. node.parent.right = null;
  53. }
  54. // 删除结点之后的处理
  55. afterRemove(node, null);
  56. }
  57. // 交给子类处理的
  58. afterChildRemove(willNode, node);
  59. return oldValue;
  60. }
  61. /** * @param willNode 即将要删除的节点 * @param removeNode 实际删除的节点 */
  62. protected void afterChildRemove(Node<K, V> willNode, Node<K, V> removeNode) {
  63. }

6、containsValue 和 containsKey实现

  • 检测哈希表中是否存在某值,通过遍历所有桶中的红黑树去寻找,使用层序遍历实现。

    @Override
    public boolean containsKey(K key) {

    1. return node(key) != null;

    }

    @Override
    public boolean containsValue(V value) {

    1. if (size == 0) return false;
    2. Queue<Node<K, V>> queue = new LinkedList<>();
    3. // 遍历哈希表中的所有的桶, 然后根据每个桶中的红黑树根节点, 层序遍历, 看看是否存在value
    4. for (int i = 0; i < table.length; i++) {
    5. // 说明哈希表中的table[i]的位置没有红黑树根节点, 也就是为空, 此时不用遍历比较.跳过
    6. if (table[i] == null) continue;
    7. queue.offer(table[i]);
    8. while (!queue.isEmpty()) {
    9. Node<K, V> node = queue.poll();
    10. if (Objects.equals(value, node.value)) return true;
    11. if (node.left != null) {
    12. queue.offer(node.left);
    13. }
    14. if (node.right != null) {
    15. queue.offer(node.right);
    16. }
    17. }
    18. }
    19. return false;

    }

7、扩容

  • 扩容操作, 在put方法, 中调用
  • 当哈希表的table数组中添加元素过多后,哈希冲突概率增大,效率变低。所以要根据装填因子判断是否需要扩容。
  • 装填因子:节点总数量 / 哈希表桶数组长度,也叫做负载因子
  • 在JDK1.8的HashMap中,如果装填因子超过0.75,就扩容为原来的2倍。
  • 如果装填因子超过0.75,就将数组长度扩大为原来的两倍,并将原来的数据迁移进新数组。
  • 扩容之后,原来数据算出的节点值就有可能不一样了(保持不变index = index + 旧容量),因为节点的计算需要涉及到table.length

    /* 扩容 */
    private void resize() {

    1. // 装填因子 <= 0.75, 不扩容
    2. if (size / table.length <= DEFAULT_LOAD_FACTOR) return;
    3. Node<K, V>[] oldTable = table;
    4. table = new Node[oldTable.length << 1];
    5. Queue<Node<K, V>> queue = new LinkedList<>();
    6. for (int i = 0; i < oldTable.length; i++) {
    7. if (oldTable[i] == null) continue;
    8. // 层序遍历
    9. queue.offer(oldTable[i]);
    10. while (!queue.isEmpty()) {
    11. Node<K, V> node = queue.poll();
    12. if (node.left != null) {
    13. queue.offer(node.left);
    14. }
    15. if (node.right != null) {
    16. queue.offer(node.right);
    17. }
    18. // 挪动代码,放到后面
    19. moveNode(node);
    20. }
    21. }

    }

    /* 将之前哈希表中的节点, 挪动到扩容后的哈希表中 @param newNode */
    private void moveNode(Node newNode) {

    1. // 首先重置挪动过来node的left,right,parent
    2. newNode.parent = null;
    3. newNode.left = null;
    4. newNode.right = null;
    5. newNode.color = RED;
    6. // 哈希表中的索引
    7. int index = index(newNode);
    8. // 取出index位置(数组中)的红黑树根节点(因为哈希表中存储的就是红黑树的根节点(键值对))
    9. Node<K, V> root = table[index];
    10. if (root == null) {
    11. root = newNode;
    12. table[index] = root;
    13. afterPut(root);
    14. return;
    15. }
    16. Node<K, V> parent = root; // 这个是第一次比较的父节点
    17. Node<K, V> node = root;
    18. int cmp = 0;
    19. K k1 = newNode.key;
    20. int h1 = newNode.hash;
    21. do {
    22. parent = node; // 记录其每一次比较的父节点
    23. K k2 = node.key;
    24. int h2 = node.hash;
    25. // 先比较哈希值, 因为是挪动, 所以不用equals比较key, 肯定没有相同的key
    26. if (h1 > h2) {
    27. cmp = 1;
    28. } else if (h1 < h2) {
    29. cmp = -1;
    30. } else if (k1 != null && k2 != null
    31. && k1.getClass() == k2.getClass()
    32. && k1 instanceof Comparable
    33. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
    34. } else {
    35. cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
    36. }
    37. if (cmp > 0) {
    38. // 插入的元素大于根节点的元素,插入到根节点的右边
    39. node = node.right;
    40. } else if (cmp < 0) {
    41. // 插入的元素小于根节点的元素,插入到根节点的左边
    42. node = node.left;
    43. }
    44. } while (node != null);
    45. // 看看插入到父节点的哪个位置
    46. newNode.parent = parent;
    47. if (cmp > 0) {
    48. parent.right = newNode;
    49. } else {
    50. parent.left = newNode;
    51. }
    52. // 添加节点之后的逻辑
    53. afterPut(newNode);

    }

8、元素遍历traversal实现

  1. @Override
  2. public void traversal(Visitor<K, V> visitor) {
  3. if (size == 0 || visitor == null) return;
  4. Queue<Node<K, V>> queue = new LinkedList<>();
  5. // 遍历哈希表中的所有的桶, 然后根据每个桶中的红黑树根节点, 层序遍历, 看看是否存在value
  6. for (int i = 0; i < table.length; i++) {
  7. // 说明哈希表中的table[i]的位置没有红黑树根节点, 也就是为空, 此时不用遍历比较.跳过
  8. if (table[i] == null) continue;
  9. queue.offer(table[i]);
  10. while (!queue.isEmpty()) {
  11. Node<K, V> node = queue.poll();
  12. // 返回为true, 就停止遍历
  13. if (visitor.visit(node.key, node.value)) return;
  14. if (node.left != null) {
  15. queue.offer(node.left);
  16. }
  17. if (node.right != null) {
  18. queue.offer(node.right);
  19. }
  20. }
  21. }
  22. }

9、equals的优化

  • 我们在添加元素时,要谨防因equals的函数实现有问题,导致a.equals(b)和b.equals(a)的结果不一致。
  • 所以在实现equals函数时,要遵循以下条件:
    在这里插入图片描述

三、TreeMap VS HashMap

  • TreeMap增删改查都是O(logn)级别,而哈希表是O(1)级别。
  • 当元素具备可比较性且要求升序遍历时,使用TreeMap。当对遍历没有要求时,选择HashMap

四、LinkedHashMap

  • 在HashMap的基础上维护元素的添加顺序,使得遍历的结果是遵循添加顺序的。在HashMap的基础上维护一个双向链表
  • 假设添加顺序是37,21,31,41,97,95,52,42,83

注意: 该链表是跨树的, 将所有中的红黑树的节点, 通过添加顺序, 都串到一个链表
在这里插入图片描述

五、LinkedHashMap的接口实现

  • 新增firstlast两个属性。

    public class LinkedHashMap extends HashMap {

    1. private LinkedNode<K, V> first;
    2. private LinkedNode<K, V> last;

    }

  • 给Node增加前驱和后继两个指针。

    private static class LinkedNode extends Node {

    1. LinkedNode<K, V> prev;
    2. LinkedNode<K, V> next;
    3. public LinkedNode(K key, V value, Node<K, V> parent) {
    4. super(key, value, parent);
    5. }

    }

  • 添加一个构造节点的函数。

    @Override
    protected Node createNode(K key, V value, Node parent) {

    1. LinkedNode node = new LinkedNode(key, value, parent);
    2. if (first == null) { //没有头节点
    3. first = last = node;
    4. } else { //有头节点
    5. last.next = node;
    6. node.prev = last;
    7. last = node;
    8. }
    9. return node;

    }

  • clear函数需要清空first和last指针。

    @Override
    public void clear() {

    1. super.clear();
    2. first = null;
    3. last = null;

    }

  • 遍历函数

    @Override
    public void traversal(Visitor visitor) {

    1. if (visitor == null) return;
    2. LinkedNode<K, V> node = first;
    3. while (node != null) {
    4. if (visitor.visit(node.key, node.value)) return;
    5. node = node.next;
    6. }

    }

  • 删除函数,不仅仅需要删除数据,还需要修改指针指向。

  • 删除度为2的节点时,需要注意更换node前驱/后继节点的链接位置。
  • 例如,删除31

img

  1. @Override
  2. protected void afterRemove(Node<K, V> willNode, Node<K, V> removedNode) {
  3. LinkedNode<K, V> node1 = (LinkedNode<K, V>) willNode;
  4. LinkedNode<K, V> node2 = (LinkedNode<K, V>) removedNode;
  5. if (node1 != node2) {
  6. // 交换linkedWillNode和linkedRemovedNode在链表中的位置
  7. // 交换prev
  8. LinkedNode<K, V> tmp = node1.prev;
  9. node1.prev = node2.prev;
  10. node2.prev = tmp;
  11. if (node1.prev == null) {
  12. first = node1;
  13. } else {
  14. node1.prev.next = node1;
  15. }
  16. if (node2.prev == null) {
  17. first = node2;
  18. } else {
  19. node2.prev.next = node2;
  20. }
  21. // 交换next
  22. tmp = node1.next;
  23. node1.next = node2.next;
  24. node2.next = tmp;
  25. if (node1.next == null) {
  26. last = node1;
  27. } else {
  28. node1.next.prev = node1;
  29. }
  30. if (node2.next == null) {
  31. last = node2;
  32. } else {
  33. node2.next.prev = node2;
  34. }
  35. }
  36. LinkedNode<K, V> prev = node2.prev;
  37. LinkedNode<K, V> next = node2.next;
  38. if (prev == null) {
  39. first = next;
  40. } else {
  41. prev.next = next;
  42. }
  43. if (next == null) {
  44. last = prev;
  45. } else {
  46. next.prev = prev;
  47. }
  48. }
  • 核心就是交换。

img

  • 遍历节点

    @Override
    public boolean containsValue(V value) {

    1. LinkedNode<K, V> node = first;
    2. while (node != null) {
    3. if (Objects.equals(value, node.value)) return true;
    4. node = node.next;
    5. }
    6. return false;

    }

六、使用HashMap和LinkedHashMap实现HashSet和LinkedHashSet

HashSet

  1. /** * Description: 使用HashMap实现HashSet * * @author guizy1 * @date 2020/12/24 12:58 */
  2. public class HashSet<E> implements Set<E> {
  3. private HashMap<E, Object> map = new HashMap<>();
  4. @Override
  5. public int size() {
  6. return map.size();
  7. }
  8. @Override
  9. public boolean isEmpty() {
  10. return map.isEmpty();
  11. }
  12. @Override
  13. public void clear() {
  14. map.clear();
  15. }
  16. @Override
  17. public boolean contains(E element) {
  18. return map.containsKey(element);
  19. }
  20. @Override
  21. public void add(E element) {
  22. map.put(element, null);
  23. }
  24. @Override
  25. public void remove(E element) {
  26. map.remove(element);
  27. }
  28. @Override
  29. public void traversal(Visitor<E> visitor) {
  30. map.traversal(new Map.Visitor<E, Object>() {
  31. @Override
  32. public boolean visit(E key, Object value) {
  33. return visitor.visit(key);
  34. }
  35. });
  36. }
  37. }

LinkedHashSet

  1. /** * Description: 使用LinkedHashMap 实现 LinkedHashSet * * @author guizy1 * @date 2020/12/24 13:02 */
  2. public class LinkedHashSet<E> implements Set<E> {
  3. private LinkedHashMap<E, Object> map = new LinkedHashMap<>();
  4. @Override
  5. public int size() {
  6. return map.size();
  7. }
  8. @Override
  9. public boolean isEmpty() {
  10. return map.isEmpty();
  11. }
  12. @Override
  13. public void clear() {
  14. map.clear();
  15. }
  16. @Override
  17. public boolean contains(E element) {
  18. return map.containsKey(element);
  19. }
  20. @Override
  21. public void add(E element) {
  22. map.put(element, null);
  23. }
  24. @Override
  25. public void remove(E element) {
  26. map.remove(element);
  27. }
  28. @Override
  29. public void traversal(Visitor<E> visitor) {
  30. map.traversal(new Map.Visitor<E, Object>() {
  31. @Override
  32. public boolean visit(E key, Object value) {
  33. return visitor.visit(key);
  34. }
  35. });
  36. }
  37. }

附录: HashMap完整代码

  1. package com.hashtable.map;
  2. import com.hashtable.printer.BinaryTreeInfo;
  3. import com.hashtable.printer.BinaryTrees;
  4. import java.util.LinkedList;
  5. import java.util.Objects;
  6. import java.util.Queue;
  7. /** * Description: 使用哈希表来实现一个HashMap * * @author guizy * @date 2020/12/14 22:00 */
  8. @SuppressWarnings("all")
  9. public class HashMap<K, V> implements Map<K, V> {
  10. private static final boolean RED = false;
  11. private static final boolean BLACK = true;
  12. private static final int DEFAULT_CAPACITY = 1 << 4;
  13. // 装载因子, 当哈希表容量为table.length用到75%就进行扩容
  14. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  15. private int size; // HashMap的容量(哈希表容量), 用来记录存放多少个entry(键值对)
  16. // 存放 红黑树根节点 的数组(哈希表底层就是数组), 也就是说哈希表中每一个桶中都是一颗红黑树. 桶数组
  17. private Node<K, V>[] table;
  18. public HashMap() {
  19. table = new Node[DEFAULT_CAPACITY];
  20. }
  21. @Override
  22. public int size() {
  23. return size;
  24. }
  25. @Override
  26. public boolean isEmpty() {
  27. return size == 0;
  28. }
  29. @Override
  30. public void clear() {
  31. // 这里的判断是因为, 如果size==0的时候, 调用clear, 还是会进入下面的循环,进行清空,数组只是开辟了16个空的位置,本来就是空的.
  32. if (size == 0) return;
  33. size = 0;
  34. // 将哈希表中每一个桶(红黑树的根节点)都清空.
  35. for (int i = 0; i < table.length; i++) {
  36. table[i] = null;
  37. }
  38. }
  39. /** * 往哈希表数组中(桶中)添加 * * @param key * @param value * @return 返回是被替代的value */
  40. @Override
  41. public V put(K key, V value) {
  42. resize();
  43. // 哈希表中的索引
  44. int index = index(key);
  45. // 取出index位置(数组中)的红黑树根节点(因为哈希表中存储的就是红黑树的根节点(键值对))
  46. Node<K, V> root = table[index];
  47. if (root == null) {
  48. //root = new Node<>(key, value, null);
  49. root = createNode(key, value, null);
  50. table[index] = root;
  51. size++;
  52. // 修复红黑树性质
  53. afterPut(root);
  54. return null;
  55. }
  56. // 出现hash冲突, 说明table[index]表中的位置不为空,已经有红黑树的根节点了
  57. // 添加的不是第一个节点
  58. Node<K, V> parent = root; // 这个是第一次比较的父节点
  59. Node<K, V> node = root;
  60. int cmp = 0;
  61. K k1 = key;
  62. //int h1 = k1 == null ? 0 : k1.hashCode();
  63. int h1 = hash(k1);
  64. // 搜索结果
  65. Node<K, V> result = null;
  66. // 是否搜索过, 为了防止重复搜索; 提高代码性能
  67. boolean searched = false;
  68. do {
  69. parent = node; // 记录其每一次比较的父节点
  70. K k2 = node.key;
  71. int h2 = node.hash;
  72. // 先比较哈希值
  73. if (h1 > h2) {
  74. cmp = 1;
  75. } else if (h1 < h2) {
  76. cmp = -1;
  77. } else if (Objects.equals(k1, k2)) {
  78. cmp = 0;
  79. } else if (k1 != null && k2 != null
  80. && k1.getClass() == k2.getClass()
  81. && k1 instanceof Comparable
  82. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
  83. // 什么都不做. 直接就会到下下面cmp的比较逻辑
  84. // 这里就不比较了, 因为我们之前说过哈希表的元素可以不具备可比较性.
  85. // 确定两个key是否相同,只能比equals是否相同; 通过compareTo比较相等,也不代表他们就是相等的
  86. // cmp = ((Comparable) k1).compareTo(k2);
  87. } else if (searched) {
  88. // 已经搜索过了
  89. cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
  90. } else { // 先进行扫描, 然后再根据内存地址大小决定,插入到左/右; searched == false 还没有搜索过
  91. if ((node.left != null && (result = node(node.left, k1)) != null)
  92. || (node.right != null && (result = node(node.right, k1)) != null)) {
  93. // 已经存在这个key
  94. node = result;
  95. cmp = 0;
  96. } else {
  97. // 不存在这个key
  98. searched = true;
  99. cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
  100. }
  101. }
  102. if (cmp > 0) {
  103. // 插入的元素大于根节点的元素,插入到根节点的右边
  104. node = node.right;
  105. } else if (cmp < 0) {
  106. // 插入的元素小于根节点的元素,插入到根节点的左边
  107. node = node.left;
  108. } else { // 相等
  109. node.key = key;
  110. V oldValue = node.value;
  111. node.value = value;
  112. // 这里写不写都行,hash本来就等于h1; 因为key是相同(equals相同)的时候,才会来到这里,key相同,hash值也肯定相同
  113. node.hash = h1;
  114. return oldValue; // 返回之前node的value
  115. }
  116. } while (node != null);
  117. // 看看插入到父节点的哪个位置
  118. //Node<K, V> newNode = new Node<>(key, value, parent);
  119. Node<K, V> newNode = createNode(key, value, parent);
  120. if (cmp > 0) {
  121. parent.right = newNode;
  122. } else {
  123. parent.left = newNode;
  124. }
  125. size++;
  126. // 添加节点之后的逻辑
  127. afterPut(newNode);
  128. // 这里的key是第一次加进去的, 之前没有值, 所以返回null
  129. return null;
  130. }
  131. @Override
  132. public V get(K key) {
  133. Node<K, V> node = node(key);
  134. return node != null ? node.value : null;
  135. }
  136. @Override
  137. public V remove(K key) {
  138. return remove(node(key));
  139. }
  140. @Override
  141. public boolean containsKey(K key) {
  142. return node(key) != null;
  143. }
  144. @Override
  145. public boolean containsValue(V value) {
  146. if (size == 0) return false;
  147. Queue<Node<K, V>> queue = new LinkedList<>();
  148. // 遍历哈希表中的所有的桶, 然后根据每个桶中的红黑树根节点, 层序遍历, 看看是否存在value
  149. for (int i = 0; i < table.length; i++) {
  150. // 说明哈希表中的table[i]的位置没有红黑树根节点, 也就是为空, 此时不用遍历比较.跳过
  151. if (table[i] == null) continue;
  152. queue.offer(table[i]);
  153. while (!queue.isEmpty()) {
  154. Node<K, V> node = queue.poll();
  155. if (Objects.equals(value, node.value)) return true;
  156. if (node.left != null) {
  157. queue.offer(node.left);
  158. }
  159. if (node.right != null) {
  160. queue.offer(node.right);
  161. }
  162. }
  163. }
  164. return false;
  165. }
  166. @Override
  167. public void traversal(Visitor<K, V> visitor) {
  168. if (size == 0 || visitor == null) return;
  169. Queue<Node<K, V>> queue = new LinkedList<>();
  170. // 遍历哈希表中的所有的桶, 然后根据每个桶中的红黑树根节点, 层序遍历, 看看是否存在value
  171. for (int i = 0; i < table.length; i++) {
  172. // 说明哈希表中的table[i]的位置没有红黑树根节点, 也就是为空, 此时不用遍历比较.跳过
  173. if (table[i] == null) continue;
  174. queue.offer(table[i]);
  175. while (!queue.isEmpty()) {
  176. Node<K, V> node = queue.poll();
  177. // 返回为true, 就停止遍历
  178. if (visitor.visit(node.key, node.value)) return;
  179. if (node.left != null) {
  180. queue.offer(node.left);
  181. }
  182. if (node.right != null) {
  183. queue.offer(node.right);
  184. }
  185. }
  186. }
  187. }
  188. public void print() {
  189. if (size == 0) return;
  190. for (int i = 0; i < table.length; i++) {
  191. final Node<K, V> root = table[i];
  192. System.out.println("[index = " + i + "]");
  193. BinaryTrees.println(new BinaryTreeInfo() {
  194. @Override
  195. public Object root() {
  196. return root;
  197. }
  198. @Override
  199. public Object left(Object node) {
  200. return ((Node<K, V>) node).left;
  201. }
  202. @Override
  203. public Object right(Object node) {
  204. return ((Node<K, V>) node).right;
  205. }
  206. @Override
  207. public Object string(Object node) {
  208. return node;
  209. }
  210. });
  211. System.out.println("----------------------------------");
  212. }
  213. }
  214. /** * 为了拓展, HashMap子类可以创建自己的Node结点 * @param key * @param value * @param parent * @return */
  215. protected Node<K, V> createNode(K key, V value, Node<K, V> parent) {
  216. return new Node<>(key, value, parent);
  217. }
  218. /** * @param willNode 即将要删除的节点 * @param removeNode 实际删除的节点 */
  219. protected void afterChildRemove(Node<K, V> willNode, Node<K, V> removeNode) {
  220. }
  221. /** * 扩容 */
  222. private void resize() {
  223. // 装填因子 <= 0.75, 不扩容
  224. if (size / table.length <= DEFAULT_LOAD_FACTOR) return;
  225. Node<K, V>[] oldTable = table;
  226. table = new Node[oldTable.length << 1];
  227. Queue<Node<K, V>> queue = new LinkedList<>();
  228. for (int i = 0; i < oldTable.length; i++) {
  229. if (oldTable[i] == null) continue;
  230. // 层序遍历
  231. queue.offer(oldTable[i]);
  232. while (!queue.isEmpty()) {
  233. Node<K, V> node = queue.poll();
  234. if (node.left != null) {
  235. queue.offer(node.left);
  236. }
  237. if (node.right != null) {
  238. queue.offer(node.right);
  239. }
  240. // 挪动代码,放到后面
  241. moveNode(node);
  242. }
  243. }
  244. }
  245. /** * 将之前哈希表中的节点, 挪动到扩容后的哈希表中 * * @param newNode */
  246. private void moveNode(Node<K, V> newNode) {
  247. // 首先重置挪动过来node的left,right,parent
  248. newNode.parent = null;
  249. newNode.left = null;
  250. newNode.right = null;
  251. newNode.color = RED;
  252. // 哈希表中的索引
  253. int index = index(newNode);
  254. // 取出index位置(数组中)的红黑树根节点(因为哈希表中存储的就是红黑树的根节点(键值对))
  255. Node<K, V> root = table[index];
  256. if (root == null) {
  257. root = newNode;
  258. table[index] = root;
  259. afterPut(root);
  260. return;
  261. }
  262. Node<K, V> parent = root; // 这个是第一次比较的父节点
  263. Node<K, V> node = root;
  264. int cmp = 0;
  265. K k1 = newNode.key;
  266. int h1 = newNode.hash;
  267. do {
  268. parent = node; // 记录其每一次比较的父节点
  269. K k2 = node.key;
  270. int h2 = node.hash;
  271. // 先比较哈希值, 因为是挪动, 所以不用equals比较key, 肯定没有相同的key
  272. if (h1 > h2) {
  273. cmp = 1;
  274. } else if (h1 < h2) {
  275. cmp = -1;
  276. } else if (k1 != null && k2 != null
  277. && k1.getClass() == k2.getClass()
  278. && k1 instanceof Comparable
  279. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
  280. } else {
  281. cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
  282. }
  283. if (cmp > 0) {
  284. // 插入的元素大于根节点的元素,插入到根节点的右边
  285. node = node.right;
  286. } else if (cmp < 0) {
  287. // 插入的元素小于根节点的元素,插入到根节点的左边
  288. node = node.left;
  289. }
  290. } while (node != null);
  291. // 看看插入到父节点的哪个位置
  292. newNode.parent = parent;
  293. if (cmp > 0) {
  294. parent.right = newNode;
  295. } else {
  296. parent.left = newNode;
  297. }
  298. // 添加节点之后的逻辑
  299. afterPut(newNode);
  300. }
  301. protected V remove(Node<K, V> node) {
  302. if (node == null) return null;
  303. Node<K, V> willNode = node; // 本来要删除的节点,由于红黑树中度为2的节点删除方式, 和链表中的删除方式不同,所以要做交换
  304. // node 不为空, 必然要删除结点, 先size--;
  305. size--;
  306. V oldValue = node.value;
  307. // 删除node是度为2的结点
  308. if (node.hasTwoChildren()) {
  309. /*//1 找到后继(也可以找到前驱) Node<E> successor = successor(node); //2 用后继结点的值覆盖度为2结点的值 node.element = successor.element; //3 删除后继节点 node = successor;*/
  310. //1、找到前驱
  311. Node<K, V> predecessor = predecessor(node);
  312. //2、用前驱节点的值覆盖度为2节点的值
  313. node.key = predecessor.key;
  314. node.value = predecessor.value;
  315. node.hash = predecessor.hash;
  316. //3、删除前驱节点
  317. node = predecessor;
  318. }
  319. // 删除node,即删除后继节点 (node节点必然是度为1或0)
  320. // 因为node只有一个子节点/0个子节点, 如果其left!=null, 则用node.left来替代, node.left==null, 用node.right来替代,
  321. // 若node为叶子节点, 说明, node.left==null, node.right也为null, 则replacement==null;
  322. Node<K, V> replacement = node.left != null ? node.left : node.right;
  323. // 获取要删除节点的索引(就是红黑树所在桶的索引)
  324. int index = index(node);
  325. // 删除node是度为1的结点
  326. if (replacement != null) {
  327. // 更改parent
  328. replacement.parent = node.parent;
  329. // 更改parent的left、right的指向
  330. if (node.parent == null) { // node是度为1且是根节点
  331. table[index] = replacement;
  332. } else if (node == node.parent.left) {
  333. node.parent.left = replacement;
  334. } else if (node == node.parent.right) {
  335. node.parent.right = replacement;
  336. }
  337. // 删除结点之后的处理
  338. afterRemove(node, replacement);
  339. // 删除node是叶子节点, 且是根节点
  340. } else if (node.parent == null) {
  341. table[index] = null;
  342. // 删除结点之后的处理
  343. afterRemove(node, null);
  344. } else { // node是叶子结点, 且不是根节点
  345. if (node == node.parent.left) {
  346. node.parent.left = null;
  347. } else { // node == node.parent.right
  348. node.parent.right = null;
  349. }
  350. // 删除结点之后的处理
  351. afterRemove(node, null);
  352. }
  353. // 交给子类处理的
  354. afterChildRemove(willNode, node);
  355. return oldValue;
  356. }
  357. /** * 根据一个key, 找到对应的节点 * * @param key * @return */
  358. private Node<K, V> node(K key) {
  359. Node<K, V> root = table[index(key)];
  360. return root == null ? null : node(root, key);
  361. }
  362. private Node<K, V> node(Node<K, V> node, K k1) {
  363. //int h1 = k1 == null ? 0 : k1.hashCode();
  364. int h1 = hash(k1);
  365. // 存储查找结果
  366. Node<K, V> result = null;
  367. int cmp = 0;
  368. // 递归去左右子树查找
  369. while (node != null) {
  370. K k2 = node.key;
  371. int h2 = node.hash;
  372. // 先比较哈希值
  373. if (h1 > h2) {
  374. node = node.right;
  375. } else if (h1 < h2) {
  376. node = node.left;
  377. // 哈希值相同, 看看k1,k2是否相同,如果相同,则表示找到
  378. } else if (Objects.equals(k1, k2)) {
  379. return node;
  380. // 哈希值相同, equals不同, 看看是否具备可比较性
  381. } else if (k1 != null && k2 != null
  382. && k1.getClass() == k2.getClass()
  383. && k1 instanceof Comparable
  384. && (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
  385. // 具备可比较性
  386. //cmp = ((Comparable) k1).compareTo(k2); // 和put相同,compareTo相同不应该确定key就相同,应该继续向下搜索节点的key
  387. if (cmp > 0) {
  388. node = node.right;
  389. } else if (cmp < 0) {
  390. node = node.left;
  391. }
  392. // else {
  393. // return node;
  394. // }
  395. // 哈希值相同,equals不同, 不具备可比较性
  396. } else if (node.right != null && (result = node(node.right, k1)) != null) {
  397. return result;
  398. } else {
  399. node = node.left; // 优化下面的6行代码, 减少一次递归调用
  400. }
  401. // } else if (node.left != null && (result = node(node.left, k1)) != null) {
  402. // return result;
  403. // } else {
  404. // // 没找到
  405. // return null;
  406. // }
  407. }
  408. return null;
  409. }
  410. /** * 计算key的索引(在哈希表(数组)的哪个索引位置) * * @param key * @return */
  411. private int index(K key) {
  412. // if (key == null) return 0; // 如果key为空, 插入到哈希表的0位置
  413. // int hash = key.hashCode();
  414. // // 同Double,Long的hashCode类似实现,因为key.hashCode()是我们自己实现的,在JDK底层又作了一次混合运算
  415. // // 拿到我们自己实现的hash值, 将hash值和hash值无符号右移16位再做一次运算
  416. // hash = hash ^ (hash >>> 16);
  417. // return hash & (table.length - 1);
  418. return hash(key) & (table.length - 1);
  419. }
  420. /** * 扰动计算哈希值 * * @param key * @return */
  421. private int hash(K key) {
  422. if (key == null) return 0;
  423. int hash = key.hashCode();
  424. return hash ^ (hash >>> 16);
  425. }
  426. /** * 根据传入的node,计算它在数组中的哪个索引位置(即使它不是根节点) * * @param node * @return */
  427. private int index(Node<K, V> node) {
  428. return node.hash & (table.length - 1);
  429. }
  430. private void afterPut(Node<K, V> node) {
  431. Node<K, V> parent = node.parent;
  432. // 添加的是根节点(染成黑色)
  433. if (parent == null) {
  434. black(node);
  435. return;
  436. }
  437. // ------------- 一共 12 种情况--------------
  438. // 不需要处理的4种情况: 如果父节点是黑色, 直接返回
  439. if (isBlack(parent)) return;
  440. // 根据uncle节点的颜色来判断其他的各4种情况
  441. Node<K, V> uncle = parent.sibling();
  442. // 祖父节点
  443. Node<K, V> grand = parent.parent;
  444. // 需要处理的4种情况: 叔父节点是红色
  445. if (isRed(uncle)) {
  446. black(parent);
  447. black(uncle);
  448. // 把祖父节点染成红色, 当做新添加的节点处理(递归调用afterAdd)
  449. afterPut(red(grand));
  450. return;
  451. }
  452. /* 因为这4种情况, RBTree需要对节点进行旋转操作; 此时就需要使用到AVLTree中的旋转代码, 因为AVLTree和RBTree都是平衡二叉搜索树(BalanceBinarySearchTree),BBST在BST的基础上增加了旋转功能; 为了程序的拓展性, 我们在创建一个BBST 继承 BST, AVLTree和RBTree再 继承 BBST */
  453. // 需要处理的4种情况: 叔父节点不是红色(叔父节点为空)
  454. if (parent.isLeftChild()) { // L
  455. // LL,LR, grand都要染成红色
  456. red(grand);
  457. if (node.isLeftChild()) { // LL
  458. black(parent);
  459. } else { // LR
  460. black(node);
  461. rotateLeft(parent);
  462. }
  463. // LL,LR, grand最后都要右旋转
  464. rotateRight(grand);
  465. } else { // R
  466. red(grand);
  467. if (node.isLeftChild()) { // RL
  468. black(node);
  469. rotateRight(parent);
  470. } else { // RR
  471. black(parent);
  472. }
  473. rotateLeft(grand);
  474. }
  475. }
  476. private void afterRemove(Node<K, V> node, Node<K, V> replacement) {
  477. // 删除的节点, 都是叶子节点
  478. // 如果删除的节点为红色,则不需要处理
  479. if (isRed(node)) return;
  480. // 用于取代node的节点replacement为红色
  481. if (isRed(replacement)) {
  482. // 将替代节点染为黑色
  483. black(replacement);
  484. return;
  485. }
  486. // 删除的是根节点
  487. Node<K, V> parent = node.parent;
  488. if (parent == null) return;
  489. // 删除黑色的叶子节点(肯定会下溢)
  490. // 判断被删除的node是左还是右(如果直接通过sibling()方法,拿到的不准确,因为在remove方法中已经将node置为null了,然后才调用的afterRemove
  491. boolean left = parent.left == null || node.isLeftChild();
  492. Node<K, V> sibling = left ? parent.right : parent.left;
  493. if (left) { // 被删除的节点在左边, 兄弟节点在右边
  494. if (isRed(sibling)) {
  495. black(sibling);
  496. red(parent);
  497. rotateLeft(parent);
  498. sibling = parent.right;
  499. }
  500. // 兄弟节点必然是黑色
  501. if (isBlack(sibling.left) && isBlack(sibling.right)) { // 表示node的黑兄弟节点的left,right子节点都是黑节点
  502. boolean parentBlack = isBlack(parent);
  503. black(parent);
  504. red(sibling);
  505. if (parentBlack) {
  506. afterRemove(parent, null);
  507. }
  508. } else { // 表示兄弟节点至少有一个红色子节点,可以向被删除节点的位置借一个节点
  509. if (isBlack(sibling.right)) {
  510. rotateRight(sibling);
  511. sibling = parent.right;
  512. }
  513. color(sibling, colorOf(parent));
  514. black(sibling.right);
  515. black(parent);
  516. rotateLeft(parent);
  517. }
  518. } else { // 被删除节点在右边, 兄弟节点在左边
  519. if (isRed(sibling)) { // 兄弟节点是红色
  520. black(sibling);
  521. red(parent);
  522. rotateRight(parent); // 旋转之后,改变兄弟节点,然后node的兄弟节点就为黑色了
  523. // 更换兄弟节点
  524. sibling = parent.left;
  525. }
  526. // 兄弟节点必然是黑色
  527. if (isBlack(sibling.left) && isBlack(sibling.right)) { // 表示node的黑兄弟节点的left,right子节点都是黑节点
  528. // 兄弟节点没有一个红色子节点(不能借一个节点给你), 父节点要向下跟node的兄弟节点合并
  529. /* 首先这里要判断父节点parent的颜色(如果为parent为红色,则根据B树红色节点向其黑色父节点合并原则,parent向下合并,肯定不会 发生下溢; 如果parent为黑色,则说明parent向下合并后,必然也会发生下溢,这里我们当作移除一个叶子节点处理,复用afterRemove */
  530. boolean parentBlack = isBlack(parent);
  531. // 下面两行染色的代码,是说明parent为红色的情况
  532. black(parent);
  533. red(sibling);
  534. if (parentBlack) {
  535. afterRemove(parent, null);
  536. }
  537. } else { // 表示兄弟节点至少有一个红色子节点,可以向被删除节点的位置借一个节点
  538. // 兄弟节点的左边是黑色, 先将兄弟节点左旋转; 旋转完之后和后面两种的处理方式相同,都是再对父节点进行右旋转
  539. if (isBlack(sibling.left)) {
  540. rotateLeft(sibling);
  541. sibling = parent.left; // 因为旋转之后,要更改node的sibling,才能复用下面的染色代码.不然出现bug
  542. }
  543. // 旋转之后的中心节点继承parent的颜色; 旋转之后的左右节点染为黑色
  544. // 先染色,再旋转: 肯定要先对node的sibling先染色
  545. color(sibling, colorOf(parent));
  546. black(sibling.left);
  547. black(parent);
  548. rotateRight(parent);
  549. }
  550. }
  551. }
  552. /** * 对node进行左旋转 * * @param grand */
  553. private void rotateLeft(Node<K, V> grand) {
  554. Node<K, V> parent = grand.right;
  555. Node<K, V> child = parent.left;
  556. grand.right = child;
  557. parent.left = grand;
  558. afterRotate(grand, parent, child);
  559. }
  560. /** * 对node进行右旋转 * * @param grand */
  561. private void rotateRight(Node<K, V> grand) {
  562. Node<K, V> parent = grand.left;
  563. Node<K, V> child = parent.right;
  564. grand.left = child;
  565. parent.right = grand;
  566. afterRotate(grand, parent, child);
  567. }
  568. /** * 旋转之后, 更新它们的parent; 并且更新旋转后的高度 * * @param grand * @param parent * @param child */
  569. private void afterRotate(Node<K, V> grand, Node<K, V> parent, Node<K, V> child) {
  570. // 让parent为子树的根节点
  571. parent.parent = grand.parent;
  572. // 如果grand是其父节点的left, 则将grand.parent.left = parent;
  573. if (grand.isLeftChild()) {
  574. grand.parent.left = parent;
  575. } else if (grand.isRightChild()) {
  576. grand.parent.right = parent;
  577. // grand是根节点
  578. } else {
  579. // 更改原来的红黑树根节点(就是哪个桶的位置)
  580. //table[index(grand.key)] = parent;
  581. table[index(grand)] = parent;
  582. }
  583. // 更新child的parent
  584. if (child != null)
  585. child.parent = grand;
  586. // 更新grand的parent
  587. grand.parent = parent;
  588. }
  589. /** * 根据传入的节点, 返回该节点的前驱节点 (中序遍历) * * @param node * @return */
  590. private Node<K, V> predecessor(Node<K, V> node) {
  591. if (node == null) return node;
  592. // (中序遍历)前驱节点在左子树当中(node.left.right.right.right...)
  593. Node<K, V> p = node.left;
  594. // 左子树存在
  595. if (p != null) {
  596. while (p.right != null) {
  597. p = p.right;
  598. }
  599. return p;
  600. }
  601. // 程序走到这里说明左子树不存在; 从父节点、祖父节点中寻找前驱节点
  602. /* * node的父节点不为空 && node是其父节点的左子树时. 就一直往上寻找它的父节点 * 因为node==node.parent.right, 说明你在你父节点的右边, 那么node.parent就是其node的前驱节点 */
  603. while (node.parent != null && node == node.parent.left) {
  604. node = node.parent;
  605. }
  606. // 能来到这里表示: 两种情况如下
  607. // node.parent == null 表示没有父节点(根节点),返回空 ==> return node.parent;
  608. // node==node.parent.right 说明你在你父节点的右边, 那么node.parent就是其node的前驱节点 ==> return node.parent;
  609. return node.parent;
  610. }
  611. /** * 将node染成color色 * * @param node 需要染色的节点 * @param color 染成的颜色 * @return 返回染色的节点 */
  612. private Node<K, V> color(Node<K, V> node, boolean color) {
  613. if (node == null) return node;
  614. node.color = color;
  615. return node;
  616. }
  617. /** * 传进来的节点染成黑色 * * @param node * @return */
  618. private Node<K, V> black(Node<K, V> node) {
  619. return color(node, BLACK);
  620. }
  621. /** * 传进来的节点染成红色 * * @param node * @return */
  622. private Node<K, V> red(Node<K, V> node) {
  623. return color(node, RED);
  624. }
  625. /** * 返回当前节点的颜色 * * @param node * @return 如果传入的节点为空, 默认为黑色 */
  626. private boolean colorOf(Node<K, V> node) {
  627. return node == null ? BLACK : node.color;
  628. }
  629. /** * 节点是否为黑色 * * @param node * @return */
  630. private boolean isBlack(Node<K, V> node) {
  631. return colorOf(node) == BLACK;
  632. }
  633. /** * 节点是否为红色 * * @param node * @return */
  634. private boolean isRed(Node<K, V> node) {
  635. return colorOf(node) == RED;
  636. }
  637. protected static class Node<K, V> {
  638. int hash;
  639. K key;
  640. V value;
  641. boolean color = RED;
  642. Node<K, V> left;
  643. Node<K, V> right;
  644. Node<K, V> parent;
  645. public Node(K key, V value, Node<K, V> parent) {
  646. this.key = key;
  647. int hash = key == null ? 0 : key.hashCode();
  648. this.hash = hash ^ (hash >>> 16);
  649. this.value = value;
  650. this.parent = parent;
  651. }
  652. public boolean isLeaf() {
  653. return left == null && right == null;
  654. }
  655. public boolean hasTwoChildren() {
  656. return left != null && right != null;
  657. }
  658. public boolean isLeftChild() {
  659. return parent != null && this == parent.left;
  660. }
  661. public boolean isRightChild() {
  662. return parent != null && this == parent.right;
  663. }
  664. public Node<K, V> sibling() {
  665. if (isLeftChild()) {
  666. return parent.right;
  667. }
  668. if (isRightChild()) {
  669. return parent.left;
  670. }
  671. return null;
  672. }
  673. @Override
  674. public String toString() {
  675. return "Node_" + key + "_" + value;
  676. }
  677. }
  678. }

发表评论

表情:
评论列表 (有 0 条评论,45人围观)

还没有评论,来说两句吧...

相关阅读