跳转至

哈希表

定义

哈希表又称散列表,哈希表中存储的是一些键值对 \(key-value\) ,每个 \(key\) 可以通过一个哈希函数 \(f\) ,得到 \(value\) 的存储地址。

所以我们可以通过关键字 \(key\) 通过哈希表快速得到其对应的 \(value\),即 \(value=hash(key)\)

类似数组的下标访问,\(value=arr[index]\)

不同的是哈希表中的 \(key\) 不一定是下标索引,可以是字符串,类等等。只要我们能构造出对应的哈希函数 \(f\) 使每个关键字能映射到唯一一个存储空间上。

在理想情况下,每个 \(key\) 值能映射到的地址是不一样的。但是现实中常常会出现 \(f(k_1)=f(k_2)\) 的情况,这被叫做冲突

哈希函数

哈希表的核心就是哈希函数,一个好的哈希函数能有效提高哈希表的效率。

一个好的哈希函数有以下特点:

  • 冲突少
  • 计算时间复杂度低
  • 散列地址分布均匀

常见的哈希函数有

  • 直接定值法:就是取 \(key\) 的一个线下函数为散列地址。
  • 除留余数法:设定一个模数 \(mod\) ,对 \(key\) 取余数,存到对应下标的地址中。
  • 平方取中法:取 \(key\) 的平方后中间的几位数为散列地址。

冲突处理

无论是怎样的哈希函数,总会出现冲突,那么如何处理冲突就很重要了。

一般处理冲突有两种方法

  • 开散列
  • 闭散列

开散列

开散列的思路是在每个地址处维护一个链表,每当遇到冲突时就将键值加到链表中。

查找时,就遍历对应地址处的链表,对其中的每个数据比较其键值与查询的键值是否一致。

闭散列

闭散列的思路是当某个存储位置出现冲突时,就按照一定方法寻找一个没有冲突的地方,再存值。

如线性探测法如果在 \(d\) 处发生冲突,就依次检查 \(d + 1,d + 2\dots\)

竞赛中的使用

一般哈希表在各个语言的库中都有对应的实现,如 \(c++\) 中的 \(map,unordered\_ map\)\(map\) 是用红黑树实现的,也能实现哈希表的功能,但是会多一个 \(\log n\) 排序的复杂度),\(Python\) 中的字典 \(dict()\)

所以在竞赛中一般直接调库。


参考文章:

《大话数据结构》 Oi Wiki - 哈希表