AcWing算法基础课笔记——哈希表

本文介绍了哈希表的两种主要存储结构——开放寻址法和拉链法,以及如何通过离散化和哈希函数设计避免冲突。还涵盖了字符串前缀哈希的基本思路和一个示例模板题,展示了如何在实际问题中运用这些技术。
摘要由CSDN通过智能技术生成

来自AcWing算法基础课第二章笔记。
欢迎访问我的个人网站:
我的个人网站
将不定时更新一些笔记。

哈希表


两大块内容:

  1. 存储结构
    • 开放寻址法
    • 拉链法
  2. 字符串哈希方式

存储结构——开放寻址法与拉链法

哈希函数h(x)将 [ − 1 0 9 , 1 0 9 ] [-10^{9},10^{9}] [109,109]的数映射到 [ 0 , 1 0 5 ] [0, 10^{5}] [0,105]之间的数。

离散化是需要保序的,h(x)需要保序。

可以看作是一种特殊的哈希函数。

  1. 哈希函数怎么写? x m o d 1 0 5 x mod 10 ^{5} xmod105 【一般模的长度要取成质数,要离2的整次幂尽可能远 (冲突的概率小)】

  2. 冲突(将若干不同的数映射到了同一个数)如何解决?

    • 开放寻址法:【数组一般开两倍】

      • 添加:先h(x)找到一个坑位,如果已被占用就找下一个坑位,以此类推

      • 查找:先h(x)找到一个坑位,如果不是就继续往下找,直到找到一个空的坑位说明不存在

      • 删除:查找后,打标记表示删除

    • 拉链法:在每个槽上拉一个链。

算法题一般只会在哈希表中添加或查找数。如果要删除,开一个数组做标记表示是否删除。


模板题1 模拟散列表

题目

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

  1. I x,插入一个数 x x x
  2. Q x,询问数 x x x是否在集合中出现过;
    现在要进行 N N N次操作,对于每个询问操作输出对应的结果。

输入格式
第一行包含整数 N N N,表示操作数量。

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

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

每个结果占一行。

数据范围
1 ≤ N ≤ 1 0 5 1 \le N \le 10^{5} 1N105

− 1 0 9 ≤ x ≤ 1 0 9 -10^{9} \le x \le 10^{9} 109x109

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No
模板

首先找到质数:

int main() {
    //从200000开始往后找,找到最小的质数为止
	for(int i = 200000; ; i ++ ) {
        //flag表示是否是质数,一开始默认质数,如果后面能被整除,则认为不是质数
        bool flag = true;
        
        // 判断i能否被整除
        for(int j = 2; j * j <= i; j ++) {
            if(i % j == 0) {
                flag = false;
                break;
            }
        }
        
        if(flag) {
            cout << i << endl;
            break;
        }
    }
}
拉链法:
#include <iostream>
using namespace std;

const int N = 100003;

// e[i]表示第i个位置上的值
// ne[i]表示下一个的位置
// idx表示当前的位置
int h[N], e[N], ne[N], idx;


void insert(int x) {
    // 在第k个位置上插入x
    int k = (x % N + N) % N;  // 保证k属于[0, N]  c++中负数mod负数仍为负数
    
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx ++ ;
}

bool find(int x) {
    int k = (x % N + N) % N;
    //从h[k]的位置开始寻找,每次用ne找下一个值,如果i=-1则说明没找到
    for(int i = h[k]; i != -1; i = ne[i]) { 
        if(e[i] == x) 
            return true;
    }
    return false;
}


int main() {
    int n;
    scanf("%d", &n);
    
    memset(h, -1, sizeof h); //将所有槽清空,空指针用-1来表示
    
    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 <iostream>
using namespace std;
// null 表示未被占用(要在数据范围之外)
const int N = 200003, null = 0x3f3f3f3f; //N要开两倍

int h[N]// 查找x,如果存在,返回x的位置,如果不存在,返回x应该存储的位置
int find(int x) {
    int k = (x % N + N) % N;
    
    //从应该存储的位置k开始往后找,如果找到第N-1个还没找到,从0开始重新找
    while (h[k] != null && h[k] != x) {
        k ++;
        if (k == N) k = 0;
    }
    
    return k;
}


int main() {
    int n;
    scanf("%d", &n);
    
    //memset是按字节,h是int型的数据,有四个字节,每个字节设置为3f
    memset(h, 0x3f, sizeof h); 
    
    while(n --) {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        int k = find(x);
        if(*op == 'I') {
            h[k] = x;
        }
        else {
            if (h[k] != null) puts("Yes");
            else puts("No");
        }
    }
    
    return 0;
}


字符串前缀哈希

基本思路

对于字符串str = “ABCDEFHJH”

h[0] = 0

h[1] = "A"的hash值

h[2] = "AB"的hash值

h[3] = "ABC"的hash值。。。

字符串hash值的定义:

eg. 对字符串"ABCD",将ABCD看作为4位数字组成的p进制的数字(如1 2 3 4 ),然后将该p进制数字转为十进制,后modQ

这样,就可以将任何一个字符串映射到0~Q-1之间的数字

注意:

  1. 一般情况下不能将字母映射为0(从1开始即可)。
  2. 假定完全不存在冲突。

当:

P = 131 或 13331 P = 131 或 13331 P=13113331

Q = 2 64 Q = 2^{64} Q=264

在大部分情况下不会发生冲突。

这样,可以利用前缀哈希+字符串hash计算出任何一个字串hash值

在字符串中,L-R位的hash值为:
h [ R ] − h [ L − 1 ] × p R − L + 1 h[R] - h[L -1] \times p^{R - L + 1} h[R]h[L1]×pRL+1

用unsigned long long 存储h,就可以默认mod 2 64 2^{64} 264,不用mod Q

预处理时,
h [ i ] = h [ i − 1 ] × p + s t r [ i ] h[i] = h [i - 1] \times p + str[i] h[i]=h[i1]×p+str[i]

模板题2 字符串哈希

字符串哈希

#include <iostream>

using namespace std;

typedef unsigned long long ULL;

const int N = 100010, P = 131;

int n, m;
char str[N];
ULL h[N], p[N]; // h[i]用来表示0-i的字符串的hash值,p[i]用来表示P的i次方

// 求字符串l-r的hash值
ULL get(int l, int r) {
    return h[r] - h[l-1] * p[r - l + 1];
}

int main() {
	scanf("%d%d%s", &n, &m, str + 1);
    
    p[0] = 1;
    for(int i = 1; i <= n; i ++) {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];
    }
    
    while (m -- ) {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        
        if (get(l1, r1) == get(l2, r2)) puts("Yes");
        else puts("No");
    }
    
    return 0;
}
  • 40
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值