Skip to content

Lecture 3. Hash Tables 哈希表 ​

哈希表是实现 字典操作(dictionaries)的高效的数据结构。

哈希表在最坏情况下,退化成普通的链表,查找的时间复杂度是 ;然而只要规划合理,查找操作可以达到 的平均时间复杂度。

像 Python 语言内置的字典(dictionary),就是用哈希表实现的。然而 Python 的字典是有序的,我觉得这可能会让时间复杂度退化到 级别。

直接寻址表 Directed-address table ​

当关键字的全域范围 不大的时候(比如 ),且键不重复,直接寻址是简单且高效的。

通常可以用一个数组(或称直接寻址表)来表示一个动态的集合,记作 。每一个下标(slot)都对应着 当中的一个键。

如图, 很小, 当中所有元素都对应着数组 当中的一个位置。数组 的每一个元素保存了键值对,或是空的:

直接寻址表

对于直接寻址表,查找、插入、删除,都很简单,都是 的,伪代码如下:

直接寻址表伪代码

然而,如果 非常大,比如 ,这种情况下,就无法使用直接寻址表了,因为内存不够。

但是,有一种可能的情况: 很大,但实际存在的键的集合 不大。那么,实际需要的内存,可能只是 的。这也避免了空间的浪费。

哈希表 hash table ​

哈希表就是,存储空间只需要 ,而效率仍然是 。

在直接寻址表当中,关键字 对应的元素,放在下标 的位置上;在哈希表当中,它在 的位置上。其中, 是哈希函数。这个 相对于 通常是非常小的。可以说 就是键 的哈希值。

但是如果哈希函数 设计的不好,可能会发生哈希 冲突(collision)。举个例子,假如 ,那么对于键 与 ,,他俩的哈希值相等。这样就会导致,这俩不同的键,映射到了同一个位置。

我们希望哈希冲突不存在,但这是不可能的,因为 ,根据抽屉原理,至少有两个键的哈希值相等。所以只能通过精心设计哈希函数,尽量的减少哈希冲突。然后找到一种方法,解决这种冲突。

链接法(chaining) ​

预备知识:实现链表的插入、查找、删除 ​

这里的链表,就当他是双向的。

元素插入到头部: ​

链表头部插入

已知键,查找元素: ​

链表查找

已知键值对,删除元素: ​

链表删除

假如只知道键,那就需要先查找到再删除了。

链接法的思想 ​

也有的教材管这个叫做「十字链表法」。这是最简单的解决哈希冲突的方式。

链接法的思想是,每个位置,都是一个指向 链表 的指针。如果这个位置没有元素,那么这个指针就是空指针;如果有,就指向一个链表,这个链表保存了所有的被哈希到这个位置的元素。

这个链表可以是单链表(singly),也可以是双向链表(doubly)。下面图示当中的是双向链表,因为双向链表的删除操作更快。至于为什么更快,稍后分析。

链接法

链接法的实现 ​

插入 ​

插入是 的。因为插入是可以插在链表头部,而不是尾部,无需遍历链表。

链接法插入

查找 ​

最坏情况下的效率是 ,这里的 指的是链表长度。

链接法查找

删除 ​

双向链表删除元素是 的(假设已查找过),因为删除元素的函数,参数是 x 而不是 k,这个 x 包含了前驱节点、后继结点的位置,所以只需要执行:T[x.prePos].nextPos = x.nextPos 和 T[x.nextPos].prePos = x.prePos 即可实现删除。

对于单链表,如果要删除的元素不在链表的两端而在中间,就需要先找到前驱再修改前驱,这需要一次遍历,所以可能会很慢。

链接法删除

链接法的性能分析 ​

插入删除都是 ,性能瓶颈在于查找,所以下面主要分析查找的性能。

假设哈希表 一共具有 个 slot、一共有 个元素需要插入表中。定义 的 装载因子(load factor) 为 ,即每个链的平均元素数量。

假设,每一个元素被哈希函数映射到每一个位置上的概率都是相等的,且每个元素的映射都相互独立,即:简单均匀散列(simple uniform hashing)。

用 表示第 个链表的长度。那么, 且 .

在一个链表当中寻找键 的元素,结果只有两种可能:要么找到了,要么没找到。

有两个定理

  • 对于简单均匀散列的链接法,没找到,平均时间复杂度是
  • 对于简单均匀散列的链接法,找到了,平均时间复杂度是

下面分别证明。

证明:简单均匀散列,链接法,没找到,平均时间复杂度是 ​

没找到,那就意味着要从表头一直遍历到末尾,那么所花费的时间就跟链表的长度有关。由于 均匀地映射到 个位置,而 个位置的平均长度是 ,所以不成功的查找,加上计算哈希函数的消耗,所需平均时间复杂度是 。

证明:简单均匀散列,链接法,找到了,平均时间复杂度是 ​

预备知识:指示器随机变量 indicator random variable ​

给定一个样本空间 和一个事件 ,那么事件 对应的指示器随机变量 定义为: