【数据结构】哈希表(Map和Set)

本文详细介绍了Map和Set数据结构,包括它们的模型、常见实现类如TreeMap、HashMap、TreeSet和HashSet的特性。文章还探讨了哈希表的基本概念,如哈希冲突的解决方法,如闭散列和开散列(哈希桶),以及如何通过哈希函数设计和负载因子调节来降低冲突。此外,文章通过源码分析解释了HashMap的内部工作原理,包括扩容和树化策略,以及为何在实现哈希表时需要同时重写hashCode()和equals()方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Map和Set

Map和Set是一种专门用来搜索的数据结构。这俩都是接口,并不是具体实现类。

image-20221113213529353

模型

一般把搜索的数据称为关键字(Key),把与关键字对应的称为值(Value),将其称为Key-Value键值对,产生两种模型:

纯Key模型:比如存储英文单词,对应的是Set接口。

Key-Value模型:比如要存储英文单词,以及单词对应的出现次数。对应的是Map接口。

Map

1️⃣Map是一个接口,不能直接new对象,可以new其实现类:TreeMap,HashMap

2️⃣Map中的key不可以重复,value可以重复,如果添加的键值对中的key重复了,会覆盖掉原有的键值对

3️⃣Map中键值对的key不能直接修改,value可以修改,如果要修改key,需要将键值对删除掉,再添加新的键值对

4️⃣TreeMap和HashMap的对比:TreeMap关于key有序,HashMap无序;TreeMap中存储的元素必须是可比较的,如果是自定义类型,需要传一个比较器。

TreeMap和HashMap对比

image-20221114134345603

Entry<K,V>

Map是一个接口,其中有一个内部接口:

image-20221113215318144

⭕️Entry<K,V>相对于Map<K,V>的关系就相当于Node相对于LinkedList。所以Entry<K,V>是真正存储键值对的。所以当Map的实现类实现了Map接口的时候,实现类中也得有个内部类实现Entry<K,V>接口。

比如TreeMap中的内部类Entry<K,V>:这个内部类其实就是真正存储键值对的节点,

image-20221113215822194

常用方法

Map中的常用方法:

image-20221113215100406

1️⃣V get(Object key):返回key对应的value,如果key不存在,返回null

2️⃣V getOrDefalult(Object key,V defaultValue):如果key存在,返回对应的value,如果key不存在,返回defaultValue。

3️⃣V put(K key,V value):设置一组键值对,如果key在map中已经存在了,则会覆盖掉之前的键值对,并返回旧的键值对的Value(因为Map中Key不能重复)。如果key在map中不存在,返回的是null。

4️⃣V remove(Object key):删除key及其对应的Value这组键值对,如果原map中有对应的key,则删除并返回对应的value,如果原map中没有对应的value,则返回的是null

5️⃣Set keySet() : 返回所有key的不重复集合:将map中所有的key存储到Set中

6️⃣Collection values():返回所有value的可重复集合

7️⃣Set<Map.Entry<K,V>> entrySet() : 返回所有的key-value映射关系,返回的是一个集合,集合中的元素是一个个对象,对象中存储的是原map中的key和value

image-20221114112519090

8️⃣boolean containsKey(Object key):判断map中是否包含key

9️⃣boolean coontainsValue(Object value) :判断map中是否包含value

Set

Set和Map的区别在于Set继承自Collection接口,Set中存储的是Key,Map中存储的是Key-Value

但其实TreeSet和TreeMap底层用的是一样的结构,都是红黑树,HashSet和HashMap底层用的结构是一样的,都是哈希桶。

在源码中:

image-20221114170530191

image-20221114170550291

创建Set实例实际创建的还是Map实例。

1️⃣Set中存储的是Key,Map中存储的是Key-Value,且Set中的key不能有重复的

2️⃣Set实现类:TreeSet和HashSet,TreeSet中key是有序的,HashSet中key是无序的。

3️⃣Set底层是使用的Map

4️⃣Set中的key不能修改,如果要修改需先删除再插入

5️⃣TreeSet中不能插入null,因为插入null无法比较

TreeSet和HashSet对比

image-20221114171731873

常用方法

image-20221114171847515

1️⃣boolean add(E e):添加元素到集合中,如果集合中有则不添加成功

2️⃣void clear():清空集合

3️⃣boolean contains(Object o):判断集合中是否有该元素

4️⃣boolean remove(Object o):删除集合中的元素

5️⃣int size():返回set中元素个数

6️⃣ boolean isEmpty(): 返回集合是否为空

7️⃣Object[] toArray():将集合中的元素转换为数组返回

8️⃣boolean containsAll(Collection<?> c):判断集合c中的元素是否在set中全部存在,是返回true,否返回false

image-20221114195332953

9️⃣boolean addAll(Collection<? extends E> c),将集合c中的元素全部添加到set中,还是会保证元素不重复。

