AcWing 840. 模拟散列表

907 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

AcWing 840. 模拟散列表

维护一个集合,支持如下几种操作:

  1. I x,插入一个数 x;
  2. Q x,询问数 x 是否在集合中出现过;

现在要进行 N 次操作,对于每个询问操作输出对应的结果。

输入格式

第一行包含整数 N,表示操作数量。

接下来 N 行,每行包含一个操作指令,操作指令为 I xQ x 中的一种。

输出格式

对于每个询问指令 Q x,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1 ≤ N ≤ 10^5
−10^9 ≤ x ≤ 109

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No

思路

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

拉链法图解 image.png

image.png image.png image.png image.png

开放寻址法图解
使用哈希计算映射到数组中的位置,如果当前位置有数字,被占领,就向后移动,直至有空缺位置,如果移动到最后一个位置仍然没有空缺,就从头开始重新遍历寻找, image.png

一般哈希模板

拉链法

int h[N], e[N], ne[N], idx;
// h[]散列表保存头节点的下标
// e[]保存值,ne[]保存前一个值的下标,idx是链表的索引
// 向哈希表中插入一个数
void insert(int x){
    int k = (x % N + N) % N;
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx ++ ;
}

// 在哈希表中查询某个数是否存在
bool find(int x){
    int k = (x % N + N) % N;
    for (int i = h[k]; i != -1; i = ne[i]) //循环遍历从头节点向下遍历,遍历ne数组的存储的指向地址
        if (e[i] == x)
            return true;

    return false;
}

开放寻址法

int h[N];

// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x){
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x){
        t ++ ;
        if (t == N) t = 0;
    }
    return t;
}

ac代码

开放寻址法代码

#include <cstring>
#include <iostream>

using namespace std;

const int N = 200003, null = 0x3f3f3f3f;

int h[N];

int find(int x){
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x){
        t ++ ;
        if (t == N) t = 0;
    }
    return t;
}

int main(){
    memset(h, 0x3f, sizeof h);

    int n;
    scanf("%d", &n);

    while (n -- ){
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        if (*op == 'I') h[find(x)] = x;
        else{
            if (h[find(x)] == null) puts("No");
            else puts("Yes");
        }
    }
    return 0;
}

拉链法代码

#include <cstring>
#include <iostream>

using namespace std;

const int N = 100003; //是大于10000的第一个素数

int h[N], e[N], ne[N], idx;
//h数组是哈希函数的一维数组,e数组是链表中存的值,ne数组是指针存的指向地址,idx是当前指针
void insert(int x){
    int k = (x % N + N) % N; //计算哈希映射,包含对负数的处理
    e[idx] = x; //对于链表的处理
    ne[idx] = h[k]; //表示指向数组中的哪一个位置
    h[k] = idx ++ ; //储存哈希后映射到k的数 的链表的头结点在e数组的下标
}

bool find(int x){
    int k = (x % N + N) % N;
    for (int i = h[k]; i != -1; i = ne[i])//如果头节点不为-1,表示有节点,然后遍历
        if (e[i] == x)
            return true;

    return false;
}

int main(){
    int n;
    scanf("%d", &n);

    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;
}