Hash+哈希表+HashMap+HashSet
Hash+哈希表+HashMap+HashSet
- 哈希算法,是一类「算法」。
- 哈希表(Hash Table),是一种「数据结构」。
- 哈希函数,是支撑哈希表的一类「函数」。
- Map是映射/地图的意思,在Java中Map表示一种把K映射到V的「数据类型」。
- HashMap,是Java中用哈希表实现的一种「Map」。
Hash表 和HashMap的关系?
答:Hash表 是一种逻辑数据结构,HashMap是Java中的一种数据类型(结构类型),它通过代码实现了Hash表 这种数据结构,并在此结构上定义了一系列操作。
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变,此类允许使用null元素。
当有新值加入时,底层的HashMap会判断Key值是否存在(HashMap细节请移步深入理解HashMap),如果不存在,则插入新值,同时这个插入的细节会依照HashMap插入细节;如果存在就不插入
LinkedHashSet是继承自HashSet,底层实现是LinkedHashMap。
TreeSet的底层实现是TreeMap(TreeMap的底层实现借助了HashMap)
哈希表
一、哈希表概述
1.定义:存放数据的集合。
2.操作:根据(Key, Value)进行,插入、查找、删除(可以没有)。
3.空间复杂度:O(m)。
4.单次操作时间复杂度:O(1) 。
5.本质:Key的索引。
二、哈希表例题
1.题目:给出n个[0, m)范围内的整数,去重。
2.解题思路:
①快速排序:期望时间复杂度 O(nlogn) ,附加空间复杂度 O(1)。
②计数(基数)排序: 时间复杂度 O(n + m) 、附加空间复杂度 O(m)。
3.在思考一下:
若n << m,计数排序的大量空间被浪费。
只需判断是否出现过,优化?
将Key区间[0, m) 映射到 [0, p) 。
H(key) = key mod p、若m > p, 多对一的映射方式。
三、哈希表的实现
1.处理冲突(Key, Value):开放地址法(数组)、拉链法 (数组+链表)。
2.负载率:负债率=已有元素大小 / 存储散列大小。
3.哈希函数设计:负载率越低,效率越高,一般负载率小于50%。
四、哈希表应用
1.题目:设字符串A=‘12314123’,求‘123’在A中出现的次数。如果不会写KMP又想要O(n),应该怎么处理那?
2.思路:Key(‘123’) = ‘1’* 10^2 +‘2’* 10 + ‘3’* 1 = 123。
3.问题:Key相等时Value有可能不同、每次比较Value也是不小的开销,特别是Value可能很大、不考虑Value将产生错误率(错误率换时间)、多重哈希(降低错误率)。
一、Hash算法
1. 是什么?
查词典
先来看英语翻译:
hash 英 [hæʃ] 美 [hæʃ]n. 剁碎的食物;混杂,拼凑;重新表述vt. 搞糟,把…弄乱;切碎;推敲n. (Hash)人名;(阿拉伯、保、英)哈什;(西)阿什
我觉得 切碎 最适合,但正式上会被称为“散列 ”。有时候也叫“哈希 ”,据说是因为最早翻译的人以为这是某个叫Hash 的人发明的算法,所以音译了其名字;
(下面我可能会根据情况混合使用这些词,所以要记得他们是同义词)
所以
Hash算法 是这样一类算法:
这类算法接受任意长度的二进制输入值,对输入值做换算(切碎),最终给出固定长度的二进制输出值;
所以注意:Hash算法 不是某个固定的算法,它代表的是一类算法。
以更好理解的方式来说,Hash算法 是摘要算法 :也就是说,从不同的输入中,通过一些计算摘取出来一段输出数据,值可以用以区分输入数据。
所以,MD5 可能是最著名的一种Hash算法 了。
2. 有什么用?
那么,具体来说Hash/摘要/散列/切碎算法 有哪些用处呢?
信息安全领域:
Hash算法 可用作加密算法。
如文件校验:通过对文件摘要,可以得到文件的“数字指纹”,你下载的任何副本的“数字指纹”只要和官方给出的“数字指纹”一致,那么就可以知道这是未经篡改的。例如著名的MD5 ;
数据结构领域:
Hash算法 通常还可用作快速查找。
这是今天我想说的部分。根据Hash函数 我们可以实现一种叫做哈希表(Hash Table)的数据结构。这种结构可以实现对数据进行快速的存取。
接下来我们就来看看Hash算法 的一个重要的应用领域:数据结构 - 哈希表;
二、哈希表
1. 什么是哈希表
首先想一个问题:我们是如何在数据结构中做查找的呢?
- 线性表、树
线性表、树 这些结构中,记录 在结构 中的相对位置是随机的,和记录的关键字之间不存在确定关系,因此,在结构中查找时需要进行一系列和关键字的比较。这一类查找方法建立在“比较”的基础上。在顺序查找时,比较的结果为“=”与“≠”2种可能;在折半查找、二叉排序树查找和B-树查找时,比较的结果为“<”“=”“>”3种可能。查找的效率依赖于查找过程中所进行的比较次数。
- 哈希表
理想的情况是希望不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的关系为哈希(Hash)函数 ,按这个思想建立的表为哈希表 。
(插播:注意“理想情况”这几个字~~ 这会在后文给出解释)
这是《数据结构(C语言版)》[1]中引出哈希表的一段描述,通俗易懂。所以,我们知道了什么是哈希函数 和哈希表 。
- 哈希函数
- 灵活
哈希函数是一个映像,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许的范围之内即可。
- 冲突
对不同的关键字可能得到同一哈希地址,即 ,这种现象称为冲突(collision);
冲突只能尽量地少,而不能完全避免。因为,哈希函数是从关键字集合到地址集合的映像。而通常关键字集合比较大,它的元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。因此,在实现哈希表这种数据结构的时候不仅要设定一个“好”的哈希函数,而且要设定一种处理冲突的方法。
综上所述,我们可以给出哈希表的定义如下:
根据设定的Hash函数 - 和处理冲突的方法,将一组关键字映象 到一个有限的连续的地址集(区间)上,并以关键字在地址集中的象 作为记录在表中的存储位置,这样的表便称为Hash表 ;
2. 哈希函数
上面我们已经引出了并解释了Hash函数 。实际工作中,需要视不同的情况采用不同的Hash函数 ,通常要考虑的因素有:
- Hash函数 执行的时间;
- 关键字 的长度;
- Hash表 的大小;
- 关键字 的分布情况;
- 记录 的查找频率;
有如下一些常用的Hash函数 构造方法:
- 直接寻址法:
取k 或k 的某个线性函数为Hash地址 。
特点:由于直接地址法相当于有多少个关键字就必须有多少个相应地址去对应,所以不会产生冲突,也正因为此,所以实际中很少使用这种构造方法。
- 数字分析法:
首先分析待存的一组关键字 ,比如是一个班级学生的出生年月日 ,我们发现他们的出生年 大体相同,那么我们肯定不能用他们的年 来作为存储地址 ,这样出现冲突 的几率很大;但是,我们发现月日 的具体数字差别很大,如果我们用月日 来作为Hash地址 ,则会明显降低冲突几率。因此,数字分析法就是找出关键字 的规律,尽可能用差异数据来构造Hash地址 ;
特点:需要提前知道所有可能的关键字,才能分析运用此种方法,所以不太常用。
- 平方取中法:
先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
例:我们把英文字母在字母表中的位置序号作为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理我们可以得到关键字“KYAB”、“AKEY”、“BKEY”的内部编码。之后对关键字进行平方运算后,取出第7到第9位作为该关键字哈希地址,如下图所示:
关键字 |
内部编码 |
内部编码的平方值 |
H(k)关键字的哈希地址 |
KEYA |
11050201 |
122157778355001 |
778 |
KYAB |
11250102 |
126564795010404 |
795 |
AKEY |
01110525 |
001233265775625 |
265 |
BKEY |
02110525 |
004454315775625 |
315 |
特点:较常用。
- 折叠法:
将关键字分割成位数相同的几部分(最后一部分位数可以不同),然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
- 随机数法:
选择一个随机函数,取关键字的随机函数值作为Hash地址 ,通常用于关键字长度不同的场合。即
特点:通常,关键字长度不相等时,采用此法构建Hash函数 较为合适。
- 除留取余法:
取关键字被某个不大于Hash表 长m 的数p 除后所得的余数为Hash地址 。
特点:这是最简单也是最常用的Hash函数构造方法。可以直接取模,也可以在平法法、折叠法之后再取模。
值得注意的是,在使用除留取余法 时,对p 的选择很重要,如果p 选的不好会容易产生同义词 。由经验得知:p 最好选择不大于表长m 的一个质数 、或者不包含小于20的质因数的合数。
3. 处理冲突
如何处理冲突是哈希造表不可缺少的一个方面。现在完整的描述一下处理冲突:
假设哈希表的地址集为的位置上已存有记录,则“处理冲突”就是为该关键字的记录找到另一个“空”的哈希地址。
在处理冲突的过程中可能得到一个地址序列为记录在表中的地址。
(需要注意此定义不太适合链地址法)
那么,通常有以下4种方法:
- 开放定址法:
为哈希表表长;
为增量序列,有3种取法:
- ,称为线性探测再散列;
- ,称为二次探测再散列;
- ,称为伪随机探测再散列;
- 再哈希法:
均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生,这种方法不易产生聚集 ,但增加了计算时间;
- 链地址法:
将所有关键字为同义词的记录存储在同一线性表中。即在Hash 出来的哈希地址中不直接存Key ,而是存储一个Key 的链表 ,当发生冲突 时,将同义的Key 加入链表 ;
- 公共溢出区:
可以建立一个公共溢出区,用来存放有冲突的Key 。比如设立另一个哈希表,专门用来存放出现冲突的同义词。
4. 查找及分析
在哈希表上进行查找的过程和哈希造表的过程基本是一致的,过程就不累述了。我们需要看一看其查找的长度。
- 平均查找长度
- 虽然哈希表在关键字与记录的存储位置之间建立了直接映像,但由于“冲突”的存在,使得哈希表的查找过程仍然是一个“给定值和关键字进行比较”的过程。因此,仍需以平均查找长度作为衡量哈希表的查找效率的量度;
(还记得上面我们说的“理想情况下”吗?~~ 现实告诉我们,一般情况下,还是不得不需要“比较”!)
在一般情况下,我们设计的哈希函数肯定是尽量均匀的,所以可以不考虑它对平均查找长度的影响。那么,处理冲突方法相同的哈希表,其平均查找长度就依赖于哈希表的装填因子了。其定义如下:
标志哈希表的装满程度
直观的看:
- 越小,发生冲突的可能性就越小;
- 越大,代表着表中已填入的元素越多,再填入元素时发生冲突的可能性就越大。那么在查找时,给定值需要比较的关键字的个数就越多;
结论如下:
哈希表的平均查找长度是的函数,而不是n的函数。因此,不管n多大,我们总是可以选择一个合适的装填因子以便将平均查找长度限定在一个范围内。(Java中HashMap的默认装填因子是0.75)
三、数据结构、数据类型
在看Java的HashMap之前,插播一点重要的数据结构要点。
1. 数据结构(data structure)
- 数据结构表达的是:用什么样的结构,组织一类数据。
- 分为逻辑结构和物理结构:
- 基本的逻辑结构有:集合、线性结构、树形结构、图;
- 物理结构:顺序存储、链式存储;
2. 数据类型(data type)
- 数据类型是和数据结构密切相关的,它是:值的集合和定义在这个值集上的一组操作的总称。
例如:C语言中的一种数据类型:整型变量,其值集为某个区间上的整数,定义在这些整数上的操作为加、减、乘、除和取模等算数运算。
- 高级语言中数据类型分为两类:
- 原子类型:值不可分解,是什么就是什么。如整型、字符型等;
- 结构类型:其值是由若干成分按某种结构组成的,因此可分解,并且它的成分可以是原子类型也可以是结构类型。比如数组,其值是由若干分量组成的,每个分量可以是整数,或者也可以是数组。
- 所以,结构类型可以看成由一种数据结构和定义在其上的一组操作组成。
- 所以你看,数据结构仅仅代表着一种结构,而我们在编程语言中是使用数据类型,如果编程语言想要实现某种数据结构,那么必须将其封装为一种数据类型,更狭义的说是数据类型中的结构类型。
3. 深入理解
也许你还是有些混沌,但是没关系,在哪里跌倒就在哪里睡着嘛~ 我再说点能让你深入理解的…
- 实际上,在计算机中,数据类型的概念并非局限于高级语言中,每个处理器[a]都提供了一组原子类型或结构类型。
- 例如,一个计算机硬件系统通常含有“位”、“字节”、“字”等原子类型,他们的操作通过计算机设计的一套指令系统直接由电路系统完成;
- 而高级程序语言提供的数据类型,其操作需要通过编译器或解释器转化为底层,即汇编语言或机器语言的数据类型来实现。
- 引入“数据类型”的目的,
- 从硬件角度看,是作为解释计算机内存中信息含义的一种手段,
- 而对使用数据类型的用户来说,实现了信息的隐蔽,即将一切用户不必了解的细节都封装在类型中。
- 例如,用户在使用“整数”类型时,既不需要了解“整数”在计算机内部是如何表示的,也不需要知道其操作是如何实现的。
- 如“两个整数求和”,程序员注重的仅仅是其“数学上求和”的抽象特性,而不是其硬件的“位”操作如何进行。
([a]:处理数据的单元,不局限于CPU,包括硬件系统、操作系统、高级语言、数据库等)
所以,
在编程语言中运用“数据结构”就是在使用被一层一层封装起来的某种数据类型
在编程语言中运用“数据结构”就是在使用被一层一层封装起来的某种数据类型
在编程语言中运用“数据结构”就是在使用被一层一层封装起来的某种数据类型
四、Java的HashMap
FAQ:
- 为什么要有HashMap?
答:我非常期待能在Java 中使用Hash表 这种数据结构 ,因为它的快速存取特性。
- Hash表 和HashMap的关系?
答:Hash表 是一种逻辑数据结构,HashMap是Java中的一种数据类型(结构类型),它通过代码实现了Hash表 这种数据结构,并在此结构上定义了一系列操作。
- 这一章节我们要干嘛?
答:首先要明白我们是在干嘛,我们是在分析一个叫做哈希表的数据结构吗?
不是!我们是在讨论一种高级程序设计语言中某个数据类型的实现,它实现了哈希表这种数据结构,但它绝不是哈希表本身,它就是它自己 - HashMap类型。
不明白的话我再说一句:记不记得你学Map(HashMap父接口)时见到的第一句描述“An object that maps keys to values. ”简单翻译就是:Map是一个键值对对象。但是,可没人告诉过你哈希表是键值对结构。
- Java中的数据类型
答:有些话不明白的说出来,其实容易让人想不明白。所以我想说:
- 实际上,编程语言中数据类型都是层层封装的结果;
- 实际上,Java 中只有3类数据类型:原生类型(primitive8个)、数组、Object;
- 实际上,无论官方的集合框架也好,你自己创建的类也好,都只能是源自于Object并依赖于原有的这3类数据类型;
- 最终,到现在你可能才会发现,“数组”这种类型竟是如此的重要,在Java 中,如果没有数组作为基础结构,你是不可能构造出任何想实现某种数据结构的Object类型的。
其实有了以上内容,你应该可以轻松的看懂HashMap的源码了,不过我们还是一起来看一下↓
- 上帝视角的HashMap
- HashMap是基于数组来实现哈希表的,数组就好比内存储空间,数组的index就好比内存的地址;
- HashMap的每个记录就是一个Entry<K, V>对象,数组中存储的就是这些对象;
- HashMap的哈希函数 = 计算出hashCode + 计算出数组的index;
- HashMap解决冲突:使用链地址法,每个Entry对象都有一个引用next来指向链表的下一个Entry;
- HashMap的装填因子:默认为0.75;
- 基本上HashMap就像这样:
。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
HashMap延伸到Object对象
不知道大家还记得Object的equals()和hashCode()方法吗?(Java - Object类),Object中这样描述道:
- hashCode():general contract说到:equals的对象必须有相同的哈希码。
- equals()方法说:覆盖此方法,通常有必要重写hashCode()方法,以维护其general contract;
就是说要记住下面的话:
覆盖equals方法通常有必要也覆盖hashCode方法,因为你必须保证对象equals,hashCode必须相等!
覆盖equals方法通常有必要也覆盖hashCode方法,因为你必须保证对象equals,hashCode必须相等!
覆盖equals方法通常有必要也覆盖hashCode方法,因为你必须保证对象equals,hashCode必须相等!
那为什么要保证这个保证呢?学完HashMap我们就知道了原因:
- 首先,祖先类Object保证了以上话述:
- 从Object的equals()默认是使用==来判断的;
- hashCode()是native方法,它是直接依赖C来算出的。
- 所以,做子孙们的必须要遵循祖制!:
那如果不遵循呢?会造成使用Hash函数的数据类型出现错误!!!,你就用不了哈希表这种数据结构了!!!
会出什么错?为什么会出错?我们上面看过了HashMap源码,可以容易知道:
- 导致错误计算index,覆盖操作失效!
- 原因:
假设你将equals覆盖为name相等即相等(张三等于张三),不过你没覆盖hashCode();
put(key)操作,由于你的新key和HashMap中原有的老key是两个不同的对象,尽管他们equals,不过由于继承自Object的hashCode()方法给出了两个不同的hashCode,在根据hashCode计算出index这一步,它们两个属于不同的index!
这直接导致本该是一次覆盖的操作,却做成了新增了一个值的操作!
所以,要避免出现这个问题,就必须在改写equals()的同时改写hashCode(),以保证对象equals则HashCode一致。
你可以看到官方提供的这些API类中,如果它需要覆盖equals()那么在同时也都覆盖了hashCode(),我们写class时也要这样。
参考:java中HashMap与Hash表详解
参考:几道和散列(哈希)表有关的面试题
参考:动画:什么是散列表?
参考:微信公众号+程序员小吴 +五分钟学算法
参考:深入理解HashSet(底层是HashMap)
Hash+哈希表+HashMap+HashSet相关推荐
- hashmap是散列表吗_一篇文章教你读懂哈希表-HashMap
题图Pid=68670770 在最近的学习过程中,发现身边很多朋友对哈希表的原理和应用场景不甚了解,处于会用但不知道什么时候该用的状态,所以我找出了刚学习Java时写的HashMap实现,并以此为基础 ...
- [剑指offer]面试题第[50]题[JAVA][第一个只出现一次的字符][哈希表][HashMap]
[问题描述][简单] 在字符串 s 中找出第一个只出现一次的字符.如果没有,返回一个单空格. s 只包含小写字母.示例:s = "abaccdeff" 返回 "b&quo ...
- java中哈希表HashMap详解遍历操作
一.主题外谈 在进入主题之前,先讲一点与哈希表相关的一些东西: 1.Hash算法, 通常还可用作快速查找. 2.哈希函数,是支撑哈希表的一类「函数」. 3.哈希表(Hash Table),是一种**「 ...
- Hash哈希(hashCode、HashSet 、HashMap)
文章目录 Hash HashMap类 存储 HashMap的长度 Java的hashCode()方法 hashCode() 与 equals() 的关联 情况1(不重写hashCode()和equal ...
- 纸上谈兵: 哈希表 (hash table)
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! HASH 哈希表(hash table)是从一个集合A到另一个集合B的映射(map ...
- 左神算法课笔记(二):链表、栈和队列、递归Master公式、哈希表、有序表
单向链表 双向链表 单链表.双链表最简单的面试题 1.单链表和双链表如何反转 package class02;import java.util.ArrayList;public class Code0 ...
- 【数据结构与算法】五、哈希表和链表
前言: 大家好,我是春风 今天继续刷左神的算法视频,前面刷完了入门的查找和排序算法,也对排序算法做了一些总结. 现在开始刷结构部分,第一个结构是哈希表,然后是链表.哈希表的应用在Java中有现成的ma ...
- C++八股文分享---数据结构其二---哈希表
C++八股文分享-数据结构其二-哈希表 前言 什么是哈希表? 搜索二叉树对值的查找是通过从根节点开始,逐个节点与目标值做比较,向下查找,直至找到目标值或是到达根节点未查找到,时间复杂度为O(logn) ...
- 制作销售属性的组合的集合为哈希表,根据用户点击的属性组合判断某个skuId并跳转到对应的页面
一.查询spu系列商品: 1.查询spu系列商品中所有属性值之间的组合(一个组合,即一个sku商品)!!! 这里的数据苹果11 颜色:黑色.白色.红色: 版本:128G.256G: 总共6种组合: S ...
最新文章
- 为 Asp.net 网站新增发送手机短信功能
- 浏览器前进后退对下拉框数据的丢失(省市联动实现和例子)
- 5.4Python数据处理篇之Sympy系列(四)---微积分
- EasyUI框架入门学习
- Java 技术篇 - 从指定的web网页页面中读取html内容实例演示,从http协议下的url地址中读取web页面内容方法
- 程序设计竞赛算法基础考试真题2020年(回忆版)
- QT的QLatin1String类的使用
- 这个库厉害了,自动补全Python代码,节省50%敲码时间
- 转】.NET强名称工具(Sn.exe)使用详解
- ASP.NET MVC应用程序把文字写在图片上
- 关于单体化和属性文件的说明
- 在IDEA中实战Git-branch入门
- 密码学原理与实践_浅谈SSH2工作原理
- 苹果设备型号代码 device model id / device codes(更新至iPhone 13 / iPhone SE3 / iPad Air 5代
- ug计算机环境变量,ug80添加中文环境变量的具体方法
- Git撤销操作之使用--amend改写单次提交
- 【数字信号处理】Python离散信号卷积的代码实现/时域直接法/列表法/信号与系统
- C++单链表学生管理系统(有登录界面)
- win 10 caffe python=3.5 小白安装全过程(最详细,完美解决各种版本不兼容等问题)
- 计算机制图员主要学什么,计算机辅助设计绘图员高级绘图员(机械类)考试说明...
热门文章
- 操作系统面试知识点总结2
- Python3面向对象编程的三大特性 封装、继承、多态
- 基于双目视觉的目标检测与追踪方案详解
- 超越YOLOv5,1.3M超轻量,高效易用,目标检测领域这一个就够了!
- 三维点云对应关系聚合算法的性能评价
- ios mysql 修改数据,iOS数据库FMDB--增删改查(模糊查询)实写记录
- ecplise中插件Jrebel的安装
- Nat. Biotechnol | PHATE:高维生物数据的可视化方法
- android 桌面提醒功能,安卓手机桌面上使用的工作提醒软件选择哪个?
- 如何用python实现自动化办公_python自动化办公操作PPT的实现