Datastruct|哈希表 (Hash Table)


文章目录

  • 哈希表
    • 哈希函数的构造方法
      • 直接定址法
      • 数字分析法
      • 平方取中法
      • 叠加法
      • 除留余数法
      • 伪随机数法
      • 小结
    • 处理冲突的方法
      • 开放定址法
      • 链地址法
      • 再哈希
      • 建立公共溢出区
    • 哈希表的查找过程

哈希表 【Datastruct|哈希表 (Hash Table)】哈希表(Hash Table)又称杂凑表或散列表。其基本思想是:首先在在数据元素的关键字 k 和数据元素的存储位置 addr 之间建立一个对应关系 H,使得 addr = H(k),H 称为哈希函数。在建立哈希表时,把关键字为 k 的元素直接存入 H(k) 的单元中。以后当查找关键字为 k 的元素时,再利用哈希函数计算出该元素的存储地址 addr = H(k),从而达到按关键字直接存取元素的目的。关键字值不同的元素可能会得到相同的地址,即 k1 != k2,但 H(k1) = H(k2),这种现象称为冲突。实际应用中,冲突是不可避免的,只能通过改进哈希函数的性能来减少冲突。由此可见,哈希表的难点主要是构造哈希函数和处理冲突。
哈希函数的构造方法 所谓优雅的哈希函数,通常要追求构造方法的简单并且发生冲突的可能性小。即,能将给的的 key 均匀的散列到地址区间。
直接定址法
直接定址法的思想是:取关键字或关键字的某个线性函数值作为哈希地址,即 H(key) = key 或 H(key) = a * key + b ,其中 a、b 为常数。具体实现时为了使哈希地址与存储地址吻合可调整 a、b 的值。
直接定址法的特点是:地址集合和关键字集合的大小相同,即一个关键字对应一个存储地址,因此不会发生冲突,而且构造方法特别简单。但是在实际应用中能使用直接定址法的哈希函数的情况很少,这种方法只适用于关键字分布基本连续,且关键字集合较小的情况。
数字分析法
如果事先知道关键字的集合,并且每个关键字的位数比哈希表的地址码位数多时,可以从关键字中选出分布较均匀的若干位,构造哈希地址。
数字分析法适用于关键字是数字的情形,且可能出现的关键字均事先知道。
平方取中法
当无法确定关键字中哪几位分布比较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为,平方后中间几位和关键字中的每一位都相关,故不同的关键字会以较高的概率产生不同的哈希地址。
叠加法
叠加法是按哈希表的地址位数将关键字分成位数相等的几部分(最后一部分可以较短或舍弃),然后将这几部分相加,舍弃最高进位后的结果就是哈希地址。
具体实现方法有折叠法与移位法。折叠法是从一端向另一端沿分割界来回折叠,然后将各段相加。移位法是将分割后的每部分低位对齐相加。例如,key = 12345678901234,哈希表长 1000,则应把关键字分成 3 位一段,在此舍弃最低两位 34。折叠法求得的哈希地址 addr = 123 + 654 + 789 + 210。移位法求得的哈希地址 addr = 123 + 456 + 789 + 012。
除留余数法
取关键字被某个不大于哈希表长的数除后所得余数作为哈希地址。
例如,p 为小于等于表长的最大素数,则哈希函数 H(key) = key % p (其中 % 为求模运算)。为了尽可能地减少冲突,通常取 p 为不大于表长且最接近表长的素数,例如,表长为 1000 时,可取 p = 997。
伪随机数法
采用一个伪随机数作为哈希函数,即 H(key) = randdom(key)。
小结
哈希函数的选择,通常考虑以下五个因素:
  • 计算哈希函数所需的时间
  • 关键字的长度
  • 哈希表的长度
  • 关键字的分布
  • 记录查找的频率
处理冲突的方法 通过构造优雅的哈希函数可以减少冲突,但一般不能完全避免冲突,因此解决冲突是哈希表的另一个关键问题。创建哈希表和查找哈希表都会遇到冲突,两种情况下解决冲突的方法应一致。
开放定址法
开放定地址法也称再散列法,其基本思想是:当关键字 key 的哈希地址 addr = H(key),出现冲突时,以 addr 为基础产生另一个哈希地址 addr1,如果 addr1 仍冲突,再以 addr1 为基础,产生另一个哈希地址 addr2,…,直到找到不冲突的哈希地址 addri,将相应的元素存入其中。
这种方法利用公式 addri = (H(key) + d[i]) % m 计算求得下一个地址,其中 H(key) 为哈希函数,m 为表长,d[i] 称为增量序列的第 i 个元素。增量序列的取值方式不同,相应的再散列方式也就不同。主要有以下几种方法:
  • 线性探测再散列,d = {1, 2, 3, …, m-1},发生冲突时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
  • 二次探测再散列,d = {12,(-1)2, 2^2, (-2)^2, …, k^2, (-k)^2} (k <= m/2),发生冲突时,在表的左右进行跳跃式探索,比较灵活。
  • 伪随机探测再散列,d = {伪随机数序列},具体实现时,应建立一个伪随机数发生器,并给定一个随机数作起点。
线性探测再散列容易产生二次聚集,即在处理同义词的冲突时又导致非同义词的冲突。线性探测再散列的特点是,只要哈希表不满,就一定能找到一个不冲突的哈希地址,而二次探测再散列和伪随机探测再散列则不一定。
链地址法
链地址法的基本思想是:将所有关键字是同义词的元素连接成一条线性链表,并将其链头存在相应的哈希地址所指的存储单元中。因此,查找、插入和删除主要是在同义词链中执行。链地址法用于经常进行插入和删除的情况。
再哈希
再哈希的思想是:当发生冲突时,再用另一个哈希函数来计算下一个哈希地址,如果再发生冲突,再使用另一个哈希函数,直到不发生冲突。具体实现应预先设置一个哈希函数序列,序列中均为不同的哈希函数,在同义词产生地址冲突时,计算另一个哈希函数地址,直到冲突不再发生。
这种方法不易产生聚集,但增加了计算时间。
建立公共溢出区
建立公共溢出区的思想是:将哈希表分为基本表和溢出表两部分,凡是与基本表发生冲突的元素一律填入溢出表。
哈希表的查找过程 哈希表的查找过程与哈希表的创建过程基本一致,当查找关键字为 k 的元素时,首先计算 addr = H(k)。如果 addr 为空,则所查元素不存在,如果 addr 中的元素的关键字为 k,则找到查找元素,否则,按照构造哈希函数时解决冲突的方法,找出下一个哈希地址,直到哈希表中的某个位置为空或元素的关键字等于给定值为止。

    推荐阅读