模拟散列表( 哈希表 )

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

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

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

输入格式

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

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

输出格式

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

每个结果占一行。

数据范围

1≤N≤105
−109≤x≤109

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No

 拉链法

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 3;
int ne[N], e[N], h[N];
int n, 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]){
        if(e[i] == x)
            return true;
    }
    return  false;
}
int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    while(n -- ){
        string op;
        int x;
        cin >> op >> x;
        if(op == "I"){
            insert(x);
        }
        else{
            if(find(x))
                puts("Yes");
            else
                puts("No");
        }
    }
    return 0;
}

 

 开放寻址法

 原文链接:https://www.acwing.com/solution/content/33699/

思路
开放寻址法采用hash函数找到在hash数组中对应的位置,如果该位置上有值,并且这个值不是寻址的值,则出现冲突碰撞,需要解决冲突方案,该算法采用简单的向右继续寻址来解决问题。

由于思想简单,将不深入探讨,在次,我们探讨一下文中常常让人摸不着头脑的参数

让人费解的参数
1. const int N = 200003; 
    1.1开放寻址操作过程中会出现冲突的情况,一般会开成两倍的空间,减少数据的冲突

    1.2如果使用%来计算索引, 把哈希表的长度设计为素数(质数)可以大大减小哈希冲突
    比如
    10%8 = 2      10%7 = 3
    20%8 = 4      20%7 = 6
    30%8 = 6      30%7 = 2
    40%8 = 0      40%7 = 5
    50%8 = 2      50%7 = 1
    60%8 = 4      60%7 = 4
    70%8 = 6      70%7 = 0

这就是为什么要找第一个比空间大的质数
2.const int null = 0x3f3f3f3f 和  memset(h, 0x3f, sizeof h)之间的关系;

    首先,必须要清楚memset函数到底是如何工作的
    先考虑一个问题,为什么memset初始化比循环更快?
    答案:memset更快,为什么?因为memset是直接对内存进行操作。memset是按字节(byte)进行复制的

    void * memset(void *_Dst,int _Val,size_t _Size);
    这是memset的函数声明
    第一个参数为一个指针,即要进行初始化的首地址
    第二个参数是初始化值,注意,并不是直接把这个值赋给一个数组单元(对int来说不是这样)
    第三个参数是要初始化首地址后多少个字节
    看到第二个参数和第三个参数,是不是又感觉了
    h是int类型,其为个字节, 第二个参数0x3f八位为一个字节,所以0x3f * 4(从高到低复制4份) = 0x3f3f3f3f

    这也说明了为什么在memset中不设置除了-1, 0以外常见的值
    比如1, 字节表示为00000001,memset(h, 1, 4)则表示为0x01010101
3. 为什么要取0x3f3f3f,为什么不直接定义无穷大INF = 0x7fffffff,即32个1来初始化呢?

3.1 首先,0x3f3f3f的体验感很好,0x3f3f3f3f的十进制是1061109567,也就是10^9级别的
    (和0x7fffffff一个数量级),而一般场合下的数据都是小于10^9的,所以它可以作为无穷大
    使用而不致出现数据大于无穷大的情形。
    比如0x3f3f3f3f+0x3f3f3f3f=2122219134,这非常大但却没有超过32-bit,int的表示范围,
    所以0x3f3f3f3f还满足了我们“无穷大加无穷大还是无穷大”的需求。
    但是INF不同,一旦加上某个值,很容易上溢,数值有可能转成负数,有兴趣的小伙伴可以去试一试。

3.2 0x3f3f3f3f还能给我们带来一个意想不到的额外好处:如果我们想要将某个数组清零,
    我们通常会使用memset(a,0,sizeof(a))这样的代码来实现(方便而高效),但是当我们想
    将某个数组全部赋值为无穷大时(例如解决图论问题时邻接矩阵的初始化),就不能使用
    memset函数而得自己写循环了(写这些不重要的代码真的很痛苦),我们知道这是因为memset
    是按字节操作的,它能够对数组清零是因为0的每个字节都是0,
    现在如果我们将无穷大设为0x3f3f3f3f,那么奇迹就发生了,0x3f3f3f3f的每个字节都是0x3f!所以
    要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 3;
const int null = 0x3f3f3f3f;
int n;
int h[N];
int find(int x){
    int k = (x % N + N) % N;
    while(h[k] != null && h[k] != x){
        k ++ ;
    }
    if(k == N){
        k = 0;
    }
    return k;
}
int main(){
    cin >> n;
    memset(h, 0x3f, sizeof h);
    while(n -- ){
        string op;
        int x;
        cin >> op >> x;
        if(op == "I"){
            h[find(x)] = x;
        }
        else{
            if(h[find(x)] == null)
                puts("No");
            else
                puts("Yes");
        }
    }
    return 0;
}

题解里看到别的解题方法,贴一下orz

原文链接:https://www.acwing.com/solution/content/8546/
 

线性试探法

平方试探法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值