Map和Set

一.二叉搜索树(二叉排序树)

1.概念

二叉搜索树,他或是空树,或具有以下性质:

1.若它的左子树不为空,则它的左子树上的每个节点都小于根节点的值

2.若它的右子树不为空,则它的右子树上的每个节点都大于根节点的值

3.它的左右子树也都为二叉搜索树

2.操作-查找

查找代码:

java 复制代码
    static class Node{
            int val;
            Node left;
            Node right;
            Node(int key){
                this.val = key;
            }
        }
    public Node root; 
/**
    //查找指定节点
     * @param key 要查找的值
     * @param root 根节点
     * @return
     */
    public boolean search(int key,Node root){
        if(root==null){
            return false;
        }
        Node cur=root;
        while(cur!=null){
            if(cur.val==key){
                return true;
            }else if(cur.val<key){
                cur=cur.right;
            }else{
                cur=cur.left;
            }
        }
        return false;
    }

3.插入

1.如果树为空,则直接插入 root==node;

2.如果树不为空,按照查找逻辑确定插入位置,插入新结点

(新插入节点的位置一定是叶子结点)

java 复制代码
/**
     * 插入节点 插入成功,返回true,失败返回false
     * @param key
     * @return
     */
static class Node{
        int val;
        Node left;
        Node right;
        Node(int key){
            this.val = key;
        }
    }
public Node root;
    public boolean insert(int key){
        Node node=new Node(key);
        if(root==null){
            root=node;
        }
        Node cur=root;//要插入位置
        Node prev=null;//要插入位置的父节点
        while(cur!=null){
            prev=cur;
            if(cur.val<key){
                cur=cur.right;
            }else if(cur.val>key){
                cur=cur.left;
            }else{
                return false;
            }
        }
        if(prev.val<key){
            prev.right=node;
        }else{
            prev.left=node;
        }
        return true;
    }

4.删除(难点)

删除根据子树是否存在节点分为几种情况:

1.要删除节点的左右子树都为空

2.要删除节点的左子树,或右子树为空

3.要删除节点的左右子树都不为空

代码实现:

java 复制代码
     static class Node{
        int val;
        Node left;
        Node right;
        Node(int key){
            this.val = key;
        }
    }
    public Node root;
    /**
     * 删除节点
     * @param del
     * @return
     */
    public boolean remove(int del){
        //1.先找到该节点
        if(root==null){
            return false;
        }
        Node cur=root;
        Node prev=null;
        while(cur!=null){
            if(cur.val==del){
                break;
            }else if(cur.val<del){//当前节点值小于要删除节点值,向右子树找
                prev=cur;
                cur=cur.right;
            }else if(cur.val>del){//cur.val>del//当前节点值大于要删除节点值,向左子树找
                prev=cur;
                cur=cur.left;
            }else{
                return false;//未找到要删除节点,返回false
            }
        }
        removeNode(prev,cur);
        return true;
    }
    public void removeNode(Node prev,Node cur){
        if(cur.left==null){
            //1.要删除节点的左子树为空
            if(cur==root){
                //要删除节点为根节点
                root=root.right;
            }else if(prev.left==cur){
                //找到cur的位置,将cur的右子树放在cur位置上
                prev.left=cur.right;
            }else{
                prev.right=cur.right;
            }
        }else if(cur.right==null){
            //2.要删除节点的右子树为空
            if(cur==root){
                //要删除节点为根节点
                root=root.left;
            }else if(prev.left==cur){
                //找到cur的位置,将cur的左子树放在cur位置上
                prev.left=cur.left;
            }else{
                prev.right=cur.left;
            }
        }else{//要删除节点的左右子树都不为空
            //使用替换法(替罪羊):法1:将其左子树的最右端节点值与cur.val交换,让后删除左子树的最右端节点
            //              法2:将其右子树的最左端节点值与cur.val交换,让后删除右子树的最左端节点
            //这里采用删除右子树最左端节点
            Node target=cur.right;//替罪羊节点
            Node targetParent=cur;//替罪羊节点的父节点
            while(target.left!=null){
                targetParent=target;
                target=target.left;
            }
            cur.val=target.val;//将值替换
            //删除target
            if(targetParent.left==target){
                targetParent.left=target.right;
            }else{
                targetParent.right=target.right;
            }
        }
    }

