目录
2.开散列/哈希桶来解决哈希冲突(解决哈希冲突的第二种方法)
一.引言
引入哈希表的目的就是为了使查找和处理一个数时(不经过比较)让时间复杂度保持在O(1),这样就是为了加快查询效率,这里我们需要了解有关如何设计哈希函数以及尽可能地避免哈希冲突的方法。
二.哈希表和哈希冲突概念
使用哈希表来存储数据就是将数与存储位置通过一个函数来建立对应关系,之后不经过比较,通过这个函数(哈希函数)的结果就能确定该元素的位置。
1.通过取模运算来建立一个简单的哈希表
数据:int[] array = {1,7,6,5,4,8};
哈希函数:hash(key) = key% 表长; //这里的key为每一个元素的值,表长为10
2.什么是哈希冲突
对于上面的hash表来说,如果现在选哟插入一个元素为14,这时候通过hash函数计算出来的hash地址为4,很明显地址为4已经有了一个元素4,这时候就会发生哈希冲突。首先由于我们底层的数组一般是小于需要存储关键字的数量,所以引起哈希冲突是不可避免的,接下来是我们降低哈希冲突的方法。
3.哈希函数设计原则
- 哈希地址必须在哈希表中
- 哈希函数产生的哈希地址尽可能均匀
- 哈希函数要简单
4.常见的哈希函数
(1)直接定址法
取关键字的某个线性函数为散列地址如:
Hash(key) = A*key+B;
应用场景:查找比较小且连续的情况
(2)除留余数法
哈希函数为:Hash(key) = key%p //这里的p<=哈希表的长度
(3)平方取中法
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址。
应用场景:不知道关键字的分布且位数又不是很大。
(4)折叠法
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
使用场景:事先不知道关键字分布,而且关键字位数比较多的情况。
(5)随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。
使用场景:关键字长度不等时使用。
5.散列表的负载调节因子(与解决哈希冲突有关)
负载调节因子a=填入表中元素个数/散列表的长度。
由负载调节因子公式可以看出,a越大,散列表中元素越多,散列表空间越来越小,之后放进来的元素就越容易产生哈希冲突。
当冲突率太大的话,我们可以通过降低负载调节因子来降低哈希冲突。要是哈希表中关键字的个数不能改变时,我们就需要扩大哈希表的大小来降低哈希冲突的概率。
三.解决哈希冲突的方法
1.解决哈希冲突的第一种方法(闭散列)
什么是闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
这里将寻找key的下一个位置又有两种方式,分别如下:
(1)线性探测法
线性探测的缺点:可以看出产生冲突的数据都堆积在一起,而且还不能随意的删除哈希表中已存在的元素,因为直接删除可能会导致其他元素的查找,如果4位置的元素删除后,会影响14元素的查找,这时候我们还需要使用标记的伪元素删除法来删除一个元素(差不多就是标记一下这个元素,之后如果由其他元素可以直接覆盖,但是需要保存这个位置的信息,因为后面的元素可能选哟这个哈希地址再进行线性探测进行寻找)。
(2)二次探测法
寻找空位置的方法;Hi = (H0+i^2)%m ,(这里的H0就是通过哈希函数计算出来的哈希地址,m为哈希表的大小,i=1,2,3...,这里的i从小到大取,直到不产生哈希冲突即可,最终Hi为元素的存储位置)。
2.开散列/哈希桶来解决哈希冲突(解决哈希冲突的第二种方法)
(1)开散列/哈希同的概念
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
(2)哈希桶图示
注意;如果哈希冲突特别严重时,就说明桶下的哈希冲突元素太多,这时候也可以将问题逐渐缩小,解决的方法如:下面还有哈希表,或者转化为搜索树(思路)。
四.哈希表的性能分析
虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的,也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1)。