哈*希*表

模拟散列表 

一共介绍了四种三种方法

  • 开放寻址法
  • 拉链法
  • Trie字典树法

前两种为本题算法要求掌握的方法

Trie字典树法

将数字转换成二进制进行操作

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int son[32*N][2],idx,cnt[N];
void insert(int x){
    int p=0,u;
    for(int i=32;i>=0;i--){
        u=x>>i&1;
        if(!son[p][u])son[p][u]=++idx;
        p=son[p][u];
    }
    
}
int find(int x){
    int p=0,u;
    for(int i=32;i>=0;i--){
        u=x>>i&1;
        if(!son[p][u])return 0;
        p=son[p][u];
    }
    return 1;
}
int main(){
    int n;
    cin>>n;
    while(n--){
        char op[2];
        int x;
        x=x+1e9;
        cin>>op>>x;
        if(*op=='I') {insert(x);//cout<<"haha"<<endl;
        }
        else {
            if(find(x))puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

开放寻址法 (蹲坑法

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))。

作者:空_22
链接:https://www.acwing.com/solution/content/33699/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

#include<iostream>
#include<cstring>
using namespace std;
const int N=2000003,null=0x3f3f3f3f;
int h[N];
int find(int x){
    int k=(x%N+N)%N;
    while(h[k]!=null&&h[k]!=x){//如果没有坑位了,要一直寻找下一个坑位所以不能用if
        k++;
        if(k==N)k=0;
    }
    return k;
}
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 = 1e5 + 3;  // 取大于1e5的第一个质数,取质数冲突的概率最小 可以百度

//* 开一个槽 h
int h[N], e[N], ne[N], idx;  //邻接表

void insert(int x) {
    // c++中如果是负数 那他取模也是负的 所以 加N 再 %N 就一定是一个正数
    int k = (x % N + N) % N;
    e[idx] = x;
    ne[idx] = h[k];//头节点时取模得到的数,存入的数都是跟在头节点后面的小尾巴
    h[k] = idx++;
}

bool find(int x) {
    //用上面同样的 Hash函数 讲x映射到 从 0-1e5 之间的数
    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 n;

int main() {
    cin >> n;

    memset(h, -1, sizeof h);  //将槽先清空 空指针一般用 -1 来表示

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


#include<bits/stdc++.h>
#include<cstring>
using namespace std;
const int N=1e5+10,M=1e5;
int h[N],e[N],ne[N],idx=1;
void insert(int x){
    int k=x%M;
    e[idx]=x;
    ne[idx]=h[k];
    h[k]=idx++;
}
int find(int x){
    int k=x%M;
    for(int i=h[k];i!=0;i=ne[i]){
        if(e[i]==x)
        return 1;
    }
    return 0;
}
int main(){
    int n;
    scanf("%d", &n);
    while(n--){
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        x+=1e9;
        if(*op=='I')insert(x);
        else {
            if(find(x))puts("Yes");
            else puts("No");
        }
        
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值