[数据结构] Map和Set

一.搜索树

1. 概念

二叉搜索树 又称 二叉排序树 , 它是一颗空树 , 或者是具有以下性质 的二叉树 :

  • 若它的左子树不为空 , 则 左子树上所有结点的值 都小于根节点的值
  • 若他的右子树不为空 , 则 右子树上所有结点的值 都大于根节点的值
  • 他的左右子树也分别为为二叉搜索树

代码 :

java 复制代码
public class BinarySearchTree {
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int val) {
            this.val = val;
        }
    }
    
    public TreeNode root;

    /**
     * 查找
     */
    public TreeNode search(int val) {
        TreeNode cur = root;
        while (cur != null) {
            if (cur.val == val) {
                return cur;
            } else if (cur.val > val) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        return null;
    }

    /**
     * 插入
     */
    public void insert(int val) {
        TreeNode node = new TreeNode(val);
        if (root == null) {
            root = node;
            return;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null) {
            if (cur.val == val) {
                return; // 不允许插入重复元素
            } else if (cur.val > val) {
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }
        if (parent.val > val) {
            parent.left = node;
        } else {
            parent.right = node;
        }
    }

    /**
     * 删除
     */
    public void remove(int val) {
        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null) {
            if (cur.val < val) {
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val) {
                parent = cur;
                cur = cur.left;
            } else {
                removeNode(parent, cur);
                return;
            }
        }
    }

    private void removeNode(TreeNode parent, TreeNode cur) {
        if (cur.left == null) {
            // 左子树为空,用右子树替换
            if (cur == root) {
                root = cur.right;
            } else if (cur == parent.left) {
                parent.left = cur.right;
            } else {
                parent.right = cur.right;
            }
        } else if (cur.right == null) {
            // 右子树为空,用左子树替换
            if (cur == root) {
                root = cur.left;
            } else if (cur == parent.left) {
                parent.left = cur.left;
            } else {
                parent.right = cur.left;
            }
        } else {
            // 左右子树都不为空,找中序后继(右子树最左节点)
            TreeNode target = cur.right; // 从右子树开始查找
            TreeNode targetParent = cur; // 后继的父节点初始为当前节点
            while (target.left != null) { // 寻找最左节点
                targetParent = target;
                target = target.left;
            }
            // 用后继的值覆盖当前节点
            cur.val = target.val;
            // 删除后继节点(后继最多只有右子树)
            if (targetParent.left == target) {
                targetParent.left = target.right;
            } else {
                targetParent.right = target.right;
            }
        }
    }
}

2.性能分析

  • 插入和删除都必须先查找 , 查找效率代表了二叉搜索树的各个操作的性能
  • 对于 有 N个结点的二叉搜索树 , 若每个元素查找的概率相等 , 则二叉搜索树的平均查找长度是 在二叉搜索树的深度的函数 , 即结点越深 , 比较次数越多
  • 但是 对于同一个关键码集合 , 如果各关键码的插入次序不同 , 可能得到不同的二叉搜索树
  • 最优情况 : 二叉搜索树为完全二叉树 , 其平均比较次数为 log2N
  • 最差情况 : 二叉搜索树退化为单分支树 , 其平均比较次数为 N/2

3.和 Java 类集的关系

TreeMap 和 TreeSet 即 Java 中利用搜索树实现的 Map 和 Set ; 实际上用的是红黑树 , 而红黑树是一颗近似平衡的二叉搜素树 , 即在二叉搜索树的基础上 加 颜色及红黑树性质验证

二.搜索

1.模型一般把搜索的数据称为关键字 (Key) , 和关键字对应的称为值 (Value) , 将其称为 Key-Value 的键值对 , 搜索模型有以下两种 :

① 纯 Key 模型 , 例如 :

  • 有一个词典 , 快速查找一个单词是否在词典中
  • 快速查找某个名字在不在通讯录中

②Key-Value 模型 , 例如 :

  • 统计文件中 每个单词出现的次数 , 统计结果是每个单词都有 与之对应的次数 : <单词 , 单词出现的次数>
  • 梁山好汉的江湖绰号 : 每个好汉都有自己的江湖绰号

Map 中存储的就是 Key-Value 的键值对 , Set 中只存储了 Key

三.Map 的使用

1.关于 Map 的说明

Map 是一个接口类 , 该类没有继承自 collection , 该类中存储的是 <K,V>结构的键值对 , 并且 K 一定 是唯一的 , 不能重复

2.关于 Map.Entry<Key , Value>的说明

Map.Entry<Key , Value>是 Map 内部实现的用来存放<Key,Value>键值对映射关系的内部类 , 该内部类中主要提供了<Key,Value>的获取 , Value 设置以及 Key 的比较方式

|---------------------|--------------------------|
| 方法 | 解释 |
| K getKey() | 返回 entry 中的 Key |
| V getValue() | 返回 entry 中的 Value |
| V setValue(V value) | 将键值对中的 Value 替换为指定 Value |

