我们知道数组的特点是寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的散列表,也可以称为哈希表。散列表常常适用于要求查找性能高,并且数据元素之间无逻辑关系的场景中,例如Redis中的字典的底层就是利用散列表实现的。除此之外,散列表还被用在数字签名和文件校验中。
01散列表(哈希表)散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构,用函数思想描述的话,就是关键字为 算法专题 之 散列表(上)_散列函数存放在算法专题 之 散列表(上)_数组_02 的位置上,其中对应关系 f 称为散列函数,按这个思想建立的表为散列表。理想散列表(哈希表)是一个包含关键字的具有固定大小的数组,它能够以常数时间执行插入,删除和查找操作。但是理想终究逃不过现实,现实中往往由于数组单元有限或者散列函数设计不合理,都会会出现两个不同的关键字映射到同一个单元,从而导致散列冲突。
02散列函数的设计从上述分析可以得到两个核心点:一方面是需要一个优秀的散列函数,另外一方面我们需要一个合理的解决散列冲突的方案。这一节先介绍一下如何根据场景选择合适的构造散列函数的方法。2.1 标准一个优秀的散列函数的标准 = 计算简单 + 分布均匀

2.2 构造散列函数的方法

(1)直接寻址法(直接定址法):说明:f(key)=a*key+b (a,b都是常数)优点:简单、均匀,不会产生冲突缺点:需要知道关键字的分布,现实中不常用适用场景:适合查找表较小且连续的情况(2)数字分析法说明:抽取关键字中的一部分来计算存储位置,这个方法大部分人不太能从字面含义理解,下面举一个栗子:以11位的手机号作为关键字。算法专题 之 散列表(上)_存储位置_03手机号一共11位,前3位是网络识别号,对应不同运营商的子品牌;中间4位表示归属地;最后4位是用户号。不同手机号前7位相同的可能性很大,所以可以选择后4位作为散列地址,或者对后4位反转(1234 -> 4321)、循环右移(1234 -> 4123)、循环左移等等之后作为散列地址。适用场景:数字分析法通常适合处理关键字位数比较长的情况,如果事先知道关键字的分布且关键字的若干位分布比较均匀,就可以考虑这个方法。(3)平方取中法说明将关键字先平方,然后截取中间x位作为存储位置适用场景:适合用于不知道关键词分布,且位数不长的情况(4)折叠法说明:将关键字拆分成若干部分后累加起来,根据散列表表长取总和的后若干位作为存储位置适用场景:适用于不知道关键字分布,且位数较长的情况(5)除留余数法说明:f(key)=key mod p (p<=m),m是散列表表长,p取小于等于m的最小质数或者不包含小于20质因子的合数,以减少冲突的情况(6)随机数法说明:f(key)=random(key)注意random的随机种子需要是固定的,以便查询的时候能够根据key重新找到存储位置适用场景:适用于关键字长度不等的情况

算法专题 之 散列表(下)将对散列冲突的解决方案,以及散列表和二叉树的选择进行详细介绍。
算法专题 之 散列表(上)_散列表_04