散列表
含AI生成内容
散列表
概述
散列表(Hash Table)是一种通过散列函数直接计算存储地址的查找结构。与基于比较的查找算法(如 顺序查找、折半查找)不同,散列查找的理想情况可以达到 O(1) 的时间复杂度。
散列查找的核心思想是:通过 H(key) 将关键字 key 映射为存储地址,从而直接定位目标元素,无需逐一比较。
哈希函数
除留余数法
408 考研中最常考的哈希函数是除留余数法:
H(key) = key % p其中 p 取不大于表长的最大素数(或质数),这样可以使关键字的分布更均匀,减少冲突。例如表长 m = 15,则取 p = 13。
装填因子
装填因子(Load Factor)α = n / m,其中 n 为表中元素个数,m 为表长。
- α 越大,表越满,冲突概率越高,查找效率越低
- 拉链法 α 可以大于 1;开放定址法 α 必须小于 1
- 一般建议 α ≤ 0.75
冲突与同义词
- 冲突:key₁ ≠ key₂,但 H(key₁) = H(key₂)
- 同义词:发生冲突的多个关键字互称同义词
冲突不可避免(鸽巢原理),关键是如何有效处理冲突。
拉链法(链地址法)
原理
每个哈希槽位维护一个链表,所有哈希值相同的元素挂在同一链表上:
下标: 0 1 2 ... p-1槽位: [头指针] [头指针] [头指针] ... [头指针] ↓ ↓ ↓ ↓ 链表 链表 链表 链表查找与插入
// 拉链法查找Node* search(HashTable ht, int key) { int idx = key % MAX_SIZE; Node *p = ht[idx].head; while (p != NULL) { if (p->key == key) return p; p = p->next; } return NULL;}插入采用头插法,删除可直接在链表中摘除结点——这是拉链法相比开放定址法的一大优势(开放定址法只能逻辑删除)。
ASL 分析
| 指标 | 公式 | 说明 |
|---|---|---|
| ASL 成功 | ≈ 1 + α/2 | 前提:等概率、头插法 |
| ASL 失败 | ≈ α | 对所有槽位的空链表和长度求平均 |
计算示例:哈希函数 H(key) = key % 13,依次插入关键字 {16, 74, 60, 43, 54, 90, 46, 31, 29, 88, 77},表长 m = 13。
| 关键字 | H(key) |
|---|---|
| 16 | 3 |
| 74 | 9 |
| 60 | 8 |
| 43 | 4 |
| 54 | 2 |
| 90 | 12 |
| 46 | 7 |
| 31 | 5 |
| 29 | 3 |
| 88 | 10 |
| 77 | 12 |
H(16) = H(29) = 3,H(90) = H(77) = 12,分别产生冲突挂在同一链表上。
- ASL 成功 = 各元素比较次数之和 / 11(按实际链表位置计算)
- ASL 失败 = 各槽位链表长度之和 / 13
易错:拉链法失败 ASL 的分母是表长 m(所有可能的哈希地址数),不是元素个数 n。如果哈希函数模 p 且 p < m,分母应取 p(只有 0~p-1 是合法地址)。
开放定址法
通用公式
H_i = (H(key) + d_i) % m, i = 1, 2, 3, ...其中 d_i 是第 i 次探测的增量,不同的取法对应不同的探测方法。
三种探测方法
| 方法 | 增量序列 d_i | 特点 |
|---|---|---|
| 线性探测法 | d_i = 1, 2, 3, … | 依次探测下一个位置,堆积(一次聚集)严重 |
| 二次探测法 | d_i = 1², -1², 2², -2², … | 探测哈希地址两侧,缓解堆积,表长需为 4k+3 素数 |
| 双散列法 | d_i = i x H₂(key) | 使用第二个哈希函数,聚集最轻 |
线性探测法详解
// 线性探测法查找int linearSearch(HashTable *ht, int key) { int pos = key % ht->size; int i = 0; while (i < ht->size) { int cur = (pos + i) % ht->size; if (ht->data[cur] == EMPTY) return -1; // 遇到空位,失败 if (ht->data[cur] == key) return cur; // 成功 i++; } return -1;}堆积(Clustering):线性探测法的最大缺陷。同义词和非同义词都可能争夺同一段连续区域,导致大量元素聚集,探测次数急剧增加。
易错:线性探测的堆积是非同义词也会聚集(一次聚集),二次探测的聚集是仅同义词聚集(二次聚集)。
删除问题
开放定址法不能直接删除元素。如果直接将位置置空,会导致后续探测到此位置时误认为查找失败,中断探测链。
解决方案:使用墓碑标记(Tombstone / 懒删除)——将被删除位置标记为 DELETED 状态,查找时跳过,插入时可复用。
ASL 计算示例
m = 11,H(key) = key % 11,依次插入 19, 1, 23, 14, 55:
下标: 0 1 2 3 4 5 6 7 8 9 10元素: 55 1 23 14 - - - - 19 - -查找成功 ASL = (1 + 1 + 2 + 1 + 1) / 5 = 6/5 = 1.2
查找失败 ASL:对每个哈希地址(0~10),探测到空位所需的比较次数求平均。
| 地址 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 比较次数 | 5 | 4 | 3 | 2 | 1 | 1 | 1 | 1 | 2 | 1 | 1 |
ASL 失败 = (5+4+3+2+1+1+1+1+2+1+1) / 11 = 22/11 = 2.0
易错:查找失败 ASL 的分母是表长 m(所有可能的哈希地址数),不是元素个数 n。如果 H(key) = key % p 且 m != p,分母应取 p。408 真题中 m 和 p 经常不同,这是最常见的丢分点。
拉链法与开放定址法对比
| 比较维度 | 拉链法 | 开放定址法 |
|---|---|---|
| 冲突处理 | 链表存储同义词 | 探测下一个空位 |
| 装填因子 α | 可大于 1 | 必须小于 1 |
| 删除操作 | 直接删除结点 | 只能逻辑删除(打标记) |
| 堆积现象 | 不会产生 | 会产生聚集 |
| 空间利用 | 需额外指针空间 | 无额外指针开销 |
| ASL 成功 | ≈ 1 + α/2 | 线性探测:½(1 + 1/(1-α)) |
| ASL 失败 | ≈ α | 线性探测:½(1 + 1/(1-α)²) |
| 适用场景 | 表长不确定、频繁增删 | 表长已知、较少删除 |
散列查找复杂度
| 操作 | 平均时间复杂度 | 最坏时间复杂度 | 说明 |
|---|---|---|---|
| 查找 | O(1+α) | O(n) | 平均取决于装填因子 α |
| 插入 | O(1+α) | O(n) | 需先查找位置 |
| 删除 | O(1+α) | O(n) | 拉链法直接删,开放定址法墓碑标记 |
空间复杂度:拉链法 O(m+n),开放定址法 O(m)。
考研高频考点
- 给定关键字序列和哈希函数,画出哈希表并计算 ASL(选择/填空/大题必考)
- ASL 成功和 ASL 失败的计算(几乎每年必考,注意分母区分)
- 拉链法 vs 开放定址法的优缺点对比(简答题高频)
- 线性探测法的堆积问题:同义词和非同义词共同聚集(概念辨析)
- 开放定址法不能直接删除的原因及墓碑标记方案(简答题高频)
- 装填因子 α 的定义及对查找效率的影响(概念题)
- 除留余数法中 p 的选取原则(取不大于表长的最大素数)
- 冲突与同义词的概念辨析(选择题)
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!










