散列表

概述

散列表(Hash Table) 是一种通过散列函数 将关键字映射到数组槽位来实现字典操作的通用数据结构。它以 期望时间完成搜索、插入和删除,是实际应用中最常用的字典实现之一。与 直接寻址表 不同,散列表使用远小于全域 的存储空间 ,通过散列函数将关键字压缩映射,代价是需要处理冲突(collision)问题。

定义

散列表

散列表是一种基于数组的数据结构,它使用一个散列函数 将全域 中的关键字 映射到大小为 的数组 的某个槽位(slot)上。当两个不同的关键字被映射到同一个槽位时,称为冲突(collision)。

核心思想

散列表的核心思想是:用少量的空间 + 散列函数,将大域映射到小表

  • 直接寻址:关键字 槽位 (需要 空间)
  • 散列:关键字 槽位 (只需要 空间,

伪代码

搜索:

HASH-SEARCH(T, k)
    return LIST-SEARCH(T[h(k)], k)   // 链地址法

插入:

HASH-INSERT(T, x)
    LIST-INSERT(T[h(x.key)], x)      // 将 x 插入到 T[h(x.key)] 对应的链表头部

删除:

HASH-DELETE(T, x)
    LIST-DELETE(T[h(x.key)], x)      // 从 T[h(x.key)] 对应的链表中删除 x

核心性质

装载因子

装载因子

散列表的装载因子(load factor)定义为: 其中 是当前存储的元素数量, 是散列表的槽数(表的大小)。装载因子衡量了散列表的”满”程度。

  • 时,表为空;当 时,平均每个槽位有一个元素。
  • 装载因子直接影响散列表的性能: 越大,冲突概率越高,操作时间越长。
  • 对于 链地址法 可以大于 1;对于 开放寻址法,必须保证

时间复杂度

散列表的操作时间是期望 ,而非最坏情况

操作期望时间最坏时间说明
搜索最坏情况所有元素冲突到同一槽位
插入同上
删除同上

冲突问题

冲突

当两个关键字 满足 时,称为发生了冲突(collision)。

由于 ,根据鸽巢原理,冲突不可避免。处理冲突的两种主要方法:

  1. 链地址法(Chaining):每个槽位维护一个链表,冲突的元素串在同一链表中
  2. 开放寻址法(Open Addressing):所有元素都存放在表内,冲突时按探测序列寻找下一个空槽

散列函数的要求

一个好的散列函数应满足:

  • 确定性:同一关键字始终映射到同一槽位
  • 均匀性:将关键字尽可能均匀地分散到各槽位
  • 高效性:计算 的时间为

详见 散列函数

章节扩展

散列表 vs. 直接寻址表

特性直接寻址表散列表
映射方式(直接)(间接)
空间 可远小于
最坏时间
期望时间
冲突有(需专门处理)

散列表 vs. 平衡搜索树

特性散列表平衡搜索树(如红黑树)
搜索(期望)
搜索(最坏)
有序遍历不支持支持
适用场景无序字典、快速查找需要范围查询/有序操作

参见

  • 直接寻址表 —— 散列表的前身,用直接映射实现字典
  • 散列函数 —— 散列表的核心组件,决定映射质量
  • 链地址法 —— 冲突处理方法之一,用链表存储冲突元素
  • 开放寻址法 —— 冲突处理方法之二,在表内探测空槽