4.性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度 的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合(插入一组相同节点),如果各关键码(节点)插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况:完全二叉树:平均比较次数:logN(以2为底).

最坏情况:单分支二叉树:平均比较次数:N/2.

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

二.查找

以前常见的 搜索方式有:

  1. 直接遍历,时间复杂度为O(N),元素如果比较多效率会非常慢

  2. 二分查找,时间复杂度为 ,但搜索前必须要求序列是有序的

上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:

  1. 根据姓名查询考试成绩

  2. 通讯录,即根据姓名查询联系方式

  3. 不重复集合,即需要先搜索关键字是否已经在集合中

可能在查找时进行一些插入和删除的操作,即动动态查找,那上述两种方式就不太适合了,而Map和Set是 一种适合动态查找的集合容器。

模型

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

  1. 纯 key 模型

  2. Key-Value 模型

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

三.Map的使用

1.Map的介绍

Map是一个接口类,该类中存储的是结构的键值对,并且Key一定是唯一的,不能重复.当插入的key值已经存在时,会覆盖原来的val值,map中的key值是唯一的)

可以通过HashMap,TreeMap来实例化.

HaspMap的底层是一个哈希表,TreeMap的底层是一颗红黑树.(一会儿会说)

2.Map的常用方法:

3.注意事项

  1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap

2.Map中存放键值对的Key是唯一的,value是可以重复的

  1. 在TreeMap中插入键值对时,key不能为空(要比较大小,构成红黑树),否则就会抛NullPointerException异常,value可以为空。但是HashMap的key和value都可以为空。

  2. Map中的Key可以全部分离出来(keySet()方法),存储到Set中来进行访问(因为Key不能重复)。

  3. Map中的value可以全部分离出来(values()方法),存储在Collection的任何一个子集合中(value可能有重复)。

  4. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行 重新插入。

  5. TreeMap和HashMap的区别

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

参考代码:

java 复制代码
public class test1 {
    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<String, Integer>();
        //1.put():插入键值对,
        // 若原来的key已经存在,则覆盖原来的val,返回key对应的原来的val值,
        // 若不存在,则插入,返回null
        map.put("a",1);
        map.put("b",2);
        map.put("c",2);
        //2.get():返回key对应的val,若无对应的key,则返回null
        System.out.println(map.get("a"));
        map.put("a",10);
        //3.V getOrDefault(Object key, V defaultValue)
        //返回key对应的val,若无对应的key,则返回默认值defaultValue
        System.out.println(map.getOrDefault("a", 100));
        System.out.println(map.getOrDefault("d", 100));
        //4.V remove(Object key) 删除 key 对应的映射关系,返回val值,
        System.out.println(map.remove("a"));
        System.out.println("----");

        //5.Set<K> keySet(): 返回所有 key 的不重复集合
        Set<String> set = map.keySet();
        for (String s :set) {
            System.out.print(s);
        }
        System.out.println("---");

        //Colleation<V> values():返回所有 value 的可重复集合
        Collection<Integer> collection = map.values();
        for (Integer integer : collection) {
            System.out.print(integer);
        }
        System.out.println("---");

        //Set<Map.Entry<K, V>> entrySet() 返回所有的 key-value 映射关系
        Set<Map.Entry<String, Integer>> set2 = map.entrySet();
        for (Map.Entry<String, Integer> entry :set2) {
            System.out.println("key: "+entry.getKey()+" value: "+entry.getValue());
        }

        //boolean containsKey(Object key) 判断是否包含 key
        System.out.println(map.containsKey("a"));
        //boolean containsValue(Object value) 判断是否包含 value
        System.out.println(map.containsValue(10));
        System.out.println("size= "+map.size());

    }
}

四.Set的使用

1.Set介绍

Set是继承自Collection的接口类,Set中只存储了Key。

Set也是一个接口,不能实例化对象,需要用HashSet/TreeSet来实例化对象.

2.Set的常用方法