注意 : Map.Entry<K,V>并没有提供设置 Key 的方法

3.Map 的常用方法

|---------------------------------------------|------------------------------------------------|
| 方法 | 解释 |
| V get(Object key) | 返回 key 对应的 value |
| V getOrDefault(Object key , V defaultValue) | 返回 key 对应的 value , key 不存在 , 返回默认值defaultValue |
| V put(K key , V value) | 设置 key 对应的 value |
| V remove(Object key) | 删除 key 对应的映射关系 |
| Set<K> keySet() | 返回所有 key 的不重复集合 |
| Collection<V>values() | 返回所有 value 的可重复集合 |
| Set<Map.Entry<K,V>> entrySet() | 返回所有 key-value 的映射关系 |
| boolean containsKey(Object key) | 判断是否含 key |
| boolean containsValue(Object value) | 判断是否含 value |

注意 :

  • Map 是一个接口 , 不能直接实例化对象 , 如果要实例化对象 只能实例化实现类 TreeMap 或者 HashMap
  • Map 中存放 键值对的 **Key 是唯一的 ,**Value 是可重复的
  • TreeMap 中插入键值对时 , Key 不能为空 , 否则会抛出 NullPointerException 异常 , Value 可以为空
  • 但是在 HashMap 中 Key 和 Value 都可以为空
  • Map 中的 Key 可以全部分离出来 , 存储到 Set 中来进行访问(因为 Key 不能重复)
  • Map 中的 Value 可以全部分离出来 , 存储到 Collection 的任何一个子集合中(Value 可能重复)
  • Map 中的键值对的 Key 不能直接修改 , Value 可以修改 , 如果要修改 Key 只能详见该 Key 删除掉 , 然后再来进行重新插入

TreeMap 和 HashMap 的区别 :

|----------------|--------------------|--------------------------------|
| Map 的底层结构 | TreeMap | HashMap |
| 底层结构 | 红黑树 | 哈希桶 |
| 插入/删除/查找的时间复杂度 | O(log2N) | O(1) |
| 是否有序 | 关于 Key 有序 | 无序 |
| 线程安全 | 不安全 | 不安全 |
| 插入/删除/查找的区别 | 需要进行元素比较 | 通过哈希函数计算哈希地址 |
| 比较与覆写 | key 必须能比较 , 否则抛出异常 | 自定义类型需要覆写 equals 和 hashCode 方法 |
| 应用场景 | 需要 key 有序场景下 | Key 是否有序不关心 , 需要更高的时间性能 |

代码 :

java 复制代码
public static void main(String[] args) {
    TreeMap<String,Integer> treeMap = new TreeMap<>();
    /**
     * put(Key,Value) : 插入 Key-Value 的键值对
     * 如果 Key不存在 ,会将 Key-Value 键值对插入到 Map中 , 返回null
     */
    treeMap.put("蔡旭困",1);
    treeMap.put("hello",12);

    Integer a1 = treeMap.put("wsz",23);
    System.out.println(a1);//返回null

    treeMap.put("wsa",null);//Key不能为空 , 但是 Value可以为空

    Integer a2 = treeMap.put("wsz",21);//如果Key存在 ,会使用 新的Value 来替换 原来的Value ,返回旧Value
    System.out.println(a2);//返回旧Value23

    /*------------------------------------------------*/
    /**
     * get(Key) : 返回Key所对应的Value
     * 如果Key存在 , 返回 Key 所对应的 Value
     * 如果Key不存在 , 返回 null
     */
    System.out.println(treeMap.get("wsz"));//21
    System.out.println(treeMap.get("sdf"));//null

    /*------------------------------------------------*/
    //getOrDefault(Key,Value): 如果Key存在 ,返回与Key对应的Value , 如果不存在 , 返回一个默认值
    System.out.println(treeMap.getOrDefault("wsz", 21));//21
    System.out.println(treeMap.getOrDefault("sdf",1));//不存在Value , 返回一个默认值1

    System.out.println(treeMap.size());//4

    /*------------------------------------------------*/
    /**
     * containKey(Key):用来检测Key是否包含在Map中
     * 按照红黑树的性质来进行查找
     * 找到返回true ,否则返回false
     */
    System.out.println(treeMap.containsKey("蔡旭困"));

    /**
     * containValue(Value) :检测Value是否包含在 Map 中
     * 找到返回 true ,否则返回 false
     */
    System.out.println(treeMap.containsValue(1));//true

    /*------------------------------------------------*/
    /**
     * 打印所有的Key
     * keySet()是将Map中的Key防止在Set中返回
     */
    for (String s:treeMap.keySet()) {
        System.out.print(s+" ");
    }
    System.out.println();

    /**
     * 打印所有的Value
     * Value()是将Map中的Value放在 collect的一个集合中返回的
     */
    for (Integer x:treeMap.values()) {
        System.out.print(x+" ");
    }
    System.out.println();
    /**
     * 打印所有的键值对
     * entrySet() : 将Map中的键值对放在Set中返回了
     */
    for (Map.Entry<String, Integer> entry:treeMap.entrySet()) {
        System.out.println(entry.getKey()+"-->"+entry.getValue());
    }
    System.out.println();
}

