数据结构Map-Set和哈希表

news2025/1/22 23:40:28

目录

概念

模型

Map

Map的常用方法

对于Map的总结

Set

Set的常见方法

关于Set的总结

哈希表

概念

冲突

概念

哈希函数设计原则

常见的哈希函数

1.直接定制法(常用)

2.除留余数法(常用)

3.平方取中法

4.折叠法

5.随机数法

6.数学分析法

冲突避免-负载因子调节

冲突-解决

闭散列

线性探测

二次探测

开散列/哈希桶

三个例题理解Map和Set

复制带随机指针的链表


概念

Map和Set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关,比如TreeMap和TreeSet的效率一般是不如HashMap和HashSet的.

模型

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

Map中存储的就是key-value模型,Set中只存储了Key.

Map

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

TreeMap实现了SortedMap接口,所以TreeMap中的元素一定是可比较的.

Map的常用方法

方法演示

TreeMap<String,Integer> map = new TreeMap<>();
        map.put("hello",2);
        map.put("abc",4);
        //返回key对应的value
        //如果map中没有hello,会返回null
        Integer e =map.get("hello");
        System.out.println(e);
        //有key返回对应值,没有返回默认值
        Integer s = map.getOrDefault("hello2",520);
        System.out.println(s);
        //取出key值,进行组织
        //用Set接收
        Set<String> set = map.keySet();
        System.out.println(set);
        //取出value值,进行组织
        //用Collection接收
        Collection<Integer> collection = map.values();
        System.out.println(collection);
        //containsKey,判断是否包含key
        boolean t = map.containsKey("hello");
        System.out.println(t);
        //containsValue,判断是否包含value
        boolean r = map.containsValue("88");
        System.out.println(r);

entrySet方法

Map.Entry<K,V>是Map内部实现的用来存放<key,value>键值对映射关系的内部类,该内部类中主要提供了下列方法:

 Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        for (Map.Entry<String,Integer> entry : entrySet) {
            System.out.println("Key: " + entry.getKey() +" value: " + entry.getValue());
        }

此方法就是将<"hello",2>作为一个整体存放在set当中,这个整体的类型Map.Entry<String,Integer>.

由于Map并没有实现Iterable接口,所以for-each无法直接遍历Map.

这个方法相当于是提供了遍历Map的方法.


对于Map的总结

1. Map 是一个接口,不能直接实例化对象 ,如果 要实例化对象只能实例化其实现类 TreeMap 或者HashMap
2. Map 中存放键值对的 Key 是唯一的, value是可以重复的.因为Map的底层是搜索树(红黑树).
3. Map 中的 Key 可以全部分离出来,存储到 Set 来进行访问 ( 因为 Key 不能重复 )。
4. Map 中的 value 可以全部分离出来,存储在 Collection 的任何一个子集合中 (value 可能有重复 )。
5. Map中键值对的 Key 不能直接修改, value 可以修改,如果要修改 key ,只能先将该 key删除掉,然后再来进行重新插入。

Set

Set 与Map的不同主要有两点:Set是继承自Collection的接口类,Set中只存储了Key.

Set的常见方法

在TreeSet当中存储元素的时候,其实是存在了TreeMap当中,但是value是默认的一个值.

不管存哪个key,value永远是PRESENT这个值.

关于Set的总结

  • 1. Set是继承自Collection的一个接口类
  • 2. Set中只存储了key,并且要求key一定要唯一
  • 3. Set的底层是使用Map来实现的,其使用keyObject的一个默认对象作为键值对插入到Map中的
  • 4. Set最大的功能就是对集合中的元素进行去重
  • 5. 实现Set接口的常用类有TreeSetHashSet,还有一个LinkedHashSetLinkedHashSet是在HashSet的基础
  • 上维护了一个双向链表来记录元素的插入次序。
  • 6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
  • 7. Set中不能插入null的key,null一旦比较就会出现空指针异常.TreeSet和TreeMap去存储元素的时候,它们的Key一定是可比较的,否则会出现ClassCastException的异常.

哈希表

以往,我们在一些记录当中查找指定数据的时候,会有以下几种方法

  1. 把数据存储到数组当中,然后遍历数组去查找.时间复杂度是O(N)
  2. 假设数据是有序的情况下,那么二分查找是最快的,时间复杂度可以达到O(lognN).
  3. 我们也可以利用搜索树,可以达到O(logN).

现在,有一种可以将时间复杂度做到O(1)的结构,那就是哈希表.

概念

不经过任何比较,一次直接从表中得到要搜索的元素.如果构造一种存储结构,通过某种函数使元素的存储位置和它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快的找到该元素.

该港是即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希函数,构造出来的结构称为哈希表(HashTable)(散列表).

例如:数据集合{176459}

