一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情。
存储结构
开放寻址法
- 只开一个一维数组,没有开链表,但是数组一般来说要开到题目范围的2~3倍,冲突范围比较低
- 怎么弄的呢?
- 跟上厕所差不多,先看第k个坑位,如果有人就下一个,一直到找到一个没有人的坑位,然后占坑
- 添加
- 查找
- 删除:一般不会真的删掉,而是会打一个标记
拉链法
- 先开一个一维数组
- 在每个槽上拉一条链,存那些哈希值一样的数
- 一般只有添加跟查找操作,不会出现删除
- 就算是要删除的话,不是真正的将这个点删掉,而是会用标记,开一个bool变量,将这个点的状态存起来
常用的字符串哈希方式
字符串前缀哈希法
例子:
- 先将字符串的每个前缀的哈希值先求出来
- 如何定义
前缀的哈希值?- 先将字符串看成是一个p进制的数
- 将p进制的数转化成十进制的数
- 最后将这个数mod上一个Q,就可以映射到0~Q - 1 上面了
- 注意:
- 一般情况下,不能映射成0
- 字符串哈希,完全不考虑冲突的情况
- 一般P跟Q的取值
p = 131 或1331Q = 2 ^ 64
- 可以利用前缀哈希,用一个公式来计算出来任意一个字串的哈希值
- 例子:假设想求一下L~R这一个字串的哈希值,怎么求
- 已知:hR和h[L - 1](1~L - 1的哈希值)
- 我们需要将h[R]跟h[L - 1]对齐,也就是h[L - 1]要左移(R - L + 1)位,然后做差
- 因为后面要对哈希值进行mod Q(2^64),那么我们可以用一个unsigned long long(刚好64位),直接用这个来存哈希值就好了,溢出就相当于取模了
- 预处理前缀哈希值:h[i] = h[i - 1] + str[i]
- 例子:假设想求一下L~R这一个字串的哈希值,怎么求
问题:
- 什么情况下会用到哈希表?
作用:将一个比较多的数据,通过哈希函数映射到一个比较小的数据范围
- 哈希函数怎么写?
- 如果要将一个很大范围的数,映射到(0, 10^5)的话,我们可以让哈希函数h(x) = x mod 10^5,一般情况下,根据要映射成什么范围,直接取模即可
- 一般情况下直接取模就行了
- 但是一般要将模的这个数取成质数,因为这样取的话,冲突的概率是最小的
- 但是往往会出现若干个不同的数映射到了同一个数,那怎么处理冲突呢?
- 根据处理冲突的方式分成
- 拉链法
- 开放寻址法
- 根据处理冲突的方式分成
- 如果要将一个很大范围的数,映射到(0, 10^5)的话,我们可以让哈希函数h(x) = x mod 10^5,一般情况下,根据要映射成什么范围,直接取模即可
题目
代码
#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)