题目:模拟散列表
问题描述:
维护一个集合,支持以下操作:
I x
,插入一个数x
;Q x
,询问数x
是否在集合中出现过。
进行 N
次操作,对于每个询问操作输出对应的结果。
输入格式:
- 第一行包含整数
N
,表示操作数量。 - 接下来
N
行,每行包含一个操作指令,操作指令为I x
或Q x
中的一种。
输出格式:
- 对于每个询问指令
Q x
,输出Yes
或No
,表示x
是否在集合中出现过。
数据范围:
- 1≤N≤105
- −109≤x≤109
输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 3;
int h[N], e[N], ne[N], idx, n, m;
char str;
void insert(int x)
{
int k = ((x % N) + N) % N; //先对x取模映射到h数组里面
e[idx] = x; //先将x存进e数组
ne[idx] = h[k]; //使刚插进来的数指向前面一个结点
h[k] = idx++; //使指针指向当前结点(h[k]可以理解成尾指针)
}
bool query(int x)
{
int k = ((x % N) + N) % N;
int c = h[k];
while (c != -1) //如果不为-1就表示有数字插入了该节点,遍历这个拉链
{
if (e[c] == x)
{
return true;
}
c = ne[c];
}
return false;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++)
{
cin >> str;
if (str == 'I')
{
cin >> m;
insert(m);
}
if(str=='Q')
{
cin >> m;
if (query(m))
{
cout << "Yes" << endl;
}
else
cout << "No" << endl;
}
}
return 0;
}
代码思路总结
1. 数据结构选择
- 使用拉链法来处理哈希冲突。拉链法是一种常见的哈希表实现方式,通过在哈希表的每个槽中维护一个链表来解决冲突。
- 定义了三个数组:
h[N]
:哈希表数组,h[k]
存储哈希值为k
的链表的头节点索引。e[N]
:存储实际的值(即插入的数x
)。ne[N]
:存储链表中下一个节点的索引。
2. 哈希函数
- 哈希函数使用取模运算:
k = ((x % N) + N) % N
。- 这个哈希函数将任意整数
x
映射到[0, N-1]
的范围内。 - 通过
(x % N) + N
确保结果为非负数,然后再取模N
,确保结果在合法范围内。
- 这个哈希函数将任意整数
3. 插入操作 (insert
)
- 插入操作的步骤如下:
- 计算哈希值
k
,确定x
应该插入到哪个槽。 - 将
x
存入e[idx]
中。 - 将
ne[idx]
指向当前槽的头节点h[k]
,表示新插入的节点指向原来的头节点。 - 更新
h[k]
为当前节点的索引idx
,表示新插入的节点成为新的头节点。 - 增加
idx
,为下一个插入操作做准备。
- 计算哈希值
4. 查询操作 (query
)
- 查询操作的步骤如下:
- 计算哈希值
k
,确定x
可能位于哪个槽。 - 从
h[k]
开始遍历链表,检查链表中是否存在值等于x
的节点。 - 如果找到相等的值,返回
true
;否则遍历完链表后返回false
。
- 计算哈希值
5. 主函数 (main
)
- 主函数的逻辑如下:
- 读取操作数量
n
。 - 初始化哈希表
h
,将所有槽的头节点索引设置为-1
,表示初始时链表为空。 - 循环处理每个操作:
- 如果是插入操作
I x
,调用insert(x)
将x
插入哈希表。 - 如果是查询操作
Q x
,调用query(x)
检查x
是否在哈希表中,并输出Yes
或No
。
- 如果是插入操作
- 读取操作数量
6. 复杂度分析
- 时间复杂度:
- 插入和查询操作的平均时间复杂度为
O(1)
,因为哈希表的每个槽的链表长度在理想情况下是常数级别。 - 最坏情况下(所有元素都映射到同一个槽),时间复杂度为
O(n)
。
- 插入和查询操作的平均时间复杂度为
- 空间复杂度:
- 使用了三个数组
h[N]
、e[N]
和ne[N]
,空间复杂度为O(N)
。
- 使用了三个数组
算法刷题记录