通关数据结构 day_10 -- 哈希表
哈希表
存储结构
- 开放寻址法
- 拉链法
作用
将一个 -109~109的数 通过 x mod 105 映射到 [0,105]
mod 的数一般要取成一个质数
如果发生冲突,(将两个不一样的数,映射成了同样的数)
例子
维护一个集合,支持如下几种操作:
I x,插入一个数 x;Q x,询问数 x 是否在集合中出现过;现在要进行 N 次操作,对于每个询问操作输出对应的结果。
输入格式
第一行包含整数 N,表示操作数量。
接下来 N 行,每行包含一个操作指令,操作指令为
I x,Q x中的一种。输出格式
对于每个询问指令
Q x,输出一个询问结果,如果 xx 在集合中出现过,则输出Yes,否则输出No。每个结果占一行。
数据范围
1≤N≤105
−109≤x≤109输入样例:
5 I 1 I 2 I 3 Q 2 Q 5输出样例:
Yes No
拉链法
开一个 一维数组 存储所有的 哈希值
比如我们第一次 将 h(11) 映射到了 3,我们就在 一维数组的 3 下面拉一条链,把 11 存下来,如果我们 又将 h(23)映射到了 3,我们就在 11 的后面存一个 23
#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;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
bool find(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()
{
cin.tie(0);
ios::sync_with_stdio(false);
int n;
cin >> n;
//清空槽
memset(h,-1,sizeof h);
while(n--)
{
char op[2];
int x;
cin >> op >> x;
if(op[0] == 'I')
{
insert(x);
}else{
if(find(x))
{
cout << "Yes" << endl;
}else{
cout << "No" << endl;
}
}
}
return 0;
}
开放寻址法
假如我们求出来 h(x) = k,如果 k 已经有人了,那么就去下一个坑位,直到我们找到一个没有数据的坑位,就把 x 放进去
那么我们在查找的时候同理,按照 哈希函数 计算出 x 的位置为 k,那么我们找到对应 k 位置的数,如果这个数 != x,那么我们就找 k+1 的数,直到找到 这个数 == x,或者是个空位就说明这个数 不存在
删除的时候,我们一般不会把 x 删掉,而是在数组中打一个标记,证明这个数不存在
#include<iostream>
#include<cstring>
using namespace std;
const int N = 200003,null = 0x3f3f3f3f;
int h[N];
int find(int x)
{
//如果 x 存在,返回的是 x 存在的位置
//如果 x 不存在,返回的是 x 应该存在的位置
int k = (x % N + N ) % N;
while(h[k] != null && h[k] != x)
{
k++;
if(k == N)
{
k = 0;
}
}
return k;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int n;
cin >> n;
//清空槽
memset(h,0x3f,sizeof h);
while(n--)
{
char op[2];
int x;
cin >> op >> x;
if(op[0] == 'I')
{
int k = find(x);
h[k] = x;
}else{
int k = find(x);
if(h[k] == null)
{
cout << "No" << endl;
}else{
cout << "Yes" << endl;
}
}
}
return 0;
}
字符串前缀哈希法
常用于比较两个字符串是否相等
比如我们有一个字符串 str = "ABCABCDEFXCACWING"
先预处理出来所有前缀的哈希
h[0] = 0
h[1] = "A" 的哈希值
h[2] = "AB" 的哈希值
h[3] = "ABC" 的哈希值
h[4] = "ABCA" 的哈希值
...
我们将每一个字符串 看作 p进制的数,每一位上的字母,就代表着 P 进制的每一位数字
以 h[3] 为例,他一共有三个字母那就看成有 三位,第一位上的数字是 A,第二位上的数字是 B,第三位上的数字是 C
把 A 当成 1,B 当成 2,C 当成 3,那么 "ABC" 就可以看成 p进制的 123 = 1 x p2+2 x p1+ 3 x p0,这个数可能很大所以最后我们对他 mod Q
那么最后我们就可以把这个数 映射到 0~Q-1 的位置了
一般不能映射成 0 ---> 因为会把不同的字符串映射成 同一个数
一般来说 p = 131 或 13331 Q = 264 冲突会比较少
| |
L R
我们已知 h[L-1] 和 h[R]的哈希值,如何求 [L,R] 的哈希值
左边是高位,右边是低位
在 h[R] 里 R 就是第 0 位,1 就是 R-1位 pR-1~p0
h[L-1] 里 L-1 就是第 0 位,1 就是 L-2位 pL-2~p0
我们第一步需要将 h[L-1] 这一段往左移,移到和我们的 h[R] 对齐为止
例如 123 和 12345,123左移后 == 12300 和 12345 对齐
h[L-1] x pR-L+1
第二步就是h[R] - h[L-1] x pR-L+1
这样就可以求出来 [L,R] 这一段的哈希值了
小技巧:我们用 unsighed long long 来存所有的 h
所以:h[i] = h[i-1] x p + str[i]
模板
(1) 拉链法
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++ ;
}
// 在哈希表中查询某个数是否存在
bool find(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;
}
(2) 开放寻址法
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
/*
字符串哈希
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
*/
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
练习
给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2请你判断 [l1,r1][l1,r1] 和 [l2,r2][l2,r2] 这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数 n 和 m,表示字符串长度和询问次数。
第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。
接下来 m 行,每行包含四个整数 l1,r1,l2,r2表示一次询问所涉及的两个区间。
注意,字符串的位置从 1 开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出
Yes,否则输出No。每个结果占一行。
数据范围
1≤n,m≤105
输入样例:
8 3 aabbaabb 1 3 5 7 1 3 6 8 1 2 1 2输出样例:
Yes No Yes
#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];
ULL get(int l,int r)
{
return h[r] - h[l-1]*p[r-l+1];
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
cin >> 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;
cin >> l1 >> r1 >> l2 >> r2;
if(get(l1,r1) == get(l2,r2))
{
cout << "Yes" << endl;
}else{
cout << "No" << endl;
}
}
return 0;
}

浙公网安备 33010602011771号