散列表
概述
散列表(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)定义为: 其中 是当前存储的元素数量, 是散列表的槽数(表的大小)。装载因子衡量了散列表的”满”程度。
时间复杂度
散列表的操作时间是期望 ,而非最坏情况 。
| 操作 | 期望时间 | 最坏时间 | 说明 |
|---|---|---|---|
| 搜索 | 最坏情况所有元素冲突到同一槽位 | ||
| 插入 | 同上 | ||
| 删除 | 同上 |
冲突问题
冲突
当两个关键字 满足 时,称为发生了冲突(collision)。
由于 ,根据鸽巢原理,冲突不可避免。处理冲突的两种主要方法:
散列函数的要求
一个好的散列函数应满足:
- 确定性:同一关键字始终映射到同一槽位
- 均匀性:将关键字尽可能均匀地分散到各槽位
- 高效性:计算 的时间为
详见 散列函数。
章节扩展
散列表 vs. 直接寻址表
| 特性 | 直接寻址表 | 散列表 |
|---|---|---|
| 映射方式 | (直接) | (间接) |
| 空间 | , 可远小于 | |
| 最坏时间 | ||
| 期望时间 | ||
| 冲突 | 无 | 有(需专门处理) |
散列表 vs. 平衡搜索树
| 特性 | 散列表 | 平衡搜索树(如红黑树) |
|---|---|---|
| 搜索(期望) | ||
| 搜索(最坏) | ||
| 有序遍历 | 不支持 | 支持 |
| 适用场景 | 无序字典、快速查找 | 需要范围查询/有序操作 |