数据结构——哈希表

一、哈希表的存储结构

哈希表的作用:将一堆复杂庞大的数据映射到较小的内存空间中(一般是将1e9的数据映射到N = 1e5或1e6的数组里),手写哈希表一般支持插入、查找。如果要用哈希表实现删除操作,一般会开一个bool标记,对要删除的点要进行特别标记。哈希表的时间复杂度可以认为是O(1)。

哈希表的创建主要考虑两个问题:1、哈希函数。2、处理映射冲突(拉链法、开放寻址法)

(1)拉链法

开一个一维数组h(N)来存储所有的哈希值,在h(N)数组的每一个槽上拉以一个链表存储该位置冲突的值

处理冲突的方式:当h(11) = 3,h(21)= 3,即11与21都映射到3上时,就产生了冲突,此时可以在3上拉一条链(即单链表,h(3)为该链表的头节点head)同一条链上元素的插入方式与单链表的头插相同,把head改成h(k)即可。

哈希表中映射后的数据范围N的选择一般遵循两个原则:1、为质数。2、离2的整次幂尽可能远

以下代码可以用来确定数组大小N

int flag;
    for(int i = 100000;;i ++ ){
        for(int j = 2;j * j <= i;j ++ ){
            if(i % j == 0){
                flag = false;
                break;
            }
        }
        if(flag){
                cout << i;
                break;
            }
        flag = true;
    }

拉链法代码

#include<iostream>
#include<cstring>

using namespace std;

const int N = 100003;

int h[N],e[N],ne[N],idx;

void insert(int x){
    int k = (x % N + N) % N; //k为哈希值,+N再%N的操作确保k为正数
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx;
    idx ++ ;
    //h[k]相当于单链表里的head
}

bool query(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(){
    int n,x;
    cin >> n;
    char op[2];
    memset(h,-1,sizeof h);
    while(n -- ){
        cin >> op;
        if(*op == 'I'){
            cin >> x;
            insert(x);
        }
        else{
            cin >> x;
            if(query(x)) puts("Yes");
            else puts("No");
        }
    }

    return 0;
}

(2)开放寻址法

只开一个一维数组,不开链表

一维数组的长度一般要开到题目数据的2~3倍

处理冲突的方法:h(k)= x时,看看k处有没有元素,如果没有就放到这里,如果有就看k+1的位置,以此类推往后遍历。

null为约定的标志,h数组初始化为null,null在题目数据范围-1e9~1e9之外,当该位为null时表明次位为空,memset h数据时第二个参数传入0x3f因为memset修改的是一个字节,而int为四字节,所以这样就可以把h数组的每一位都初始化为0x3f3f3f3f了

#include<iostream>
#include<cstring>

using namespace std;

const int N = 200003,null = 0x3f3f3f3f;

int h[N];

int query(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(){
    int n;
    char op[2];
    cin >> n;
    memset(h,0x3f,sizeof h);
    while(n -- ){
        int x;
        cin >> op;
        if(*op == 'I'){
            cin >> x;
            h[query(x)] = x;
        }
        else{
            cin >> x;
            if(h[query(x)] == null) puts("No");
            else puts("Yes");
        }
    }
    return 0;
}

(3)STL unordered_map

#include<iostream>
#include<unordered_map>

using namespace std;

const int N = 100003;

unordered_map<int,int> h;

int main(){

    int n;
    char op[2];
    cin >> n;
    while(n -- ){
        int x;
        int y;
        cin >> op;
        if(*op == 'I'){
            cin >> x;
            y = (x % N + N) % N;
            h.insert({x,y});
        }
        else{
            cin >> x;
            if(h.find(x) == h.end()) puts("No");
            else puts("Yes");
        }
    }
    return 0;
}

二、字符串哈希方式——字符串前缀哈希法

字符串哈希就是把不同的字符串映射成不同的整数

关键是把字符串映射成一个p进制数字。对于一个长度为n的字符串s,这样定义哈希函数:h(s) = \sum_{i=1}^{l} s[i] \times p^{l-i} \pmod{M}。例如字符串abc,其哈希函数值为ap^2+bp^1+c,即97 x 131^2 + 98 x 131^1 +99

处理哈希冲突的方式:P取131或13331、M取2^{64}(把哈希函数值h定义为unsign long long,超过自动溢出,等价于取模),保证P与M互质,就可以认定在99.99%情况下不会产生哈希冲突,

求一个字符串的哈希值相当于求前缀和,求一个字符串字串的哈希值相当于求区间和

#include<iostream>

using namespace std;

const int N = 100010,P = 131;

typedef unsigned long long ULL;

int n,m;
char str[N];
//p[i]=P^i,h[1] = s[i - 1]的hash值
ULL h[N],p[N];

//预处理hash函数的前缀和
void init(){
    p[0] = 1,h[0] = 0;
    for(int i = 1;i <= n;i ++ ){
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];
    }
}

//计算s[l~r]的hash值
ULL get(int l1,int r1){
    return h[r1] - h[l1 - 1] * p[r1 - l1 + 1];
}

int main(){
    int l1,r1,l2,r2;
    cin >> n >> m >> str + 1;
    init();
    while(m -- ){
        cin >> l1 >> r1 >> l2 >> r2;
        if(get(l1,r1) == get(l2,r2)) puts("Yes");
        else puts("No");
    }
    return 0;
}

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还好我是c语言大佬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值