image-20221114195839004

OJ练习

只出现一次数字

OJ链接

⭕️==方法一:==使用Set,遍历数组,集合中没有当前数字则进集合,有当前数字则出集合,那最后出现两次的数字就不会在集合中存在,只剩出现一次的数字。

class Solution {
   
   
    public int singleNumber(int[] nums) {
   
   
        Set<Integer> set = new HashSet<>();
        for(int i=0;i<nums.length;i++){
   
   
            if(set.contains(nums[i])){
   
   
                set.remove(nums[i]);
            }else{
   
   
                set.add(nums[i]);
            }
        }
        for(Integer x:set){
   
   
            return x;
        }
        return 0;
    }
}

⭕️==方法二:==遍历数组,把所有数字按位异或到一个数字num上,num初始为0。

class Solution {
   
   
    public int singleNumber(int[] nums) {
   
   
        int num = 0;
        for(int i=0;i<nums.length;i++){
   
   
            num ^= nums[i];
        }
        return num;
    }
}

复制带随机指针的链表

OJ链接

⭕️==方法一:==使用map存储新旧节点的地址,这样新旧节点的对应关系就保存了下来,后续修改next指针域和random指针域就可以根据map中存储的信息修改。

class Solution {
   
   
    public Node copyRandomList(Node head) {
   
   
        if(head==null){
   
   
            return null;
        }
        Node cur = head;
        Map<Node,Node> map = new HashMap<>();
        //将旧节点和新节点的地址一一对应存到map中
        while(cur!=null){
   
   
            Node node = new Node(cur.val);
            map.put(cur,node);
            cur = cur.next;
        }
        cur = head;
        while(cur!=null){
   
   
            //对于HashMap,get的参数是null则返回null
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;                  
        }
        return map.get(head);
    }
}

🍎时间复杂度:O(N),空间复杂度:O(N)

⭕️方法二:

1️⃣每创建一个新的节点就插入到链表的旧结点之后,让旧结点和新节点有一一对应关系(前是旧结点,后是新节点)。

2️⃣然后遍历链表,修改新节点的random指针域。这时候先不能修改next指针域,一修改next对应关系就没了。

3️⃣再次遍历链表,将新节点从原链表中一个一个移出,并连接到新链表的最后。

class Solution {
   
   
    public Node copyRandomList(Node head) {
   
   
        if(head==null){
   
   
            return null;
        }
        Node cur = head;
        //把新的节点加到旧节点之后
        while(cur!=null){
   
   
            Node newNode = new Node(cur.val);
            newNode.next = cur.next;
            cur.next = newNode;
            cur = cur.next.next;           
        }
        cur = head;
        //修改radom指针域
        while(cur!=null){
   
   
            Node newNode = cur.next;
            Node nextNode = cur.next.next;
            newNode.random = (cur.random == null ? null : cur.random.next); 
            cur = nextNode;  
        }
        //修改next指针域
        cur = head;
        Node newHead = null;
        Node lastNode = null;
        while(cur!=null){
   
   
            Node newNode = cur.next;
            Node nextNode = cur.next.next;
            //把新节点从原链表拆下来
            cur.next = nextNode;
            //把拆下来的新节点连接到新链表末尾
            if(newHead == null){
   
   
                newHead = newNode;
                lastNode = newHead;
            }else{
   
   
                lastNode.next = newNode;
                lastNode = newNode;
            }
            cur = nextNode;
        }
        return newHead;
    }
}

🍎时间复杂度:O(N),空间复杂度:O(1)

宝石与石头

OJ链接

class Solution {
   
   
    public int numJewelsInStones(String jewels, String stones) {
   
   
        Set<Character> set = new HashSet<>();
        for(int i=0;i<jewels.length();i++){
   
   
            set.add(jewels.charAt(i));
        }
        int count = 0;
        for(int i=0;i<stones.length();i++){
   
   
            if(set.contains(stones.charAt(i))){
   
   
                count++;
            }
        }
        return count;
    }
}

坏键盘打字

OJ链接

import java.util.*;
public class Main{
   
   
    public static void main(String[] args){
   
   
        Scanner scanner = new Scanner(System.in);
        String str1 = scanner.nextLine();
        String str2 = scanner.nextLine();
        str2 = str2.toUpperCase();
        str1 = str1.toUpperCase();
        Set<Character> set = new HashSet<>();
        for(int i=0;i<str2.length();i++){
   
   
            set.add(str2.charAt(i));
        }
        Set<Character> set1 = new HashSet<>();
        for(int i=0;i<str1.length();i++){
   
   
            char tmp = str1.charAt(i);
            if(!set.contains(tmp) && !set1.contains(tmp)){
   
   
                set1.add(tmp);
                System.out.print(tmp);
            }
        }
    }
}

前K个高频单词

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值