《恋上数据结构与算法》笔记(十四):集合 (Set) 和 映射 (Map)、TreeSet、TreeMap实现

刺骨的言语ヽ痛彻心扉 2022-12-27 07:43 37阅读 0赞

目录

具体代码在 : set-map, 欢迎star

  • 一、集合(Set)
  • 二、集合的接口设计
  • 三、集合的实现

    • 1、通过链表实现集合 (ListSet)
    • 2、通过红黑树实现集合 (TreeSet)
    • 3、通过链表和红黑树实现的Set, 性能对比
  • 四、映射(Map)
  • 五、映射的接口设计
  • 六、映射的实现(TreeMap)

    • 1、声明节点
    • 2、put函数实现
    • 3、get函数实现
    • 4、remove函数实现
    • 5、contains函数实现
    • 6、traversal函数实现
    • 7、旋转代码
    • 8、寻找节点的前驱和后继
    • 9、红黑树辅助方法
    • 10、测试
  • 七、TreeMap代码(完整)

一、集合(Set)

特点: 不存放重复的元素, 元素唯一。

  • 常用于去重

    • 存放新增IP,统计新增IP量
    • 存放词汇,统计词汇量
  • 集合(Set)的内部实现能使用哪些数据结构?

    • 动态数组
    • 链表
    • 二叉搜索树(AVL树,红黑树

二、集合的接口设计

  1. /** * Description: Set集合 * * 这里为Set集合, 提供了遍历集合的接口, 为什么我们之前的动态数组,链表没有提供呢, 因为它们有索引的概念, 可以通过for就可以遍历。 * 然而Set集合, 没有索引的概念, 所以要提供遍历接口。 * * @author guizy * @date 2020/12/8 22:27 */
  2. public interface Set<E> {
  3. int size();
  4. boolean isEmpty();
  5. void clear();
  6. boolean contains(E element);
  7. void add(E element);
  8. void remove(E element);
  9. void traversal(Visitor<E> visitor); //遍历集合
  10. public static abstract class Visitor<E> {
  11. boolean stop;
  12. public abstract boolean visit(E element);
  13. }
  14. }

三、集合的实现

1、通过链表来实现 (ListSet)

  • 我们要使用之前实现过的链表相关的代码, 所以需要之前写的List, AbstractList,DoubleLinkedList. 请参考 小码哥《恋上数据结构与算法》笔记(二):链表

    具体代码在 LinkedList , 欢迎 star
    在这里插入图片描述

  • 复杂度为O(n)

    /* Description: 使用双向链表实现Set @author guizy @date 2020/12/8 22:32 /
    public class ListSet implements Set {

    1. private List<E> list = new DoubleLinkedList<>();
    2. @Override
    3. public int size() {
    4. return list.size();
    5. }
    6. @Override
    7. public boolean isEmpty() {
    8. return list.inEmpty();
    9. }
    10. @Override
    11. public void clear() {
    12. list.clear();
    13. }
    14. @Override
    15. public boolean contains(E element) {
    16. return list.contains(element);
    17. }
    18. @Override
    19. public void add(E element) {
    20. // 方式1: 不添加重复的元素
    21. // if (list.contains(element)) return;
    22. // list.add(element);
    23. // 方式2: 新元素覆盖掉旧的
    24. int index = list.indexOf(element);
    25. if (index != list.ELEMENT_NOT_FOUNT) { // 存在就覆盖
    26. list.set(index, element);
    27. } else { // 不存在就添加
    28. list.add(element);
    29. }
    30. }
    31. @Override
    32. public void remove(E element) {
    33. int index = list.indexOf(element);
    34. if (index != list.ELEMENT_NOT_FOUNT) {
    35. list.remove(index);
    36. }
    37. }
    38. @Override
    39. public void traversal(Visitor<E> visitor) {
    40. if (visitor == null) return;
    41. int size = list.size();
    42. for (int i = 0; i < size; i++) {
    43. if (visitor.visit(list.get(i))) return;
    44. }
    45. }

    }

2、通过红黑树来实现 (TreeSet)

  • 我们要使用之前实现过的红黑树相关的代码, 所以需要之前写的BinaryTree, BST,BBST,RBTree. 请参考 《恋上数据结构与算法》笔记(十三):红黑树

    具体代码在 : RBTree, 欢迎star
    在这里插入图片描述

  • 复杂度为O(logn)
  • 元素必须具备可比较性,否则只能使用哈希表

    /* Description: 使用红黑树实现Set @author guizy @date 2020/12/8 23:33 /
    public class TreeSet implements Set {

    1. private RBTree<E> tree;
    2. public TreeSet() {
    3. this(null);
    4. }
    5. public TreeSet(Comparator<E> comparator) {
    6. tree = new RBTree<>(comparator);
    7. }
    8. @Override
    9. public int size() {
    10. return tree.size();
    11. }
    12. @Override
    13. public boolean isEmpty() {
    14. return tree.isEmpty();
    15. }
    16. @Override
    17. public void clear() {
    18. tree.clear();
    19. }
    20. @Override
    21. public boolean contains(E element) {
    22. return tree.contains(element);
    23. }
    24. @Override
    25. public void add(E element) {
    26. // RBTree中默认就是去重的
    27. tree.add(element);
    28. }
    29. @Override
    30. public void remove(E element) {
    31. tree.remove(element);
    32. }
    33. @Override
    34. public void traversal(Visitor<E> visitor) {
    35. // 中序遍历
    36. tree.inorderTraversal(new BinaryTree.Visitor<E>() {
    37. @Override
    38. public boolean visit(E element) {
    39. return visitor.visit(element);
    40. }
    41. });
    42. }

    }

3、通过链表和红黑树实现的Set, 性能对比

  • 通过添加、搜索、删除 大批量数据来测试, 将jdk中源码src目录下的文件遍历, 通过单词量, 代码量, 文件数目多维度来检测两者性能; 这里不提供工具类代码, 具体看文章开篇提供的源码。
  • 对比ListSet, TreeSet遍历查询src/java/util的单词量, 代码量, 文件数
  • 测试代码如下

    public void testSet(Set set, String[] words) {

    1. for (int i = 0; i < words.length; i++) {
    2. set.add(words[i]);
    3. }
    4. for (int i = 0; i < words.length; i++) {
    5. set.contains(words[i]);
    6. }
    7. for (int i = 0; i < words.length; i++) {
    8. set.remove(words[i]);
    9. }

    }

    @Test
    public void testSrcWords() {

    1. FileInfo fileInfo = Files.read("C:\\Users\\guizy1\\Desktop\\src\\java\\util", new String[]{ "java"});
    2. System.out.println("文件数量:" + fileInfo.getFiles());
    3. System.out.println("代码行数:" + fileInfo.getLines());
    4. String[] words = fileInfo.words();
    5. System.out.println("单词数量:" + words.length);
    6. // 使用链表实现的ListSet来测试
    7. Times.test("ListSet", new Times.Task() {
    8. @Override
    9. public void execute() {
    10. testSet(new ListSet<>(), words);
    11. }
    12. });
    13. // 使用红黑树实现的TreeSet来测试
    14. // Times.test("TreeSet", () -> testSet(new TreeSet<>(), words));

    }

  • 使用ListSet测试的结果信息

    文件数量:364
    代码行数:211670
    单词数量:875925
    【ListSet】
    开始:10:23:01.699
    结束:10:24:03.611

    耗时:61.911秒

  • 使用TreeSet测试的结果信息

    文件数量:364
    代码行数:211670
    单词数量:875925
    【TreeSet】
    开始:10:30:00.465
    结束:10:30:01.006

    耗时:0.541秒

  • 可以发现使用红黑树实现的Set, 和使用链表实现的Set, 它们的性能差的非常多; 同样也证明了红黑树的效率是非常快的

  • 所以在JDK集合中的HashMap, TreeMap, TreeSet底层都是使用的红黑树来实现。

四、映射(Map)

  • Map在有些编程语言中也叫做字典(dictionary), 比如 Python, Objective-C, Swift
  • Map的每一个key唯一
    在这里插入图片描述
  • 类似Set,Map可以直接利用之前学习的链表二叉搜索树(AVL树,红黑树)等数据结构来实现。
  • 使用链表 : 添加,删除,搜索的时间复杂度是O(n)
  • 使用红黑树: 添加,删除,搜索的时间复杂度是O(logn)
  • 特点

    • key必须具备可比较性
    • 元素的分布是有顺序的(key大的在右边,小的在左边)。
  • 在实际应用中:

    • map中的存储的元素不需要讲究顺序。
    • map中的key一般不需要具备可比较性。
  • 不考虑顺序key的可比较性,map有更优的实现方案,平均时间复杂度可达到O(1),那就是采取哈希表来实现map

五、映射的接口设计

  1. /** * Description: Map接口 * * @author guizy * @date 2020/12/9 15:38 */
  2. public interface Map<K, V> {
  3. int size();
  4. boolean isEmpty();
  5. void clear();
  6. V put(K key, V value); //添加元素
  7. V get(K key);
  8. V remove(K key);
  9. boolean containsKey(K key); //查找key是否存在
  10. boolean containsValue(V value); //查找value是否存在
  11. void traversal(Visitor<K, V> visitor); //元素遍历
  12. public static abstract class Visitor<K, V> {
  13. boolean stop;
  14. public abstract boolean visit(K key, V value);
  15. }
  16. }

六、映射的实现(TreeMap)

  • 做法是将映射Map直接通过红黑树来实现,而不仅仅是通过红黑树存储映射的值, 而是红黑树的节点来存储一个Node<K, V>, 也就是一个键值对
  • 下面实现的TreeMap的红黑树代码, 全部揉合在TreeMap中, 对之前BinaryTree, BST, BBST, RBTree的代码进行抽取, 放在TreeMap中

1、声明节点 Node

  1. private static class Node<K, V> {
  2. K key;
  3. V value;
  4. boolean color = RED;
  5. Node<K, V> left;
  6. Node<K, V> right;
  7. Node<K, V> parent;
  8. public Node(K key, V value, Node<K, V> parent) {
  9. this.key = key;
  10. this.value = value;
  11. this.parent = parent;
  12. }
  13. public boolean isLeaf() {
  14. return left == null && right == null;
  15. }
  16. public boolean hasTwoChildren() {
  17. return left != null && right != null;
  18. }
  19. public boolean isLeftChild() {
  20. return parent != null && this == parent.left;
  21. }
  22. public boolean isRightChild() {
  23. return parent != null && this == parent.right;
  24. }
  25. public Node<K, V> sibling() {
  26. if (isLeftChild()) {
  27. return parent.right;
  28. }
  29. if (isRightChild()) {
  30. return parent.left;
  31. }
  32. return null;
  33. }
  34. }

2、put方法实现

  1. private void keyNotNullCheck(K key) {
  2. if (key == null) {
  3. // 手动抛出异常对象
  4. throw new IllegalArgumentException("key must not be null");
  5. }
  6. }
  7. /** * 想TreeMap中添加一个结点 * * @param key 键 * @param value 值 * @return 返回之前被覆盖的值 */
  8. @Override
  9. public V put(K key, V value) {
  10. keyNotNullCheck(key);
  11. // 添加第一个节点
  12. if (root == null) {
  13. // 给根节点赋值,且根节点没有父节点
  14. root = new Node<>(key, value, null);
  15. size++;
  16. // 添加节点之后的处理
  17. afterPut(root);
  18. return null;
  19. }
  20. // 添加的不是第一个节点
  21. Node<K, V> parent = root; // 这个是第一次比较的父节点
  22. Node<K, V> node = root;
  23. int cmp = 0;
  24. while (node != null) {
  25. cmp = compare(key, node.key); // 两者具体比较的方法
  26. parent = node; // 记录其每一次比较的父节点
  27. if (cmp > 0) {
  28. // 插入的元素大于根节点的元素,插入到根节点的右边
  29. node = node.right;
  30. } else if (cmp < 0) {
  31. // 插入的元素小于根节点的元素,插入到根节点的左边
  32. node = node.left;
  33. } else { // 相等
  34. node.key = key;
  35. V oldValue = node.value;
  36. node.value = value;
  37. return oldValue; // 返回之前node的value
  38. }
  39. }
  40. // 看看插入到父节点的哪个位置
  41. Node<K, V> newNode = new Node<>(key, value, parent);
  42. if (cmp > 0) {
  43. parent.right = newNode;
  44. } else {
  45. parent.left = newNode;
  46. }
  47. size++;
  48. // 添加节点之后的逻辑
  49. afterPut(newNode);
  50. // 这里的key是第一次加进去的, 之前没有值, 所以返回null
  51. return null;
  52. }
  53. // --------------------------------------------
  54. private void afterPut(Node<K, V> node) {
  55. Node<K, V> parent = node.parent;
  56. // 添加的是根节点(染成黑色)
  57. if (parent == null) {
  58. black(node);
  59. return;
  60. }
  61. // ------------- 一共 12 种情况--------------
  62. // 不需要处理的4种情况: 如果父节点是黑色, 直接返回
  63. if (isBlack(parent)) return;
  64. // 根据uncle节点的颜色来判断其他的各4种情况
  65. Node<K, V> uncle = parent.sibling();
  66. // 祖父节点
  67. Node<K, V> grand = parent.parent;
  68. // 需要处理的4种情况: 叔父节点是红色
  69. if (isRed(uncle)) {
  70. black(parent);
  71. black(uncle);
  72. // 把祖父节点染成红色, 当做新添加的节点处理(递归调用afterAdd)
  73. afterPut(red(grand));
  74. return;
  75. }
  76. /* 因为这4种情况, RBTree需要对节点进行旋转操作; 此时就需要使用到AVLTree中的旋转代码, 因为AVLTree和RBTree都是平衡二叉搜索树(BalanceBinarySearchTree),BBST在BST的基础上增加了旋转功能; 为了程序的拓展性, 我们在创建一个BBST 继承 BST, AVLTree和RBTree再 继承 BBST */
  77. // 需要处理的4种情况: 叔父节点不是红色(叔父节点为空)
  78. if (parent.isLeftChild()) { // L
  79. // LL,LR, grand都要染成红色
  80. red(grand);
  81. if (node.isLeftChild()) { // LL
  82. black(parent);
  83. } else { // LR
  84. black(node);
  85. rotateLeft(parent);
  86. }
  87. // LL,LR, grand最后都要右旋转
  88. rotateRight(grand);
  89. } else { // R
  90. red(grand);
  91. if (node.isLeftChild()) { // RL
  92. black(node);
  93. rotateRight(parent);
  94. } else { // RR
  95. black(parent);
  96. }
  97. rotateLeft(grand);
  98. }
  99. }
  100. /** * @return 返回值等于0, 代表e1=e2 * 大于0,代表e1>e2 * 小于0,代表e1<e2 */
  101. private int compare(K k1, K k2) {
  102. if (comparator != null) { // 这里表示传入了比较器
  103. // 优先使用比较器
  104. return comparator.compare(k1, k2);
  105. }
  106. // 这里表示没有使用比较器,此时再强制将传入的元素实现Comparable接口,并重写接口中的方法
  107. //return ((Comparable<E>) k1).compareTo(k2);
  108. return ((Comparable<K>) k1).compareTo(k2);
  109. }

3、get方法实现

  • 通过key先找到node节点,然后再返回节点的值

    @Override
    public V get(K key) {

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

    }

4、remove方法实现

  • 先通过key找到节点,然后再删除节点。

    @Override
    public V remove(K key) {

    1. return remove(node(key));

    }

    private V remove(Node node) {

    1. if (node == null) return null;
    2. // node 不为空, 必然要删除结点, 先size--;
    3. size--;
    4. V oldValue = node.value;
    5. // 删除node是度为2的结点
    6. if (node.hasTwoChildren()) {
    7. /*//1 找到后继(也可以找到前驱) Node<E> successor = successor(node); //2 用后继结点的值覆盖度为2结点的值 node.element = successor.element; //3 删除后继节点 node = successor;*/
    8. //1、找到前驱
    9. Node<K, V> predecessor = predecessor(node);
    10. //2、用前驱节点的值覆盖度为2节点的值
    11. node.key = predecessor.key;
    12. node.value = predecessor.value;
    13. //3、删除前驱节点
    14. node = predecessor;
    15. }
    16. // 删除node,即删除后继节点 (node节点必然是度为1或0)
    17. // 因为node只有一个子节点/0个子节点, 如果其left!=null, 则用node.left来替代, node.left==null, 用node.right来替代,
    18. // 若node为叶子节点, 说明, node.left==null, node.right也为null, 则replacement==null;
    19. Node<K, V> replacement = node.left != null ? node.left : node.right;
    20. // 删除node是度为1的结点
    21. if (replacement != null) {
    22. // 更改parent
    23. replacement.parent = node.parent;
    24. // 更改parent的left、right的指向
    25. if (node.parent == null) { // node是度为1且是根节点
    26. root = replacement;
    27. } else if (node == node.parent.left) {
    28. node.parent.left = replacement;
    29. } else if (node == node.parent.right) {
    30. node.parent.right = replacement;
    31. }
    32. // 删除结点之后的处理
    33. afterRemove(node, replacement);
    34. // 删除node是叶子节点, 且是根节点
    35. } else if (node.parent == null) {
    36. root = null;
    37. // 删除结点之后的处理
    38. afterRemove(node, null);
    39. } else { // node是叶子结点, 且不是根节点
    40. if (node == node.parent.left) {
    41. node.parent.left = null;
    42. } else { // node == node.parent.right
    43. node.parent.right = null;
    44. }
    45. // 删除结点之后的处理
    46. afterRemove(node, null);
    47. }
    48. return oldValue;

    }

    private void afterRemove(Node node, Node replacement) {

    1. // 删除的节点, 都是叶子节点
    2. // 如果删除的节点为红色,则不需要处理
    3. if (isRed(node)) return;
    4. // 用于取代node的节点replacement为红色
    5. if (isRed(replacement)) {
    6. // 将替代节点染为黑色
    7. black(replacement);
    8. return;
    9. }
    10. // 删除的是根节点
    11. Node<K, V> parent = node.parent;
    12. if (parent == null) return;
    13. // 删除黑色的叶子节点(肯定会下溢)
    14. // 判断被删除的node是左还是右(如果直接通过sibling()方法,拿到的不准确,因为在remove方法中已经将node置为null了,然后才调用的afterRemove
    15. boolean left = parent.left == null || node.isLeftChild();
    16. Node<K, V> sibling = left ? parent.right : parent.left;
    17. if (left) { // 被删除的节点在左边, 兄弟节点在右边
    18. if (isRed(sibling)) {
    19. black(sibling);
    20. red(parent);
    21. rotateLeft(parent);
    22. sibling = parent.right;
    23. }
    24. // 兄弟节点必然是黑色
    25. if (isBlack(sibling.left) && isBlack(sibling.right)) { // 表示node的黑兄弟节点的left,right子节点都是黑节点
    26. boolean parentBlack = isBlack(parent);
    27. black(parent);
    28. red(sibling);
    29. if (parentBlack) {
    30. afterRemove(parent, null);
    31. }
    32. } else { // 表示兄弟节点至少有一个红色子节点,可以向被删除节点的位置借一个节点
    33. if (isBlack(sibling.right)) {
    34. rotateRight(sibling);
    35. sibling = parent.right;
    36. }
    37. color(sibling, colorOf(parent));
    38. black(sibling.right);
    39. black(parent);
    40. rotateLeft(parent);
    41. }
    42. } else { // 被删除节点在右边, 兄弟节点在左边
    43. if (isRed(sibling)) { // 兄弟节点是红色
    44. black(sibling);
    45. red(parent);
    46. rotateRight(parent); // 旋转之后,改变兄弟节点,然后node的兄弟节点就为黑色了
    47. // 更换兄弟节点
    48. sibling = parent.left;
    49. }
    50. // 兄弟节点必然是黑色
    51. if (isBlack(sibling.left) && isBlack(sibling.right)) { // 表示node的黑兄弟节点的left,right子节点都是黑节点
    52. // 兄弟节点没有一个红色子节点(不能借一个节点给你), 父节点要向下跟node的兄弟节点合并
    53. /* 首先这里要判断父节点parent的颜色(如果为parent为红色,则根据B树红色节点向其黑色父节点合并原则,parent向下合并,肯定不会 发生下溢; 如果parent为黑色,则说明parent向下合并后,必然也会发生下溢,这里我们当作移除一个叶子结点处理,复用afterRemove */
    54. boolean parentBlack = isBlack(parent);
    55. // 下面两行染色的代码,是说明parent为红色的情况
    56. black(parent);
    57. red(sibling);
    58. if (parentBlack) {
    59. afterRemove(parent, null);
    60. }
    61. } else { // 表示兄弟节点至少有一个红色子节点,可以向被删除节点的位置借一个节点
    62. // 兄弟节点的左边是黑色, 先将兄弟节点左旋转; 旋转完之后和后面两种的处理方式相同,都是再对父节点进行右旋转
    63. if (isBlack(sibling.left)) {
    64. rotateLeft(sibling);
    65. sibling = parent.left; // 因为旋转之后,要更改node的sibling,才能复用下面的染色代码.不然出现bug
    66. }
    67. // 旋转之后的中心节点继承parent的颜色; 旋转之后的左右节点染为黑色
    68. // 先染色,再旋转: 肯定要先对node的sibling先染色
    69. color(sibling, colorOf(parent));
    70. black(sibling.left);
    71. black(parent);
    72. rotateRight(parent);
    73. }
    74. }

    }

    /* 传入key找到对应红黑树对应的结点, 然后取出k红黑树节点的value @param key @return /
    private Node node(K key) {

    1. Node<K, V> node = root;
    2. while (node != null) {
    3. int cmp = compare(key, node.key);
    4. if (cmp == 0) return node;
    5. if (cmp > 0) { // 说明key对应的结点, 比node的key大, 所以去它的右子树找
    6. node = node.right;
    7. } else {
    8. node = node.left;
    9. }
    10. }
    11. return null; // 没有找到key对应的结点

    }

5、contains方法实现

  • 因为value没有可比较性,所以containsValue只有通过树的遍历来查找value是否存在。

    @Override
    public boolean containsKey(K key) {

    1. return node(key) != null;

    }

    @Override
    public boolean containsValue(V value) {

    1. if (root == null) return false;
    2. // 层序遍历代码
    3. Queue<Node<K, V>> queue = new LinkedList<>();
    4. queue.offer(root);
    5. while (!queue.isEmpty()) {
    6. Node<K, V> node = queue.poll();
    7. if (valEquals(value, node.value))
    8. return true; // 说明存在
    9. if (node.left != null)
    10. queue.offer(node.left);
    11. if (node.right != null)
    12. queue.offer(node.right);
    13. }
    14. return false;

    }

    private boolean valEquals(V v1, V v2) {

    1. // 如果v1==null,说明为true, 走v2==null, v2等于null的话, 说明v1和v2相等, v2不等于空的话, 返回false
    2. // 如果v1!=null, 直接判断v1.equals(v2)是否相等
    3. return v1 == null ? v2 == null : v1.equals(v2);

    }

6、traversal方法实现

  • 中序遍历

    @Override
    public void traversal(Visitor visitor) {

    1. if (visitor == null) return;
    2. traversal(root, visitor);

    }

    private void traversal(Node node, Visitor visitor) {

    1. if (node == null || visitor.stop) return;
    2. traversal(node.left, visitor);
    3. if (visitor.stop) return;
    4. visitor.visit(node.key, node.value);
    5. traversal(node.right, visitor);

    }

7、旋转代码

  1. /** * 对node进行左旋转 * * @param grand */
  2. private void rotateLeft(Node<K, V> grand) {
  3. Node<K, V> parent = grand.right;
  4. Node<K, V> child = parent.left;
  5. grand.right = child;
  6. parent.left = grand;
  7. afterRotate(grand, parent, child);
  8. }
  9. /** * 对node进行右旋转 * * @param grand */
  10. private void rotateRight(Node<K, V> grand) {
  11. Node<K, V> parent = grand.left;
  12. Node<K, V> child = parent.right;
  13. grand.left = child;
  14. parent.right = grand;
  15. afterRotate(grand, parent, child);
  16. }
  17. /** * 旋转之后, 更新它们的parent; 并且更新旋转后的高度 * * @param grand * @param parent * @param child */
  18. private void afterRotate(Node<K, V> grand, Node<K, V> parent, Node<K, V> child) {
  19. // 让parent为子树的根节点
  20. parent.parent = grand.parent;
  21. // 如果grand是其父节点的left, 则将grand.parent.left = parent;
  22. if (grand.isLeftChild()) {
  23. grand.parent.left = parent;
  24. } else if (grand.isRightChild()) {
  25. grand.parent.right = parent;
  26. // grand是根节点
  27. } else {
  28. root = parent;
  29. }
  30. // 更新child的parent
  31. if (child != null)
  32. child.parent = grand;
  33. // 更新grand的parent
  34. grand.parent = parent;
  35. }

8、寻找节点的前驱和后继

  1. /** * 根据传入的节点, 返回该节点的前驱节点 (中序遍历) * * @param node * @return */
  2. private Node<K, V> predecessor(Node<K, V> node) {
  3. if (node == null) return node;
  4. // (中序遍历)前驱节点在左子树当中(node.left.right.right.right...)
  5. Node<K, V> p = node.left;
  6. // 左子树存在
  7. if (p != null) {
  8. while (p.right != null) {
  9. p = p.right;
  10. }
  11. return p;
  12. }
  13. // 程序走到这里说明左子树不存在; 从父节点、祖父节点中寻找前驱节点
  14. /* * node的父节点不为空 && node是其父节点的左子树时. 就一直往上寻找它的父节点 * 因为node==node.parent.right, 说明你在你父节点的右边, 那么node.parent就是其node的前驱节点 */
  15. while (node.parent != null && node == node.parent.left) {
  16. node = node.parent;
  17. }
  18. // 能来到这里表示: 两种情况如下
  19. // node.parent == null 表示没有父节点(根节点),返回空 ==> return node.parent;
  20. // node==node.parent.right 说明你在你父节点的右边, 那么node.parent就是其node的前驱节点 ==> return node.parent;
  21. return node.parent;
  22. }
  23. /** * 根据传入的节点, 返回该节点的后驱节点 (中序遍历) * * @param node * @return */
  24. private Node<K, V> successor(Node<K, V> node) {
  25. if (node == null) return node;
  26. Node<K, V> p = node.right;
  27. if (p != null) {
  28. while (p.left != null) {
  29. p = p.left;
  30. }
  31. return p;
  32. }
  33. // node.right为空
  34. while (node.parent != null && node == node.parent.right) {
  35. node = node.parent;
  36. }
  37. return node.parent;
  38. }

9、红黑树辅助方法

  1. /** * 将node染成color色 * * @param node 需要染色的节点 * @param color 染成的颜色 * @return 返回染色的节点 */
  2. private Node<K, V> color(Node<K, V> node, boolean color) {
  3. if (node == null) return node;
  4. node.color = color;
  5. return node;
  6. }
  7. /** * 传进来的节点染成黑色 * * @param node * @return */
  8. private Node<K, V> black(Node<K, V> node) {
  9. return color(node, BLACK);
  10. }
  11. /** * 传进来的节点染成红色 * * @param node * @return */
  12. private Node<K, V> red(Node<K, V> node) {
  13. return color(node, RED);
  14. }
  15. /** * 返回当前节点的颜色 * * @param node * @return 如果传入的节点为空, 默认为黑色 */
  16. private boolean colorOf(Node<K, V> node) {
  17. return node == null ? BLACK : node.color;
  18. }
  19. /** * 节点是否为黑色 * * @param node * @return */
  20. private boolean isBlack(Node<K, V> node) {
  21. return colorOf(node) == BLACK;
  22. }
  23. /** * 节点是否为红色 * * @param node * @return */
  24. private boolean isRed(Node<K, V> node) {
  25. return colorOf(node) == RED;
  26. }

10、测试

  1. /** * Description: 测试自己实现的TreeMap * * @author guizy1 * @date 2020/12/9 15:43 */
  2. public class Main {
  3. // 测试TreeMap来实现TreeSet
  4. @Test
  5. public void test3() {
  6. Set<Integer> treeSet = new TreeSet<>();
  7. treeSet.add(1);
  8. treeSet.add(7);
  9. treeSet.add(3);
  10. treeSet.add(7);
  11. treeSet.add(0);
  12. treeSet.traversal(new Set.Visitor<Integer>() {
  13. @Override
  14. public boolean visit(Integer element) {
  15. System.out.println(element);
  16. return false;
  17. }
  18. });
  19. }
  20. @Test
  21. public void test1() {
  22. Map<String, Integer> map = new TreeMap<>();
  23. map.put("c", 2);
  24. map.put("a", 5);
  25. map.put("b", 6);
  26. map.put("a", 8);
  27. map.traversal(new Map.Visitor<String, Integer>() {
  28. @Override
  29. public boolean visit(String key, Integer value) {
  30. System.out.println(key + "_" + value);
  31. return false;
  32. }
  33. });
  34. }
  35. @Test
  36. public void test2() {
  37. FileInfo fileInfo = Files.read("D:\\tree-structure\\tree\\src",
  38. new String[]{ "java"});
  39. System.out.println("文件数量:" + fileInfo.getFiles());
  40. System.out.println("代码行数:" + fileInfo.getLines());
  41. String[] words = fileInfo.words();
  42. System.out.println("单词数量:" + words.length);
  43. Map<String, Integer> map = new TreeMap<>();
  44. for (int i = 0; i < words.length; i++) {
  45. Integer count = map.get(words[i]);
  46. // count = count == null ? 0 : count + 1;
  47. // map.put(words[i], count + 1);
  48. count = (count == null) ? 1 : (count + 1);
  49. map.put(words[i], count);
  50. }
  51. map.traversal(new Map.Visitor<String, Integer>() {
  52. public boolean visit(String key, Integer value) {
  53. System.out.println(key + "_" + value);
  54. return false;
  55. }
  56. });
  57. }
  58. }

七、TreeMap代码(完整)

  1. package com.map.
  2. import java.util.Comparator;
  3. import java.util.LinkedList;
  4. import java.util.Queue;
  5. /** * Description: TreeMap使用红黑树来实现, 红黑树的节点中存储的是key-value键值对 * * @author guizy1 * @date 2020/12/9 15:48 */
  6. @SuppressWarnings("all")
  7. public class TreeMap<K, V> implements Map<K, V> {
  8. private static final boolean RED = false;
  9. private static final boolean BLACK = true;
  10. private int size;
  11. // 定义根节点
  12. private Node<K, V> root;
  13. private Comparator<K> comparator; // 定义一个比较器
  14. public TreeMap() {
  15. this(null);
  16. }
  17. public TreeMap(Comparator<K> comparator) {
  18. this.comparator = comparator;
  19. }
  20. public int size() {
  21. return size;
  22. }
  23. public boolean isEmpty() {
  24. return size == 0;
  25. }
  26. public void clear() {
  27. root = null;
  28. size = 0;
  29. }
  30. /** * 想TreeMap中添加一个结点 * * @param key 键 * @param value 值 * @return 返回之前被覆盖的值 */
  31. @Override
  32. public V put(K key, V value) {
  33. keyNotNullCheck(key);
  34. // 添加第一个节点
  35. if (root == null) {
  36. // 给根节点赋值,且根节点没有父节点
  37. root = new Node<>(key, value, null);
  38. size++;
  39. // 添加节点之后的处理
  40. afterPut(root);
  41. return null;
  42. }
  43. // 添加的不是第一个节点
  44. Node<K, V> parent = root; // 这个是第一次比较的父节点
  45. Node<K, V> node = root;
  46. int cmp = 0;
  47. while (node != null) {
  48. cmp = compare(key, node.key); // 两者具体比较的方法
  49. parent = node; // 记录其每一次比较的父节点
  50. if (cmp > 0) {
  51. // 插入的元素大于根节点的元素,插入到根节点的右边
  52. node = node.right;
  53. } else if (cmp < 0) {
  54. // 插入的元素小于根节点的元素,插入到根节点的左边
  55. node = node.left;
  56. } else { // 相等
  57. node.key = key;
  58. V oldValue = node.value;
  59. node.value = value;
  60. return oldValue; // 返回之前node的value
  61. }
  62. }
  63. // 看看插入到父节点的哪个位置
  64. Node<K, V> newNode = new Node<>(key, value, parent);
  65. if (cmp > 0) {
  66. parent.right = newNode;
  67. } else {
  68. parent.left = newNode;
  69. }
  70. size++;
  71. // 添加节点之后的逻辑
  72. afterPut(newNode);
  73. // 这里的key是第一次加进去的, 之前没有值, 所以返回null
  74. return null;
  75. }
  76. @Override
  77. public V get(K key) {
  78. Node<K, V> node = node(key);
  79. return node != null ? node.value : null;
  80. }
  81. @Override
  82. public V remove(K key) {
  83. return remove(node(key));
  84. }
  85. @Override
  86. public boolean containsKey(K key) {
  87. return node(key) != null;
  88. }
  89. @Override
  90. public boolean containsValue(V value) {
  91. if (root == null) return false;
  92. // 层序遍历代码
  93. Queue<Node<K, V>> queue = new LinkedList<>();
  94. queue.offer(root);
  95. while (!queue.isEmpty()) {
  96. Node<K, V> node = queue.poll();
  97. if (valEquals(value, node.value))
  98. return true; // 说明存在
  99. if (node.left != null)
  100. queue.offer(node.left);
  101. if (node.right != null)
  102. queue.offer(node.right);
  103. }
  104. return false;
  105. }
  106. @Override
  107. public void traversal(Visitor<K, V> visitor) {
  108. if (visitor == null) return;
  109. traversal(root, visitor);
  110. }
  111. private void traversal(Node<K, V> node, Visitor<K, V> visitor) {
  112. if (node == null || visitor.stop) return;
  113. traversal(node.left, visitor);
  114. if (visitor.stop) return;
  115. visitor.visit(node.key, node.value);
  116. traversal(node.right, visitor);
  117. }
  118. private boolean valEquals(V v1, V v2) {
  119. // 如果v1==null,说明为true, 走v2==null, v2等于null的话, 说明v1和v2相等, v2不等于空的话, 返回false
  120. // 如果v1!=null, 直接判断v1.equals(v2)是否相等
  121. return v1 == null ? v2 == null : v1.equals(v2);
  122. }
  123. private void afterPut(Node<K, V> node) {
  124. Node<K, V> parent = node.parent;
  125. // 添加的是根节点(染成黑色)
  126. if (parent == null) {
  127. black(node);
  128. return;
  129. }
  130. // ------------- 一共 12 种情况--------------
  131. // 不需要处理的4种情况: 如果父节点是黑色, 直接返回
  132. if (isBlack(parent)) return;
  133. // 根据uncle节点的颜色来判断其他的各4种情况
  134. Node<K, V> uncle = parent.sibling();
  135. // 祖父节点
  136. Node<K, V> grand = parent.parent;
  137. // 需要处理的4种情况: 叔父节点是红色
  138. if (isRed(uncle)) {
  139. black(parent);
  140. black(uncle);
  141. // 把祖父节点染成红色, 当做新添加的节点处理(递归调用afterAdd)
  142. afterPut(red(grand));
  143. return;
  144. }
  145. /* 因为这4种情况, RBTree需要对节点进行旋转操作; 此时就需要使用到AVLTree中的旋转代码, 因为AVLTree和RBTree都是平衡二叉搜索树(BalanceBinarySearchTree),BBST在BST的基础上增加了旋转功能; 为了程序的拓展性, 我们在创建一个BBST 继承 BST, AVLTree和RBTree再 继承 BBST */
  146. // 需要处理的4种情况: 叔父节点不是红色(叔父节点为空)
  147. if (parent.isLeftChild()) { // L
  148. // LL,LR, grand都要染成红色
  149. red(grand);
  150. if (node.isLeftChild()) { // LL
  151. black(parent);
  152. } else { // LR
  153. black(node);
  154. rotateLeft(parent);
  155. }
  156. // LL,LR, grand最后都要右旋转
  157. rotateRight(grand);
  158. } else { // R
  159. red(grand);
  160. if (node.isLeftChild()) { // RL
  161. black(node);
  162. rotateRight(parent);
  163. } else { // RR
  164. black(parent);
  165. }
  166. rotateLeft(grand);
  167. }
  168. }
  169. private void afterRemove(Node<K, V> node, Node<K, V> replacement) {
  170. // 删除的节点, 都是叶子节点
  171. // 如果删除的节点为红色,则不需要处理
  172. if (isRed(node)) return;
  173. // 用于取代node的节点replacement为红色
  174. if (isRed(replacement)) {
  175. // 将替代节点染为黑色
  176. black(replacement);
  177. return;
  178. }
  179. // 删除的是根节点
  180. Node<K, V> parent = node.parent;
  181. if (parent == null) return;
  182. // 删除黑色的叶子节点(肯定会下溢)
  183. // 判断被删除的node是左还是右(如果直接通过sibling()方法,拿到的不准确,因为在remove方法中已经将node置为null了,然后才调用的afterRemove
  184. boolean left = parent.left == null || node.isLeftChild();
  185. Node<K, V> sibling = left ? parent.right : parent.left;
  186. if (left) { // 被删除的节点在左边, 兄弟节点在右边
  187. if (isRed(sibling)) {
  188. black(sibling);
  189. red(parent);
  190. rotateLeft(parent);
  191. sibling = parent.right;
  192. }
  193. // 兄弟节点必然是黑色
  194. if (isBlack(sibling.left) && isBlack(sibling.right)) { // 表示node的黑兄弟节点的left,right子节点都是黑节点
  195. boolean parentBlack = isBlack(parent);
  196. black(parent);
  197. red(sibling);
  198. if (parentBlack) {
  199. afterRemove(parent, null);
  200. }
  201. } else { // 表示兄弟节点至少有一个红色子节点,可以向被删除节点的位置借一个节点
  202. if (isBlack(sibling.right)) {
  203. rotateRight(sibling);
  204. sibling = parent.right;
  205. }
  206. color(sibling, colorOf(parent));
  207. black(sibling.right);
  208. black(parent);
  209. rotateLeft(parent);
  210. }
  211. } else { // 被删除节点在右边, 兄弟节点在左边
  212. if (isRed(sibling)) { // 兄弟节点是红色
  213. black(sibling);
  214. red(parent);
  215. rotateRight(parent); // 旋转之后,改变兄弟节点,然后node的兄弟节点就为黑色了
  216. // 更换兄弟节点
  217. sibling = parent.left;
  218. }
  219. // 兄弟节点必然是黑色
  220. if (isBlack(sibling.left) && isBlack(sibling.right)) { // 表示node的黑兄弟节点的left,right子节点都是黑节点
  221. // 兄弟节点没有一个红色子节点(不能借一个节点给你), 父节点要向下跟node的兄弟节点合并
  222. /* 首先这里要判断父节点parent的颜色(如果为parent为红色,则根据B树红色节点向其黑色父节点合并原则,parent向下合并,肯定不会 发生下溢; 如果parent为黑色,则说明parent向下合并后,必然也会发生下溢,这里我们当作移除一个叶子结点处理,复用afterRemove */
  223. boolean parentBlack = isBlack(parent);
  224. // 下面两行染色的代码,是说明parent为红色的情况
  225. black(parent);
  226. red(sibling);
  227. if (parentBlack) {
  228. afterRemove(parent, null);
  229. }
  230. } else { // 表示兄弟节点至少有一个红色子节点,可以向被删除节点的位置借一个节点
  231. // 兄弟节点的左边是黑色, 先将兄弟节点左旋转; 旋转完之后和后面两种的处理方式相同,都是再对父节点进行右旋转
  232. if (isBlack(sibling.left)) {
  233. rotateLeft(sibling);
  234. sibling = parent.left; // 因为旋转之后,要更改node的sibling,才能复用下面的染色代码.不然出现bug
  235. }
  236. // 旋转之后的中心节点继承parent的颜色; 旋转之后的左右节点染为黑色
  237. // 先染色,再旋转: 肯定要先对node的sibling先染色
  238. color(sibling, colorOf(parent));
  239. black(sibling.left);
  240. black(parent);
  241. rotateRight(parent);
  242. }
  243. }
  244. }
  245. private V remove(Node<K, V> node) {
  246. if (node == null) return null;
  247. // node 不为空, 必然要删除结点, 先size--;
  248. size--;
  249. V oldValue = node.value;
  250. // 删除node是度为2的结点
  251. if (node.hasTwoChildren()) {
  252. /*//1 找到后继(也可以找到前驱) Node<E> successor = successor(node); //2 用后继结点的值覆盖度为2结点的值 node.element = successor.element; //3 删除后继节点 node = successor;*/
  253. //1、找到前驱
  254. Node<K, V> predecessor = predecessor(node);
  255. //2、用前驱节点的值覆盖度为2节点的值
  256. node.key = predecessor.key;
  257. node.value = predecessor.value;
  258. //3、删除前驱节点
  259. node = predecessor;
  260. }
  261. // 删除node,即删除后继节点 (node节点必然是度为1或0)
  262. // 因为node只有一个子节点/0个子节点, 如果其left!=null, 则用node.left来替代, node.left==null, 用node.right来替代,
  263. // 若node为叶子节点, 说明, node.left==null, node.right也为null, 则replacement==null;
  264. Node<K, V> replacement = node.left != null ? node.left : node.right;
  265. // 删除node是度为1的结点
  266. if (replacement != null) {
  267. // 更改parent
  268. replacement.parent = node.parent;
  269. // 更改parent的left、right的指向
  270. if (node.parent == null) { // node是度为1且是根节点
  271. root = replacement;
  272. } else if (node == node.parent.left) {
  273. node.parent.left = replacement;
  274. } else if (node == node.parent.right) {
  275. node.parent.right = replacement;
  276. }
  277. // 删除结点之后的处理
  278. afterRemove(node, replacement);
  279. // 删除node是叶子节点, 且是根节点
  280. } else if (node.parent == null) {
  281. root = null;
  282. // 删除结点之后的处理
  283. afterRemove(node, null);
  284. } else { // node是叶子结点, 且不是根节点
  285. if (node == node.parent.left) {
  286. node.parent.left = null;
  287. } else { // node == node.parent.right
  288. node.parent.right = null;
  289. }
  290. // 删除结点之后的处理
  291. afterRemove(node, null);
  292. }
  293. return oldValue;
  294. }
  295. /** * 根据传入的节点, 返回该节点的前驱节点 (中序遍历) * * @param node * @return */
  296. private Node<K, V> predecessor(Node<K, V> node) {
  297. if (node == null) return node;
  298. // (中序遍历)前驱节点在左子树当中(node.left.right.right.right...)
  299. Node<K, V> p = node.left;
  300. // 左子树存在
  301. if (p != null) {
  302. while (p.right != null) {
  303. p = p.right;
  304. }
  305. return p;
  306. }
  307. // 程序走到这里说明左子树不存在; 从父节点、祖父节点中寻找前驱节点
  308. /* * node的父节点不为空 && node是其父节点的左子树时. 就一直往上寻找它的父节点 * 因为node==node.parent.right, 说明你在你父节点的右边, 那么node.parent就是其node的前驱节点 */
  309. while (node.parent != null && node == node.parent.left) {
  310. node = node.parent;
  311. }
  312. // 能来到这里表示: 两种情况如下
  313. // node.parent == null 表示没有父节点(根节点),返回空 ==> return node.parent;
  314. // node==node.parent.right 说明你在你父节点的右边, 那么node.parent就是其node的前驱节点 ==> return node.parent;
  315. return node.parent;
  316. }
  317. /** * 根据传入的节点, 返回该节点的后驱节点 (中序遍历) * * @param node * @return */
  318. private Node<K, V> successor(Node<K, V> node) {
  319. if (node == null) return node;
  320. Node<K, V> p = node.right;
  321. if (p != null) {
  322. while (p.left != null) {
  323. p = p.left;
  324. }
  325. return p;
  326. }
  327. // node.right为空
  328. while (node.parent != null && node == node.parent.right) {
  329. node = node.parent;
  330. }
  331. return node.parent;
  332. }
  333. /** * 传入key找到对应红黑树对应的结点, 然后取出k红黑树节点的value * * @param key * @return */
  334. private Node<K, V> node(K key) {
  335. Node<K, V> node = root;
  336. while (node != null) {
  337. int cmp = compare(key, node.key);
  338. if (cmp == 0) return node;
  339. if (cmp > 0) { // 说明key对应的结点, 比node的key大, 所以去它的右子树找
  340. node = node.right;
  341. } else {
  342. node = node.left;
  343. }
  344. }
  345. return null; // 没有找到key对应的结点
  346. }
  347. /** * @return 返回值等于0, 代表e1=e2 * 大于0,代表e1>e2 * 小于0,代表e1<e2 */
  348. private int compare(K k1, K k2) {
  349. if (comparator != null) { // 这里表示传入了比较器
  350. // 优先使用比较器
  351. return comparator.compare(k1, k2);
  352. }
  353. // 这里表示没有使用比较器,此时再强制将传入的元素实现Comparable接口,并重写接口中的方法
  354. //return ((Comparable<E>) k1).compareTo(k2);
  355. return ((Comparable<K>) k1).compareTo(k2);
  356. }
  357. /** * 对node进行左旋转 * * @param grand */
  358. private void rotateLeft(Node<K, V> grand) {
  359. Node<K, V> parent = grand.right;
  360. Node<K, V> child = parent.left;
  361. grand.right = child;
  362. parent.left = grand;
  363. afterRotate(grand, parent, child);
  364. }
  365. /** * 对node进行右旋转 * * @param grand */
  366. private void rotateRight(Node<K, V> grand) {
  367. Node<K, V> parent = grand.left;
  368. Node<K, V> child = parent.right;
  369. grand.left = child;
  370. parent.right = grand;
  371. afterRotate(grand, parent, child);
  372. }
  373. /** * 旋转之后, 更新它们的parent; 并且更新旋转后的高度 * * @param grand * @param parent * @param child */
  374. private void afterRotate(Node<K, V> grand, Node<K, V> parent, Node<K, V> child) {
  375. // 让parent为子树的根节点
  376. parent.parent = grand.parent;
  377. // 如果grand是其父节点的left, 则将grand.parent.left = parent;
  378. if (grand.isLeftChild()) {
  379. grand.parent.left = parent;
  380. } else if (grand.isRightChild()) {
  381. grand.parent.right = parent;
  382. // grand是根节点
  383. } else {
  384. root = parent;
  385. }
  386. // 更新child的parent
  387. if (child != null)
  388. child.parent = grand;
  389. // 更新grand的parent
  390. grand.parent = parent;
  391. }
  392. /** * 将node染成color色 * * @param node 需要染色的节点 * @param color 染成的颜色 * @return 返回染色的节点 */
  393. private Node<K, V> color(Node<K, V> node, boolean color) {
  394. if (node == null) return node;
  395. node.color = color;
  396. return node;
  397. }
  398. /** * 传进来的节点染成黑色 * * @param node * @return */
  399. private Node<K, V> black(Node<K, V> node) {
  400. return color(node, BLACK);
  401. }
  402. /** * 传进来的节点染成红色 * * @param node * @return */
  403. private Node<K, V> red(Node<K, V> node) {
  404. return color(node, RED);
  405. }
  406. /** * 返回当前节点的颜色 * * @param node * @return 如果传入的节点为空, 默认为黑色 */
  407. private boolean colorOf(Node<K, V> node) {
  408. return node == null ? BLACK : node.color;
  409. }
  410. /** * 节点是否为黑色 * * @param node * @return */
  411. private boolean isBlack(Node<K, V> node) {
  412. return colorOf(node) == BLACK;
  413. }
  414. /** * 节点是否为红色 * * @param node * @return */
  415. private boolean isRed(Node<K, V> node) {
  416. return colorOf(node) == RED;
  417. }
  418. private void keyNotNullCheck(K key) {
  419. if (key == null) {
  420. // 手动抛出异常对象
  421. throw new IllegalArgumentException("key must not be null");
  422. }
  423. }
  424. private static class Node<K, V> {
  425. K key;
  426. V value;
  427. boolean color = RED;
  428. Node<K, V> left;
  429. Node<K, V> right;
  430. Node<K, V> parent;
  431. public Node(K key, V value, Node<K, V> parent) {
  432. this.key = key;
  433. this.value = value;
  434. this.parent = parent;
  435. }
  436. public boolean isLeaf() {
  437. return left == null && right == null;
  438. }
  439. public boolean hasTwoChildren() {
  440. return left != null && right != null;
  441. }
  442. public boolean isLeftChild() {
  443. return parent != null && this == parent.left;
  444. }
  445. public boolean isRightChild() {
  446. return parent != null && this == parent.right;
  447. }
  448. public Node<K, V> sibling() {
  449. if (isLeftChild()) {
  450. return parent.right;
  451. }
  452. if (isRightChild()) {
  453. return parent.left;
  454. }
  455. return null;
  456. }
  457. }
  458. }

发表评论

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

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

相关阅读