当前位置: 首页 > news >正文

数据结构从青铜到王者第十九话---Map和Set(2)

 🔥个人主页:寻星探路

🎬作者简介:Java研发方向学习者

📖个人专栏:《从青铜到王者,就差这讲数据结构!!!》、 《JAVA(SE)----如此简单!!!》、《数据库那些事!!!》

⭐️人生格言:没有人生来就会编程,但我生来倔强!!!



续接上一话

目录

一、Set 的说明

1、常见方法说明

2、TreeSet的使用案例

二、哈希表

1、概念

2、冲突-概念

3、冲突-避免

4、冲突-避免-哈希函数设计

4.1常见的哈希函数

4.1.1直接定制法--(常用)

4.1.2除留余数法--(常用)

4.1.3平方取中法--(了解)

4.1.4折叠法--(了解)

4.1.5随机数法--(了解)

4.1.6数学分析法--(了解)

5、冲突-避免-负载因子调节(重点掌握)


一、Set 的说明

Set 的官方文档https://docs.oracle.com/javase/8/docs/api/java/util/Set.html        Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。

1、常见方法说明

#注:

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

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

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

(4)Set最大的功能就是对集合中的元素进行去重

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

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

(7)TreeSet中不能插入null的key,HashSet可以。

(8)TreeSet和HashSet的区别【HashSet我们在后面最后会讲到】

2、TreeSet的使用案例

import java.util.TreeSet;
import java.util.Iterator;
import java.util.Set;public static void TestSet(){Set<String> s = new TreeSet<>();// add(key): 如果key不存在,则插入,返回ture// 如果key存在,返回falseboolean isIn = s.add("apple");s.add("orange");s.add("peach");s.add("banana");System.out.println(s.size());System.out.println(s);isIn = s.add("apple");// add(key): key如果是空,抛出空指针异常//s.add(null);// contains(key): 如果key存在,返回true,否则返回falseSystem.out.println(s.contains("apple"));System.out.println(s.contains("watermelen"));// remove(key): key存在,删除成功返回true//              key不存在,删除失败返回false//              key为空,抛出空指针异常s.remove("apple");System.out.println(s);s.remove("watermelen");System.out.println(s);Iterator<String> it = s.iterator();while(it.hasNext()){System.out.print(it.next() + " ");}System.out.println();
}

二、哈希表

1、概念

        顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(logN),平衡树中为树的高度,即O(logN ),搜索的效率取决于搜索过程中元素的比较次数。

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

当向该结构中:

        插入元素

                根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

        搜索元素

                对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

        该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

例如:数据集合{1,7,6,4,5,9};

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

        用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

问题:按照上述哈希方式,向集合中插入元 素44,会出现什么问题?

2、冲突-概念

        对于两个数据元素的关键字和 (i != j),有 != ,但有:Hash( ) == Hash( ),即:不同关键字通过相同哈 希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

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

3、冲突-避免

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

4、冲突-避免-哈希函数设计

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

        哈希函数设计原则

                哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1 之间

                哈希函数计算出来的地址能均匀分布在整个空间中

                哈希函数应该比较简单

4.1常见的哈希函数

4.1.1直接定制法--(常用)

        取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

        优点:简单、均匀

        缺点:需要事先知道关键字的分布情况

        使用场景:适合查找比较小且连续的情况

        面试题: 387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

        我们可以对字符串进行两次遍历。

        在第一次遍历时,我们使用哈希映射统计出字符串中每个字符出现的次数。在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回它的索引,否则在遍历结束后返回 −1。

class Solution {public int firstUniqChar(String s) {Map<Character, Integer> frequency = new HashMap<Character, Integer>();for (int i = 0; i < s.length(); ++i) {char ch = s.charAt(i);frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);}for (int i = 0; i < s.length(); ++i) {if (frequency.get(s.charAt(i)) == 1) {return i;}}return -1;}
}
4.1.2除留余数法--(常用)

        设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数: Hash(key) = key% p(p<=m),将关键码转换成哈希地址

4.1.3平方取中法--(了解)

        假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址

        平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

4.1.4折叠法--(了解)

        折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和, 并按散列表表长,取后几位作为散列地址。

        折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

4.1.5随机数法--(了解)

        选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。

        通常应用于关键字长度不等时采用此法

4.1.6数学分析法--(了解)

        设有n个d位数,每一位可能有 r 种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。

例如:

        假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前7位都是相同的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现冲突,还可以对抽取出来的数字进行反转(如1234改成4321)、右环位移(如1234改成4123)、左环移位、前两数与后两数叠加(如1234改成12+34=46)等方法。

        数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况

#注:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突

5、冲突-避免-负载因子调节(重点掌握)

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

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

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

        我们会在下一话讲到如何去解决冲突!!!

        由于内容较多,会分为多篇讲解,预知后续内容,请看后续博客!!!

http://www.xdnf.cn/news/1441765.html

相关文章:

  • 下载必要软件
  • Qt读写Excel--QXlsx基本使用
  • 基于-轻量级文档搜索系统的测试报告
  • 【GM3568JHF】FPGA+ARM异构开发板 使用指南:WIFI
  • SQLite3 操作指南:SQL 语句与 ORM 方法对比解析​
  • 存算一体:重构AI计算的革命性技术(1)
  • K8s Pod CrashLoopBackOff:从镜像构建到探针配置的排查过程
  • react-android-0.80.2-debug.aar下载很慢
  • GitHub 宕机自救指南技术文章大纲
  • Flutter Android真机器调式,虚拟机调试以及在Vscode中开发Flutter应用
  • 充电座结构设计点-经验总结
  • 10.2 工程学中的矩阵(2)
  • Android/Java 异常捕获
  • 电子病历空缺句的语言学特征描述与自动分类探析(以GPT-5为例)(中)
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘isort’问题
  • MCP模型库哪个好?2025年收录12万+服务的AI智能体工具集成平台推荐
  • AI创业公司:来牟科技-智能割草机器人
  • 如何高效记单词之:抓住首字母——以find、fund、fond、font为例
  • 股指期货放开后,市场会发生什么变化?
  • 数据结构:顺序栈与链栈的原理、实现及应用
  • 解析SWOT分析和PV/UV这两个在产品与运营领域至关重要的知识点。
  • 前端性能优化:请求和响应优化(HTTP缓存与CDN缓存)
  • Redis初阶学习
  • 宋红康 JVM 笔记 Day12|执行引擎
  • 《SVA断言系统学习之路》【03】关于布尔表达式
  • 番茄生吃熟吃大PK!VC vs 番茄红素,谁更胜一筹?医生不说的秘密!
  • 【算法--链表】142.环形链表中Ⅱ--通俗讲解如何找链表中环的起点
  • Keras/TensorFlow 中 `fit()` 方法参数详细说明
  • 编程基础-eclipse创建第一个程序
  • 存算一体:重构AI计算的革命性技术(3)