哈希表+字符串哈希

135 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情image.png

存储结构

开放寻址法image.png

  1. 只开一个一维数组,没有开链表,但是数组一般来说要开到题目范围的2~3倍,冲突范围比较低
  2. 怎么弄的呢?
    1. 跟上厕所差不多,先看第k个坑位,如果有人就下一个,一直到找到一个没有人的坑位,然后占坑
  3. 添加
  4. 查找
  5. 删除:一般不会真的删掉,而是会打一个标记

拉链法

image.png

  1. 先开一个一维数组
  2. 在每个槽上拉一条链,存那些哈希值一样的数
  3. 一般只有添加跟查找操作,不会出现删除
  4. 就算是要删除的话,不是真正的将这个点删掉,而是会用标记,开一个bool变量,将这个点的状态存起来

常用的字符串哈希方式

字符串前缀哈希法 例子: image.png

  1. 先将字符串的每个前缀的哈希值先求出来
  2. 如何定义前缀的哈希值
    1. 先将字符串看成是一个p进制的数
    2. 将p进制的数转化成十进制的数
    3. 最后将这个数mod上一个Q,就可以映射到0~Q - 1 上面了
  3. 注意:
    1. 一般情况下,不能映射成0
    2. 字符串哈希,完全不考虑冲突的情况
    3. 一般P跟Q的取值
      1. p = 131 或1331
      2. Q = 2 ^ 64
  4. 可以利用前缀哈希,用一个公式来计算出来任意一个字串的哈希值
    1. 例子:假设想求一下L~R这一个字串的哈希值,怎么求image.png
      1. 已知:hR和h[L - 1](1~L - 1的哈希值)
      2. 我们需要将h[R]跟h[L - 1]对齐,也就是h[L - 1]要左移(R - L + 1)位,然后做差
    2. 因为后面要对哈希值进行mod Q(2^64),那么我们可以用一个unsigned long long(刚好64位),直接用这个来存哈希值就好了,溢出就相当于取模了
    3. 预处理前缀哈希值:h[i] = h[i - 1] + str[i]

问题:

  1. 什么情况下会用到哈希表?

作用:将一个比较多的数据,通过哈希函数映射到一个比较小的数据范围

  1. 哈希函数怎么写?
    1. 如果要将一个很大范围的数,映射到(0, 10^5)的话,我们可以让哈希函数h(x) = x mod 10^5,一般情况下,根据要映射成什么范围,直接取模即可image.png
      1. 一般情况下直接取模就行了
      2. 但是一般要将模的这个数取成质数,因为这样取的话,冲突的概率是最小的
    2. 但是往往会出现若干个不同的数映射到了同一个数,那怎么处理冲突呢?
      1. 根据处理冲突的方式分成
        1. 拉链法
        2. 开放寻址法

题目

www.acwing.com/problem/con… image.png

代码

#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]; // p:用来存P的多少次方的

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

什么时候用字符串哈希?

当我们要快速判断两个字符串是否相等的时候,就会用到这个方法,时间复杂度O(1)