散列表
散列表(hash table)是实现字典操作的一种有效数据结构。尽管在最坏的情况下,在散列表中的查找一个元素的时间与链表中查找的时间相同,为O(n)O(n)。在一下合理的假设下,在散列表中查找一个元素的时间复杂度为O(1)O(1) 。在散列表中不是直接把关键字作为数组的下标,而是根据关键字计算出相应的下标。
直接寻址表
当关键字的全域UU比较小时,直接寻址法是简单而有效的。设,且假设任意两个元素不具有相同的关键字。则数组的大小应为m。
散列表
直接寻址技术的显著缺点是:如果全域UU非常大,则在一台标准的计算机可用内存中,要存储大小为的一张表TT非常不现实。并且,实际存储的关键字集合相对UU来说也许很小。这样分配给的空间就大部分被浪费了。
散列表即:利用散列函数(hash function)h(k)h(k),有关键字kk计算出槽的位置,函数将关键字的全域UU映射到散列表(hash table)的槽位上。
存在问题:两个关键字可能会映射到同一个槽中,这叫冲突。如何解决这种冲突?
- 尽量避免冲突:试图选择一个散列函数能避免冲突或使冲突次数最小化。实际上,散列这个术语的原意就是随机混杂和拼凑,即体现了这种思想。
- 完全避免冲突几乎是不可能的,所以还是需要有解决冲突的办法
- 链接法
- 开放寻址法
链接法
操作的时间复杂度:
插入HASH_INSERT(T,x):在链表表头进行插入O(1),前提是假设插入的元素没有出现在表中。否则需要先查找。
删除HASH_DELETE(T,x):O(1),前提是假设链表为双向链表(这里没看懂,为什么双向链表可以不用在T中找x?)。否则需要先找到x的前驱节点,这样复杂度就等同于查找的复杂度。
查找HASH_SEARCH(T,k):定义T的装载因子为 ,即一个链表的平均存储元素个数。则查找操作的时间复杂度由两部分组成:
- 查找失败:Θ(1+α)Θ(1+α),在简单均匀散列的假设下,任何尚未被存储在表中的关键字k,都等可能的被散列到m个槽中,在槽中的查找时间为,Θ(α),Θ(α),1则为计算h(k)h(k)的时间
- 查找成功:Θ(1+α)Θ(1+α),根据复杂计算所得…
所以查找操作的时间复杂度为Θ(1+α)Θ(1+α)。这意味着,如果散列表中槽数至少与表中的元素个数成正比,则查找操作平均也只需要常数的时间——O(1)O(1)
思考:
算法导论练习题:11.2-3
题目:对链表法进行改进,保证链表有序,则散列性能能得到较大的提高。试分析这种改动对成功查找、失败查找、插入和删除的运行时间各有什么影响?
- 插入:由于链表有序,用插入排序比较合适,此时为时间复杂度为O(n/m)=O(α)O(n/m)=O(α)
- 成功查找和失败查找:二分法 O(lgα)O(lgα)
- 删除:与查找线性相关 O(lgα)O(lgα)
散列函数
- 除法散列法
- h(k)=k mod mh(k)=k mod m,对m的选择敏感,m一般**不为**2的幂。
- 乘法散列法
- 优点是对m的选择不敏感,m一般为2的幂。
- 全域散列法
- 随机的选择散列函数,使之独立于存储的关键字k。随机化保证了没有那种输入会始终导致最坏的性能出现(类似于快排中随机选择povio)。
开放寻址法
所有元素都存放在散列表里,也就是说每个表项或包含动态集合中的一个元素,或包含NIL。当查找某个元素时,要系统的检查所有的表项,直到找到所需元素,或最终查明改元素不在表中。与链表法不一样,这里既没有链表,也没有元素存放在散列表以外,因此开放寻址法中,散列表可能会被填满,且导致装载因子αα一定不超过1。
因为不用存储指针,所以用链表法存储指针的空间在这里可以用来存储元素,即用相同的空间存储了更多的元素槽,潜在的减少了冲突提高了检索速度。
线性探查
存在一次集群现象,随着连续被占用的槽不断增加,平均查找时间也不断增加。当一个空槽前有i个满的槽时,该空槽为下一个将被占用的概率是(i+1)/m(i+1)/m。
二次探查
同样存在二次集群现象,但程度较轻。
双重散列
开放寻址法分析
像在链表法中一样,开放寻址法的分析也是以散列表的装载因子αα来表达的。
定理11.6 给定一个装载因子为α=n/m<1α=n/m<1的开放寻址散列表,并假设是均匀散列的,则对于每一次不成功的 查找,其期望探查次数至多为1/(1−α)1/(1−α)。
定理11.8 给定一个装载因子为α=n/m<1α=n/m<1的开放寻址散列表,并假设是均匀散列的,则对于每一次成功的 查找,其期望探查次数至多为1αln11−α1αln11−α。
定理11.7 假设采用的是均匀散列,平均情况下,向一个装载因子为αα的开放寻址散列表中插入一个元素至多需要1/(1−α)1/(1−α)次探查。
完全散列
当关键字是静态(即关键字集合一旦存入后就不改变)时,完全散列技术也能提供出色的最坏情况性能,如果用该方法进行查找,能在最坏情况下用O(1)O(1)次访问完成。