散列表
在线性表、树等数据结构中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此在结构中查找记录时需进行一系列和关键字的比较。它们查找的效率都依赖于查找过程中所进行的比较次数。
1、散列表的强大
即使是二分查找,查找的时间复杂度也是 O(log2N)。
但是——散列表,理想的情况下有没有不经过任何比较,一次存取便能得到所查记录。时间复杂度是 O(1)。
2、散列表的实现
1、使用散列函数将给定键转化为一个“数组的索引”,数组内可以存储记录对应的值,这个函数,我们称之为散列(哈希)函数;
2、但对于不同的关键字,可能得到同一哈希地址,即k1≠ k2,而f(k1) = f(k2),这种现象我们称之为冲突。冲突只能尽可能的少,但是不能完全避免;
2.1 散列函数描述
哈希表的容量一般设置为素数,因为无论按照何种步长,总可以遍历素数。
2.1.1 散列函数特征
1、相同输入映射到相同索引;
2、不同输入映射到不同索引;
3、散列表知道数组多大,只返回有效索引
构造方法:
直接定址法:
Hash(key) = a × key + b
数字分析法:
比如手机号前三位是接入号,中间四位是 HLR 识别号,只有后四位才是真正的用户号,此时我们选择后四位作为散列地址就是不错的选择;
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
平方取中法:
如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法
平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况
折叠法:
1、将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些)
2、然后将这几部分叠加求和
3、按散列表表长,取后几位作为散列地址
除留余数法
Hash(key) = key / a ... b ,b即为数组地址
随机数法
2.2冲突处理
2.2.1 拉链法
如果多个键映射到了同一位置,就在这个位置存储一个链表。
2.2.2 开放地址法
数据项具有相同下标时,在数组中其他地方寻找一个空白单元放置要插入的数据项。
2.2.2.2 线性探测:
在用除留余数法时:Hi = (Hash(key) + di) mod m di为增量序列
线性探测中di每次冲突都加1,知道找到空序列;
删除操作:删除操作复杂,这里不进行讨论
查找操作:
线性探测是逐步搜索空的位置。缺点:数据项聚集,越来越大,越后面找到空位插入数据项需要时间越多。
2.2.2.3 二次探测法:
Hi = (Hash(key) + di) mod m di为增量序列
di先后取(±1)2 ,(±2)2,(±3)……
2.2.2.4 伪随机探测法:
di为伪随机数
2.2.3 再散列法(双散列函数)
2.2.4 建立公共溢出区
3、应用
3.1 DNS解析
如:在访问像http://adit.io这样的网站时,计算机必须将adit.io转换为IP地址:
ADIT.IO -> 173.255.248.55
无论哪个网站,网址都要转化为IP地址:
GOOGLE.COM -> 74.125.239.133
FACEBOOK.COM -> 173.252.120.6
这是将网址映射到IP地址,这过程被成为DNS解析,散列表是提供这种功能方式之一。
3.2 缓存内放入散列表加快访问速度
3.3 总结:
散列函数在查找、加密、海量数据处理过程都有强大优势;
如果在能提前预测数据量的大小。那么哈希表在速度和易用性方面——无与伦比!!!
缺点:数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重
链接: 开放地址法描述.
链接: 开放地址法哈希表代码篇.
链接: 散列函数描述.