哈希函数设置为: hash(key) = key % capacity ; capacity 为存储元素底层空间总的大小。

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快.但是,如果以此方式,向集合里插入14,会出现位置占用的问题,这就出现了冲突.

冲突

概念

不同关键字通过哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或者哈希碰撞.

把具有不同关键码而具有相同哈希地址的数据元素称为"同义词".

冲突避免-巧妙设计哈希函数

我们要明确,由于哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致冲突的发生是必然的,我们能做的应该是尽量的降低冲突率.

哈希函数设计原则

引起哈希冲突的一个原因可能是:哈希函数设计不合理.

设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

常见的哈希函数

1.直接定制法(常用)
取关键字的某个线性函数为散列地址: Hash Key = A*Key + B 优点:简单、均匀 缺点:需要事先知道关 键字的分布情况 使用场景:适合查找比较小且连续的情况.
例题
直接定制法例题
class Solution {
    public int firstUniqChar(String s) {
            int[] array = new int[26];
            for(int i = 0; i < s.length();i++){
                char ch = s.charAt(i);
                array[ch-'a']++;
            }
            for(int i = 0; i < s.length();i++){
                char ch = s.charAt(i);
                if(array[ch-'a'] == 1){
                    return i;
                }
            }
            return -1;
    }
}
2.除留余数法(常用)
设散列表中允许的地址数为 m ,取一个不大于 m ,但最接近或者等于 m 的质数 p 作为除数,按照哈希函数: Hash(key) = key% p(p<=m),将关键码转换成哈希地址.
3.平方取中法
假设关键字为 1234 ,对它平方就是 1522756 ,抽取中间的 3 227 作为哈希地址; 再比如关键字为 4321 ,对它平方就是18671041 ,抽取中间的 3 671( 710)作为哈希 地址.
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况.
4.折叠法
折叠法是将关键字从左到右分割成位数相等的几部分 ( 最后一部分位数可以短些 ) ,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
5.随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random(key), 其中 random 为随机数函数。
通常应用于关键字长度不等时采用此法
6.数学分析法
设有 n d 位数,每一位可能有 r 种不同的符号,这 r 种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址.
假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前 7 位都是 相同的,那么我们可以
选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还可以对抽取出来的数字进行反转 (
1234 改成 4321) 、右环位移 ( 1234 改成 4123) 、左环移位、前两数与后两数叠加 ( 1234 改成 12+34=46) 等方
法。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均 匀的情况.

冲突避免-负载因子调节

散列表的载荷因子定义为:α =填入表中的元素个数/散列表的长度.

α 是散列表装满程度的标志因子.由于表长是定值,α 与填入表中的元素个数成正比,α 越大,表明填入表中的元素越多,产生冲突的可能性就越大.

负载因子和冲突率的关系演示

当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率.

已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组大小.

α 与哈希表数组的长度是成反比的.


冲突-解决

解决哈希冲突的两种常见方式:闭散列和开散列.

闭散列

闭散列,也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么就可以把key存放到冲突位置的"下一个"空位置去.

寻找下一个空位置有两种方法:线性探测法和二次探测法.

线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止.

比如要插入44,先通过哈希函数获取待插入元素在哈希表中的位置,如果该位置没有元素就直接插入新元素,显然这里4占据了位置;此时元素发生哈希冲突,则使用线性探测法找到下一个空位置,下标为8的位置,插入44.

需要注意的是,采用此种方法处理哈希冲突的时候,不能随便物理删除哈希表中已有的元素,若直接删除会影响其他元素的搜索.比如删除4,如果直接删除4,那么对查找44就会产生影响.因此线性探测采用标记的伪删除法来删除一个元素.

二次探测
研究表明:当表的长度为质数且表装载因子 a 不超过 0.5 时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a 不超过 0.5 ,如果超出必须考虑增容.
所以,闭散列最大的缺陷就是空间利用率低.

开散列/哈希桶

开散列法又叫做链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表连接起来,各链表的头节点存储在哈希表中.

开散列的每个桶中放的都是发生哈希冲突的元素.开散列可以认为是把一个在大集合中的搜索问题转化为在小集合中搜索了.

开散列采取数组+链表的方式来组织数据.当数组长度超过64并且链表长度超过8的时候,链表就会变为红黑树.

JDK1.7及以前,链表的插入采取的是头插法,JDK1.8开始采用尾插法.

开散列法也是Java中用来解决哈希冲突所采取的方法.


三个例题理解Map和Set

