题解:AcWing 840 模拟散列表
【题目来源】
AcWing:840. 模拟散列表 - AcWing题库
【题目描述】
维护一个集合,支持如下几种操作:
(1)I x,插入一个数x;
(2)Q x,询问数x是否在集合中出现过;
现在要进行N次操作,对于每个询问操作输出对应的结果。
【输入】
第一行包含整数N,表示操作数量。
接下来N行,每行包含一个操作指令,操作指令为I x,Q x中的一种。
【输出】
对于每个询问指令Q x,输出一个询问结果,如果x在集合中出现过,则输出 Yes,否则输出No。每个结果占一行。
【输入样例】
5
I 1
I 2
I 3
Q 2
Q 5
【输出样例】
Yes
No
【核心思想】
-
问题分析:需要维护一个支持插入和查询操作的整数集合。若用数组或链表直接存储,查询需要 \(O(n)\) 遍历。关键观察是:通过哈希函数将大值域的整数映射到小范围的数组下标,实现 \(O(1)\) 的期望时间复杂度操作。
-
算法选择:
- 拉链法(Separate Chaining):每个哈希桶维护一个链表,冲突元素链式存储
- 开放寻址法(Open Addressing):冲突时线性探测下一个空位,所有元素存储在数组中
- STL 向量法:利用
vector简化拉链法的链表实现
-
关键步骤(拉链法):
- 哈希函数:\(k = ((x \bmod N) + N) \bmod N\),处理负数确保非负
- 插入:计算哈希值 \(k\),头插法将新节点插入 \(h[k]\) 链表头部
- 查询:计算哈希值 \(k\),遍历 \(h[k]\) 链表查找 \(x\)
- 数组模拟链表:
e[idx]存值,ne[idx]存下一个节点下标,h[k]存链表头指针
-
关键步骤(开放寻址法):
- 哈希函数:同拉链法
- 查找
find(x):从 \(k\) 开始线性探测,直到遇到空位或找到 \(x\) - 插入:
find(x)返回的位置即为应插入位置 - 查询:若
find(x)返回的位置值不为空,则存在
-
时间/空间复杂度:
- 时间复杂度:期望 \(O(1)\),最坏 \(O(n)\)(大量冲突时)
- 空间复杂度:\(O(N)\),\(N\) 为哈希表大小
-
散列表的核心思想:
- 哈希映射:通过哈希函数将任意整数映射到固定范围的数组下标,实现值域压缩,使 \(O(1)\) 访问成为可能
- 冲突处理:不同值映射到同一位置时,拉链法用链表存储所有冲突元素,开放寻址法通过探测寻找下一个空位。两种方法各有优劣:拉链法实现简单、删除方便;开放寻址法缓存友好、空间利用率更高
- 质数取模:哈希表大小取质数(如 \(100003\))可减少冲突,因为质数与大多数数值互质,哈希分布更均匀
- 负数处理:
(x % N + N) % N确保哈希值始终非负,兼容正负整数输入 - 适用于快速查找、去重、计数等场景,是数据库索引、编译器符号表等系统的底层数据结构
【解题思路】

【算法标签】
哈希表
【代码详解】
// 拉链法
#include <bits/stdc++.h>
using namespace std;
const int N = 100003; // 哈希表的大小,一个质数
int h[N]; // 哈希表,h[k]存储哈希值为k的链表的头指针
int e[N]; // 存储实际插入的值
int ne[N]; // 存储下一个节点的索引
int idx; // 当前可用的节点索引
// 向哈希表中插入一个数x
void insert(int x)
{
// 计算哈希值,处理负数
int k = (x % N + N) % N;
// 头插法:将新节点插入到链表头部
e[idx] = x; // 存储值
ne[idx] = h[k]; // 新节点指向原来的头节点
h[k] = idx; // 更新头节点为新节点
idx++; // 移动到下一个可用位置
}
// 在哈希表中查找数x是否存在
bool find(int x)
{
// 计算哈希值,处理负数
int k = (x % N + N) % N;
// 遍历哈希值为k的链表
for (int i = h[k]; i != -1; i = ne[i])
{
// 如果找到相同的值,返回true
if (e[i] == x)
{
return true;
}
}
// 没有找到,返回false
return false;
}
int main()
{
int n; // 操作次数
scanf("%d", &n);
// 初始化哈希表,所有链表的头指针设为-1(表示空链表)
memset(h, -1, sizeof(h));
while (n--)
{
char op[2]; // 操作类型
int x; // 操作的值
// 读取操作和值
scanf("%s%d", op, &x);
if (*op == 'I') // 插入操作
{
insert(x);
}
else // 查询操作
{
if (find(x))
{
puts("Yes");
}
else
{
puts("No");
}
}
}
return 0;
}
// 开放寻址法
#include <bits/stdc++.h>
using namespace std;
const int N = 200003; // 哈希表的大小,一般取质数,且至少是数据量的2倍
const int null = 0x3f3f3f3f; // 特殊值,表示该位置为空
int h[N]; // 哈希表数组
// 查找函数:找到x应该插入的位置,或者x当前所在的位置
int find(int x)
{
// 计算哈希值,处理负数
int k = (x % N + N) % N;
// 线性探测:当当前位置不为空且不是要找的值时,继续查找
while (h[k] != null && h[k] != x)
{
k++; // 移动到下一个位置
if (k == N) // 如果到达数组末尾
{
k = 0; // 回到数组开头
}
}
// 返回找到的位置
// 如果x存在,返回x的位置
// 如果x不存在,返回应该插入的空位置
return k;
}
int main()
{
int n; // 操作次数
scanf("%d", &n);
// 初始化哈希表,将所有位置设为空
// 0x3f3f3f3f是一个很大的数,通常不会作为有效值
memset(h, 0x3f, sizeof(h));
while (n--)
{
char op[2]; // 操作类型
int x; // 操作的值
scanf("%s%d", op, &x);
// 找到x应该插入的位置,或者x当前所在的位置
int k = find(x);
if (*op == 'I') // 插入操作
{
h[k] = x; // 在找到的位置插入x
}
else // 查询操作
{
// 如果h[k]不是空值,说明x存在
if (h[k] != null)
{
puts("Yes");
}
else
{
puts("No");
}
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 100003; //数据规模,质数最优
vector<int> h[N];
int n; // n操作次数,idx数据存放在e中下标
void insert(int x) // 插入元素x
{
int k = (x%N + N)%N;
h[k].push_back(x);
}
bool find(int x) //查找元素x
{
int k = (x%N + N)%N;
for (int i=0; i<h[k].size(); i++)
if (h[k][i]==x) return true;
return false;
}
int main()
{
cin >> n;
char op[2];
int x;
while (n--) {
cin >> op >> x;
if (op[0]=='I') insert(x);
else {
if (find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
【运行结果】
5
I 1
I 2
I 3
Q 2
Yes
Q 5
No
浙公网安备 33010602011771号