为什么要有哈希表
(1)顺序表中,要查找某个值value的记录,需要遍历顺序表,直到找到该值的位置,该方法的时间复杂度为O(k),0<k<=n。
(2)有序表中采用二分法查找、红黑树中数据查找的时间复杂度均为O(logn)。
这些常见的方法中,查找一个数都要通过比较得出,那么,是否能够直接通过关键字key找到元素存储的位置,令其时间复杂度为O(1)?为了满足快速查找的需求,哈希表就诞生了。
哈希表定义
在了解哈希表之前,我们需要先知道哈希函数。所谓哈希函数(也成为散列函数),就是关键字与存储位置之间建立的一个确定的对应关系f,从而保证一个关键字key对应一个存储位置f(key)。可表示为:
存储位置=f(关键字)
有了哈希函数之后,就可以根据关键字(key)与存储位置的映射关系将“键值对”存储在一块连续的存储空间中,这段连续的存储空间就是哈希表(Hash Table)或散列表。由此可见,哈希表的本质上就是一个数组。
哈希函数的构造方法
设计一个简单、均匀、存储利用率高的哈希函数是哈希表中最关键的技术。
哈希函数构造的两大原则:
1、计算简单,哈希函数的计算时间不能超过其它查找技术的查找时间。若哈希函数的算法要保证所有的关键字都不会重复,可能需要复杂的计算,会耗费很多时间,大大降低了查找效率。
2、存储地址分布均匀,保证存储空间的有效利用,并减小处理冲突的时间。
常用的哈希函数构造方法:
1、直接定址法:取关键字的某个线性函数值作为存储地址,即f(key)=a*key+b(a、b为常数)
优点:简单、均匀
应用场景:已知关键字的分布情况,适合查找表较小且连续的情况,如:统计0-100岁的人口数字
2、数字分析法:使用关键字的一部分来计算存储位置
优点:适合关键字位数较多
应用场景:已知关键字分布情况且其中若干位分布均匀,如:通过手机号查询用户名
3、平方取中法:将关键字平方,并抽取中间的几位作存储地址
优点:计算简单,不用知道关键字的分布范围
应用场景:不知道关键字的分布,且位数不是很大
4、折叠法:将关键字从左至右分割成位数相等的几部分,将这几部分叠加求和,并按哈希表表长,取最后几位作为存储地址
优点:不用知道关键字的分布范围
应用场景:适合关键字位数较多的情况
5、除留余数法:将关键字除以一个小于等于表长m的数,其余数为存储位置,f(key)=key%p (p<=m),这是最常用的哈希函数构造方法。
6、随机数法:取关键字的随机数作为存储地址,f(key)=random(key)。
处理哈希冲突的方法
要想处理哈希冲突,先要了解什么是哈希冲突?理想情况下,通过哈希函数计算的地址是不一样的,但实际中时常会碰到两个关键字不同(key1不等于key2),但是计算的存储位置相同(f(key1)=f(key2))的情况,这就是哈希冲突。这样的key1和key2称为同义词。解决哈希冲突通常有以下几种方法:
1、开放定址法(也可称为线性探测法)
概念:一旦发生了哈希冲突,就去寻找下一个空的存储地址,只要哈希表的足够大,总能找到空的存储地址,并存入哈希表中。其公式可表示为:
fi(key)=(f(key)+di)%m (di=1,2,3,...,m-1)
开放地址法的弊端是:若关键字的位置都聚集在一块儿,就需要不断处理冲突,导致存入效率、查找效率大大降低。
进一步考虑另一种情况,若哈希表的长度为10,对应的下标位置为0~9,假设只有位置5是空的,现在利用除留余数法存入key=16的值,此时f(key)=16%10=6,由此调用公式fi(key)=(6+di)%10 (di=1,2,3,…,9)找到空位置,我们发现要调用9次才能找到空位置5,可见效率极低。
考虑该方法的弊端,可以改进di=12,-12,22,-22,32,-32,…,q2,-q2,(q<=m/2),就可以双向寻找可能的空位置,从而避免关键字聚集在某一块区域。这种方法可称为二次探测法。其公式可表示为:
fi(key)=(f(key)+di)%m (di=1^2,-1^2,2^2,-2^2,3^2,-3^2,...,q^2,-q^2,q<=m/2)
2、链地址法
概念:将多个关键字为同义词的键值对存储在一个单链表中,在哈希表中只存储链表的头指针。
优点:对于会造成很多哈希冲突的哈希函数来说,都能保存在对应的存储位置上,不用冲突换址。
缺点:查找时需要遍历单链表找到要查找的关键字的位置,会造成性能上的损耗。
开放定址法和链地址法是常用的解决哈希冲突的方法。
3、再哈希函数法
概念:准备多个哈希函数,如:除留余数法、折叠法、平方取中法等,每当发生存储地址冲突时,就换一个哈希函数计算,直到把哈希冲突解决掉。
优点:关键字的存储位置不会发生聚集;
缺点:增加了哈希函数计算的时间
4、公共溢出区法
概念:除了一个哈希基本表,再准备一个溢出表,存入时,将有存储位置冲突的键值对存储到溢出表中;查找时,通过哈希函数计算出存储地址,先与基本表相应位置的关键字进行比对,若相等就查找成功,若不相等就到溢出表查找。
优点:若冲突数据较少时,查找性能较高。