//统计10w个数据中,不重复的数据(去重)
    public static void func1(int[] array) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < array.length; i++) {
            set.add(array[i]);
        }
        System.out.println(set);
    }

    //2、统计10W个数据当中,第一个重复的数据?
    public static void func2(int[] array){
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < array.length; i++) {
            if (!set.contains(array[i])){
                set.add(array[i]);
            }else {
                System.out.println(array[i]);
                return;
            }
        }
    }

    //3、统计10W个数据当中,每个数据出现的次数? 对应的关系
    public static void func3(int[] array){
        HashMap<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < array.length; i++) {
            //第一次存入
            if (map.get(array[i]) == null){
                map.put(array[i],1);
            }else {
                int val = map.get(array[i]);
                map.put(array[i],val+1);
            }
        }
        Set<Map.Entry<Integer,Integer>> set = map.entrySet();
        for (Map.Entry<Integer,Integer> entry:set) {
            System.out.println(entry.getKey() + "出现了" + entry.getValue()+"次");
        }
    }

复制带随机指针的链表

用Map去做,会变得非常容易.

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        //用Map存储新老节点的对应关系
        HashMap<Node,Node> map = new HashMap<>();
        Node cur = head;
        while(cur != null){
            Node node = new Node(cur.val);
            map.put(cur,node);
            cur = cur.next;
        }
        cur = head;
        while(cur != null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

两个对象的hashcode一样,equals不一定一样.

两个对象的equals一样,hashcode一定一样.

java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key equals 方 法。所以如果要用自定义类作为 HashMap key 或者 HashSet 的值, 必须覆写 hashCode equals ,而且要做到 equals 相等的对象, hashCode 一定是一致的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1007043.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据库】数据库系统概论(二)— 关系

关系数据库 关系数据库是支持关系模型的数据库系统。 关系模型的数据结构&#xff1a;关系&#xff1b;逻辑结构&#xff1a;扁平的二维表 域 是一组具有相同数据类型的值的集合。 例如&#xff1a;{a,b,c}、{0,1,2,3}等等 笛卡尔积 定义 笛卡尔积是域上的一种集合运算…

抓准时机,成为亚马逊,速卖通,国际站的领军者

下半年即将接踵而至的万圣节、黑色星期五、圣诞节等大型节日&#xff0c;不仅仅是海外消费者的重要消费节假日&#xff0c;也是中国出海企业势在必得的流量战场。 那么对于下半年重头戏&#xff0c;卖家该如何把握好黄金时机&#xff0c;赢得一年大丰收 旺季卖家怎么掠夺竞争…

安达发|APS自动排产软件帮助制造业实现“降本增效”

随着科技的不断发展&#xff0c;制造业在生产过程中所面临的问题也在不断地改变和升级。如今&#xff0c;制造业所面临的生产问题主要包括以下几个方面&#xff1a; 1. 生产效率低下 在传统的制造业中&#xff0c;生产过程往往依赖于人工操作&#xff0c;这导致了生产效率低下的…

紫光展锐5G芯T820 解锁全新应用场景,让机器人更智能

数字经济的持续发展正推动机器人产业成为风口赛道。工信部数据显示&#xff0c;2023年上半年&#xff0c;我国工业机器人产量达22.2万套&#xff0c;同比增长5.4%&#xff1b;服务机器人产量为353万套&#xff0c;同比增长9.6%。 作为国内商用服务机器人领先企业&#xff0c;云…

PMP含金量再升级!北京上海等地可评职称!

最近PMP证书又“升级”了&#xff0c;不过不是证书上的改变&#xff0c;而是含金量在原有基础上又上升了一个档次。 9月4日&#xff0c;北京市人力资源和社会保障局联合北京市人才工作局发布关于印发《北京市境外职业资格认可目录(3.0版)》的通知&#xff0c;PMP项目管理证书也…

实时时钟和日历电路芯片MS85163/MS85163M

MS85163/MS85163M 是一款 CMOS 实时时钟 (RTC) 和 日历电路&#xff0c;针对低功耗进行了优化&#xff0c;内置了可编程的时钟输出、中断输出和低电压检测器。所有寄存器地址和数据都通过两线双向I 2 C 总线进行串行传输&#xff0c;最大总线传输速度为 400kbit/s 。采用SOP8…

02目标检测-传统检测方法

目录 一、目标学习的检测方法变迁及对比 二、 基于传统手工特征的检测算法的定义 三、传统主要手工特征与算法 Haar特征与 人脸检测算法 - Viola-Jones(了解) HOG特征与 SVM 算法(了解)&#xff08;行人检测、opencv实现&#xff09; SIFT特征与SIFT算法(了解) DPM&#…

mysql数据库的全量与增量的备份以及恢复

目录 一、全量备份与恢复 1.全量备份 2.删掉某个数据库 3.全量恢复数据库 二、增量备份与恢复 1.查看增量是否开启&#xff0c;我们发现log_bin开启证明开启了 2.创建一个数据库并创建表中信息 3.插入数据 4.进行一次全量的备份 5.做完备份刷新一下数据库&#xff0c;…

(UI资源)4k Full Fantasy GUI + over 400 png + samples

资源包含超400个4k高清png文件 窗口资源包含:登录、角色创建、探索日志、库存、商店、设定、手工艺、NPC对话、技能、6个弹出窗口 72个独特的图标 4种颜色主题的56个图标 按钮:大按钮(3种色)、小按钮(5种颜色)、复选框和单选按钮 其他要素 使用简单的填充脚本轻松自定义健康与…

【多线程】常见的几种锁策略以及synchronized的锁策略

目录 一、乐观锁与悲观锁 二、读写锁与互斥锁 三、轻量级锁与重量级锁 四、自旋锁与挂起等待锁 五、公平锁与非公平锁 六、synchronized锁策略 1、锁升级 2、锁消除 3、锁粗化 一、乐观锁与悲观锁 乐观锁与悲观锁描述了两种不同的加锁态度&#xff0c;乐观锁就预测锁冲…

怎么为Web服务器配置虚拟主机?【步骤演示】

在安装了Web服务器Apache后&#xff0c;为了更好地使用Apache&#xff0c;还需要学习如何对Apache进行配置。在项目开发中&#xff0c;经常需要配置虚拟主机和访问权限&#xff0c;下面对Web服务器的配置进行详细讲解。 配置虚拟主机 在默认情况下&#xff0c;Apache只有Chwe…

Detectron2 安装踩坑

在服务器上安装Detectron2&#xff1a; 设备&#xff1a;NVIDIA GeForce RTX 3090 显卡驱动和cuda&#xff1a; 安装detectron2时&#xff0c;报错为&#xff1a; Installing collected packages: detectron2Running setup.py develop for detectron2error: subprocess-exited…

智能博弈技术军事应用展望

源自&#xff1a;指挥与控制学院 作者&#xff1a;马悦 吴琳 郭圣明 摘 要 为利用智能技术解决现代战争中的意图判断、威胁评估与指挥控制, 提高军事决策水平, 将军事问题转化为博弈问题, 综合利用博弈论和人工智能&#xff08;artificial intelligence, AI&#xff…

【Python 实战】---- 实现批量图片的切割

1. 需求场景 在实际开发中&#xff0c;我们会遇到一种很无聊&#xff0c;但是又必须实现的需求&#xff0c;就是比如协议、大量的宣传页面、大量的静态介绍页面、或者大量静态页面&#xff0c;但是页面高度很高&#xff0c;甚至高度可能会达到50000px&#xff0c;但是为了渲染…

手动开发-实现SpringMVC底层机制--小试牛刀

文章目录 前端控制器Controller注解RequestMapping注解自定义容器LingWebApplicationContext设计handlerList完成分发请求Service注解和AutoWired注解RequestParam注解完整代码 在这里说的底层机制的实现主要是指&#xff1a;前端控制器、Controller、Service注入容器、对象自动…

对话式人工智能的数据采集方案

基于噪声数据训练对话式人工智能 聊天机器人、虚拟助手、机器人等对话式人工智能 (Conversational AI Agents ) 在我们的日常生活中已随处可见。许多企业希望以更低的成本增强与客户之间的互动&#xff0c;并为此在该领域进行大量投资。大量数据表明&#xff0c;对话式人工智能…

泛型工具类型和操作符

前言 TypeScript 内置了一些常用的工具类型。 PartialRequiredOmitPick.... 操作符 typeof typeof 操作符可以用来获取一个变量声明或对象的类型 const p {x:2,y:cm} let g:typeof p {x:3,y:ff} 这里g需要满足&#xff1a; 有x属性且值是number类型 有y属性且值是string类型…

海鲜进口一站式数字化管理,提高工作效率

2022年&#xff0c;中国水产品进口国top10有&#xff1a;厄瓜多尔&#xff08;主要品种为白虾、剑鱼、沙丁鱼、金枪鱼等&#xff09;、俄罗斯&#xff08;主要品种为鳕鱼、鲑鱼、鲱鱼等&#xff09;、越南&#xff08;主要品种为巴沙鱼、冻虾等&#xff09;、印度&#xff08;主…

认识HTTP请求

要分析HTTP请求和响应必然少不了抓包工具&#xff0c;关于抓包工具的设置和下载推荐看抓包工具Fiddler的下载与设置 通过抓包得到的一个HTTP请求 HTTP请求的格式 结构分析 一.请求行 1.post是方法&#xff0c;可以表示一条HTTP请求要进行的操作是什么&#xff0c;post通常表示…

使用终端MobaXterm连接Centos

1. 下载MobaXterm 官网&#xff1a; https://mobaxterm.mobatek.net/download.html 2. MobaXterm连接Linux 1 、查看刚才安装的 Linux 的 IP 地址 2、连接 3. Linux自带了JRE 由于javac指令不能运行&#xff0c;所以Linux只自带了JRE&#xff01;&#xff01;&#xff01;