题解: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

【核心思想】

  1. 问题分析:需要维护一个支持插入和查询操作的整数集合。若用数组或链表直接存储,查询需要 \(O(n)\) 遍历。关键观察是:通过哈希函数将大值域的整数映射到小范围的数组下标,实现 \(O(1)\) 的期望时间复杂度操作。

  2. 算法选择

    • 拉链法(Separate Chaining):每个哈希桶维护一个链表,冲突元素链式存储
    • 开放寻址法(Open Addressing):冲突时线性探测下一个空位,所有元素存储在数组中
    • STL 向量法:利用 vector 简化拉链法的链表实现
  3. 关键步骤(拉链法)

    • 哈希函数\(k = ((x \bmod N) + N) \bmod N\),处理负数确保非负
    • 插入:计算哈希值 \(k\),头插法将新节点插入 \(h[k]\) 链表头部
    • 查询:计算哈希值 \(k\),遍历 \(h[k]\) 链表查找 \(x\)
    • 数组模拟链表e[idx] 存值,ne[idx] 存下一个节点下标,h[k] 存链表头指针
  4. 关键步骤(开放寻址法)

    • 哈希函数:同拉链法
    • 查找 find(x):从 \(k\) 开始线性探测,直到遇到空位或找到 \(x\)
    • 插入find(x) 返回的位置即为应插入位置
    • 查询:若 find(x) 返回的位置值不为空,则存在
  5. 时间/空间复杂度

    • 时间复杂度:期望 \(O(1)\),最坏 \(O(n)\)(大量冲突时)
    • 空间复杂度:\(O(N)\)\(N\) 为哈希表大小
  6. 散列表的核心思想

    • 哈希映射:通过哈希函数将任意整数映射到固定范围的数组下标,实现值域压缩,使 \(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
posted @ 2026-07-02 22:45  团爸讲算法  阅读(4)  评论(0)    收藏  举报