四.Set 的说明

1.Set 的常用方法

|---------------------------------------------|-------------------------------------------------|
| 方法 | 解释 |
| boolean add (E e) | 添加元素 , 但重复不会被添加成功 |
| void clear() | 清空集合 |
| boolean contains(Object o) | 判断 o 是否在集合中 |
| Iterator<E>iterator() | 返回迭代器 |
| boolean remove(Object o) | 删除集合中的 o |
| int size() | 返回 set 中的元素的个数 |
| boolean isEmpty() | 检测 set 是否为空 ,空返回 true , 否则返回 false |
| Object[] toArray() | 将 set 中的元素转换为数组返回 |
| boolean containsAll(Collection<?>c) | 集合 c 中的元素是否在 set 中全部存在 , 是 返回 true , 否则行会 false |
| boolean addAll(Collection<? extends E> c) | 将集合 c 中的元素添加到 set 中 , 可以达到去重的效果 |

注意 :

  • Set 是继承自 collection 的一个接口类
  • Set 中只存储了 key , 并且要求 key 的值要唯一
  • TreeSet 的底层是使用 Map 来实现的 , 其使用 key 与 Object 的一个默认对象作为键值对插入到 Map 中的
  • Set 最大的功能就是对集合中的元素进行去重
  • 实现 Set 接口的常用类有 TreeSet 和 HashSet , 还有 linkedHashSet , linkedHashSet 是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序
  • Set 中的 Key 不能修改 , 如果要修改 , 先将原来的删除掉 , 然后再重新插入
  • TreeSet 中不能插入 null 的 key , HashSet 可以

TreeSet 和 HashSet 的区别 :

|--------------|---------------------|-------------------------------|
| Set 底层结构 | TreeSet | hashSet |
| 底层结构 | 红黑树 | 哈希桶 |
| 插入删除查找的时间复杂度 | O(log2N) | O(1) |
| 是否有序 | 关于 Key 有序 | 不一定有序 |
| 线程安全 | 不安全 | 不安全 |
| 插入/删除/查找的区别 | 按照红黑树的特性来进行插入和删除 | 先计算 key 哈希地址 ; 然后进行插入和删除 |
| 比较与覆写 | key 必须能够比较 , 否则抛出异常 | 自定义类需要覆写 equals 和 hashCode 方法 |
| 应用场景 | 需要 Key 有序场景下 | Key 是否有序不关心 , 需要更高的时间性能 |

代码 :

java 复制代码
public static void main(String[] args) {
    Set<String> s = new TreeSet<>();
    /**
     * add(key):如果key不存在, 则插入, 返回true
     *          如果key存在, 则返回false
     *          如果key是空的, 则会抛出空指针异常
     */
    s.add("def0");
    s.add("fgh");
    s.add("hpo");
    s.add("jfj");
    s.add("okj");
    s.add("dja");
    boolean a1 = s.add("fgh");//key存在
    boolean a2 = s.add("abc");//key不存在
    //s.add(null);//抛异常
    System.out.println(a1);//false
    System.out.println(a2);//true

    /**
     * contains(key):如果key存在, 返回true, 否则返回false
     */
    System.out.println(s.contains("abc"));//true
    System.out.println(s.contains("hji"));//false

    /**
     * remove(key):key存在, 删除成功返回true
     *             key不存在, 删除失败返回false
     *             key为空, 抛出空指针异常
     */
    s.remove("abc");
    System.out.println(s);

    /**
     * 迭代器
     */
    Iterator<String> it = s.iterator();
    while(it.hasNext()){
        System.out.print(it.next()+" ");
    }
    System.out.println();
}

相关推荐
一勺菠萝丶3 小时前
Mac 上用 Homebrew 安装 JDK 8(适配 zsh 终端)完整教程
java·python·macos
要一起看日出3 小时前
数据结构------二叉查找树
数据结构·二叉查找树
思考的笛卡尔4 小时前
密码学基础:RSA与AES算法的实现与对比
网络·算法·密码学
毕设源码-朱学姐5 小时前
【开题答辩全过程】以 办公自动化管理系统为例,包含答辩的问题和答案
java·eclipse
李宥小哥7 小时前
C#基础11-常用类
android·java·c#
小许学java8 小时前
数据结构-ArrayList与顺序表
java·数据结构·顺序表·arraylist·线性表
Java 码农9 小时前
Centos7 maven 安装
java·python·centos·maven
格林威10 小时前
常规线扫描镜头有哪些类型?能做什么?
人工智能·深度学习·数码相机·算法·计算机视觉·视觉检测·工业镜头
harmful_sheep10 小时前
maven mvn 安装自定义 jar 包
java·maven·jar