什么是哈希冲突:
- 不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
哈希冲突的避免:
- 首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率。
1. 哈希函数的设计
- 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中
- 哈希函数应该比较简单
常用哈希函数:
- 直接定制法–(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况
public int hashFunc(int key) {
return 2 * key - 1;
}
- 除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
public int hashFunc1(int key) {
return key % this.array.length;
}
- 常用的字符串的哈希算法
- MD5算法
- 无论输入多长的字符串,最终得到的MD5值的长度都是固定的。
- 如果输入俩个字符串很相似,就只有一个字符不同,得到的MD5值相差也会非常大。
- 通过字符串计算MD5值很高效,反之不行(给定MD5 值求字符串要经过大量计算,理论上是不可行的除非字符串很简单如:abc)
- MD5也常用到一些需要加密的场景和一些传输数据的校验上。
- SHA1算法
理论上比MD5更安全,也更简便,但是效率不如MD5
- 负载因子调节
- 负载因子的定义为:填入表中的元素个数 / 散列表长度
- 对于开方地址法,负载因子特别重要,要严格控制在0.7~0.8以下,一旦超过0.8哈希表的查找效率就非常低,所以在java的系统库限制了负载因子为0.75,超过或者达到就会resize(扩容)。
- 已知哈希表中的关键字个数是不可变的,那我们就只有调整哈希表中的数组的大小。(扩容)
哈希冲突的解决:
- 常见的俩种解决思想就是闭散列、开散列
1. 闭散列(不推荐这种方式)
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
- 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。(再进行删除操作的时候易出错)
- 二次探索:从发生冲突的位置开始,依次向后探测,直到寻找到下(n^2)空位置为止。(再进行删除操作的时候易出错,而且空间利用低)
2. 开散列(哈希桶)(推荐)
- 开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
这个我有具体实现:
点击这里查看:如何利用开散列(哈希桶)的思想解决哈希冲突,实现哈希表的增加(扩容)、查找、删除