散列表
散列表(hash table)是实现字典操作的一种有效数据结构。尽管在最坏的情况下,在散列表中的查找一个元素的时间与链表中查找的时间相同,为 O(n) O ( n ) 。在一下合理的假设下,在散列表中查找一个元素的时间复杂度为 O(1) O ( 1 ) 。在散列表中不是直接把关键字作为数组的下标,而是根据关键字计算出相应的下标。
直接寻址表
当关键字的全域 U U 比较小时,直接寻址法是简单而有效的。设,且假设任意两个元素不具有相同的关键字。则数组的大小应为m。
散列表
直接寻址技术的显著缺点是:如果全域 U U 非常大,则在一台标准的计算机可用内存中,要存储大小为的一张表 T T 非常不现实。并且,实际存储的关键字集合相对 U U 来说也许很小。这样分配给的空间就大部分被浪费了。
散列表即:利用散列函数(hash function) h(k) h ( k ) ,有关键字 k k 计算出槽的位置,函数将关键字的全域 U U 映射到散列表(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 ( l g α )
- 删除:与查找线性相关 O(lgα) O ( l g α )
散列函数
- 除法散列法
- h(k)=k mod m h ( k ) = k m o d 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 α l n 1 1 − α 。
定理11.7 假设采用的是均匀散列,平均情况下,向一个装载因子为 α α 的开放寻址散列表中插入一个元素至多需要 1/(1−α) 1 / ( 1 − α ) 次探查。
完全散列
当关键字是静态(即关键字集合一旦存入后就不改变)时,完全散列技术也能提供出色的最坏情况性能,如果用该方法进行查找,能在最坏情况下用 O(1) O ( 1 ) 次访问完成。