3.注意事项

  1. Set是继承自Collection的一个接口类

  2. Set中只存储了key,并且要求key一定要唯一

  3. TreeSet的底层是使用Map来实现的 ,其使用key与Object的一个默认对象作为键值对插入到Map中的

  1. Set最大的功能就是对集合中的元素进行去重

(set中原本有元素1 ,2 ,又加入col1集合,可对col1与set中元素进行去重效果)

  1. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序。因此LinkedHashSet是有序的.

  2. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入

  3. TreeSet中不能插入null的key(因为要比较其值,形成红黑树),HashSet可以(用的是其哈希值进行比较)。

  4. TreeSet和HashSet的区别

TreeSet HashSet
底层结构 红黑树 哈希桶
插入/删除/查找时间 复杂度 O(logN)(以2为底) O(1)
是否有序 关于key有序 无序(底层是以哈希值排序的)
线程安全 不安全 不安全
插入/删除/查找区别 需要进行元素的比较 通过哈希函数计算哈希地址,直接获取
元素特征 key必须能够比较,否则会抛出 ClassCastException异常 自定义类型需要重写equals和 hashCode方法
应用场景 需要key有序的情况下 需要更高的 时间性能(利用其查找的时间复杂度为O(1))

.参考代码:

java 复制代码
package Set_;

import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class test1 {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<>();
        //1.boolean add(E e) :添加元素,但重复元素不会被添加成功
        set.add(1);
        set.add(2);
        System.out.println(set.add(2));//false

        //2.boolean contains(Object o) :判断 o 是否在集合中
        System.out.println(set.contains(2));//true

        //3.Iterator<E> iterator() 返回迭代器
        System.out.println("迭代器遍历:");
        Iterator<Integer> iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next()+" ");//1 2
        }
        //4.boolean remove(Object o) :删除集合中的 o
        set.add(3);
        System.out.println(set.remove(3));//true

        //5.int size() :返回set中元素的个数
        System.out.println(set.size());//2

        //6.boolean isEmpty() :检测set是否为空,空返回true,否则返回false
        System.out.println(set.isEmpty());//false

        //7.Object[] toArray() :将set中的元素转换为数组返回
        Object[] array = set.toArray();
        for (Object o :array) {
            System.out.print(o+" ");//1 2
        }

        //8.boolean containsAll(Collection<?> c)
        //集合c中的元素是否在set中全部存在,是返回true,否则返回false
        Collection<Integer> col=new TreeSet<>();
        col.add(1);
        col.add(2);
        System.out.println(set.containsAll(col));//true

        //boolean addAll(Collection<? extends E> c)
        //将集合c中的元素添加到set中,可以达到去重的效果
        Collection<Integer> col1=new TreeSet<>();
        col1.add(11);
        col1.add(22);
        set.addAll(col1);
        Object[] arr=set.toArray();
        for(int i=0;i<set.size();i++){
            System.out.print(arr[i]+" ");//1 2 11 22
        }

        //void clear() 清空集合
        set.clear();
        System.out.println(set.size());//0
    }
}
相关推荐
<但凡.几秒前
题海拾贝:力扣 138.随机链表的复制
数据结构·算法·leetcode
田梓燊32 分钟前
图论 八字码
c++·算法·图论
Tanecious.1 小时前
C语言--数据在内存中的存储
c语言·开发语言·算法
Bran_Liu2 小时前
【LeetCode 刷题】栈与队列-队列的应用
数据结构·python·算法·leetcode
kcarly2 小时前
知识图谱都有哪些常见算法
人工智能·算法·知识图谱
CM莫问2 小时前
<论文>用于大语言模型去偏的因果奖励机制
人工智能·深度学习·算法·语言模型·自然语言处理
程序猿零零漆2 小时前
《从入门到精通:蓝桥杯编程大赛知识点全攻略》(五)-数的三次方根、机器人跳跃问题、四平方和
java·算法·蓝桥杯
无限码力3 小时前
路灯照明问题
数据结构·算法·华为od·职场和发展·华为ode卷
嘻嘻哈哈樱桃3 小时前
前k个高频元素力扣--347
数据结构·算法·leetcode
dorabighead3 小时前
小哆啦解题记:加油站的奇幻冒险
数据结构·算法