1. 核心总结
应对问题:
当我们需要保存少量数据,但是数据范围较大的时候,这时候假如数据的范围为1亿,但是数据量却只有几百个,这样子我们如何去保存这些数据呢?
分析过程:
我们要如何去保存这些数据的同时也便于我们快速查找这些数据呢?
第一种方案就是开辟对应空间(1亿大小),然后就可以便于我们快速查找了,但这个样子显然是不方便的,应为我们为了几百个数据开了上亿大小的空间,这造成了极大的资源的浪费。
第二种方案就是我们今天要介绍的哈希函数了,我们想方法将原来的元素映射到一个较小的数组当中去存放这些数据。
通用处理:
1. 开一个大小为 M 的数组,M通常为质数且为原来元素个数总和的几倍(这是开放寻址法,防止之后元素碰撞然后就向后放)(最少是两三倍以上的,如果可以最好可以开十倍二十倍最好,防止爆超时)
2. 首先将某个数据如(x, y)坐标等首先处理为一个唯一的哈希值,例如 x * 1000001 + y(也有可能直接映射若只有一个维度),然后再去映射到哈希表中
const int M = 997;
int h[M]; // 初始化 -1 表示没有存数字
int get_key(int x, int y){
int value = get_hash(x, y); // 得到(x, y)的哈希值
int key = (value % M + M) % M; // 得到映射到哈希表中的位置
while(h[key] != -1 && h[key] != value){
// 对应哈希表不为空 或者 哈希值不对应 继续搜寻
key ++;
if(key == M) key = 0; // 重新搜索
}
return key; // 返回哈希表当中存储此元素的位置
}
题目关键分析:
C++补充:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
int main(){
unordered_map <int, int> hash;
hash[100] = 10;
cout << hash[100] << endl; // 20
cout << hash.count(100) << endl; // 1
}
2. 哈希表习题
题目链接: 字符串哈希
题目分析:
想方法将字符串映射成为一个哈希值,利用 P 进制去保存,通常取 P = 133 或 P = 13331 ,然后对 2^64 取模,通常可以理解为不会发生冲突。
映射公式 (X1 ^ Pn−1 + X2 ^ Pn−2 + ⋯ + Xn−1 ^ P1 + Xn ^ P0)modQ
代码解析:
/*
前缀和公式 h[i+1]=h[i]×P+s[i] i∈[0,n−1]
区间和公式 h[l,r]=h[r]−h[l−1]×P^(r−l+1)
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5 + 10, P = 133; // P通常取 133 或 13331
ULL h[N], p[N]; // h[]为前缀和数组, p[]为p次方大小, 默认对 2^64 取模
char s[N];
ULL solve(int l, int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
int n, m;
cin >> n >> m;
h[0] = 0, p[0] = 1;
for(int i = 1; i <= n; i ++ ){
cin >> s[i];
h[i] = h[i - 1] * P + s[i]; // 哈希值映射
p[i] = p[i - 1] * P;
}
while(m -- ){
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if(solve(l1, r1) == solve(l2, r2)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
题目链接: KMP字符串
题目分析:
记录原字符串的哈希值,再逐步比对相同长度子串的哈希值进行比较。
第二种方法见KMP算法
代码解析:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ULL;
const int P = 133, N = 1e6 + 10;
ULL h[N], sum, k[N];
int main(){
string p, s;
int n, m;
cin >> n >> p;
cin >> m >> s;
k[0] = 1;
for(int i = 0; i < n; i ++ ) sum = sum * P + p[i]; // 原子字符串哈希值
for(int i = 1; i <= m; i ++ ){
// 父字符串哈希值前缀和
h[i] = h[i - 1] * P + s[i - 1];
k[i] = k[i - 1] * P;
}
for(int i = n; i <= m; i ++ ){
// 逐个比对相同长度子串哈希值是否相同
ULL t = h[i] - h[i - n] * k[n];
if(t == sum) cout << i - n << ' ';
}
return 0;
}
题目链接: 模拟散列表
题目分析: 将 x 映射到哈希数组里面即可,通常会开一个多倍空间(最少最好也要2 3倍以上)的质数数组.
代码解析:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int M = 199997, inf = 0x3f3f3f3f;
int h[M];
int get_key(int x){
int key = (x % M + M) % M;
while(h[key] != inf && h[key] != x)
if(++ key == M) key = 0;
return key;
}
int main(){
memset(h, 0x3f, sizeof(h));
int n;
cin >> n;
while(n -- ){
int x;
char op;
cin >> op >> x;
int key = get_key(x);
if(op == 'I') h[key] = x;
else{
if(h[key] != x) cout << "No" << endl;
else cout << "Yes" << endl;
}
}
return 0;
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
unordered_map<int, bool> um;
int main(){
int n;
cin >> n;
while(n -- ){
int x;
char op;
cin >> op >> x;
if(op == 'I') um[x] = true;
else{
if(um[x] == false) cout << "No" << endl;
else cout << "Yes" << endl;
}
}
return 0;
}
题目链接: A-B数对
题目分析: 将 x 映射到哈希数组里面,同时利用cnt数组记录一下这个值出现的次数即可.
代码解析:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10, M = 999997;
int a[N];
int h[M], cnt[M]; // h[] 哈希表, cnt[]数字的数量
int get_key(int x){
// 得到哈希表当中的位置
int k = (x % M + M) % M;
while(h[k] != -1 && h[k] != x){
if(++ k == M) k = 0;
}
return k;
}
int main(){
memset(h, -1, sizeof(h));
int n, c;
scanf("%d%d", &n, &c);
for(int i = 0; i < n; i ++ ){
scanf("%d", &a[i]);
int key = get_key(a[i]);
h[key] = a[i], cnt[key] ++;
}
LL ans = 0;
for(int i = 0; i < n; i ++ ){
int key = get_key(a[i] + c);
ans += cnt[key];
}
cout << ans;
return 0;
}
题目链接: 扫雷
题目分析:
将 炸雷(x, y) 坐标首先处理为唯一的哈希值后再映射到哈希数组当中去进行保存,同时保存其对应炸雷号码,访问状态信息。
逐个扫面每个排雷火箭附件的 r 空间,判断其位置是否有炸雷,若存在炸雷且未被访问且可以炸到则进一步的深度探索这个炸雷。
最后对每个炸雷的状态进行一次访问,判断其是否被访问过即可。
代码解析:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5e4 + 10, M = 199997; // 某些测试网站爆超时,可换成999997
typedef long long LL;
struct Circle{
int x, y, r;
}cir[N];
LL h[M], id[M], st[M]; // h[] 哈希表, id[]记录对应位置炸雷标号,st[]记录是否被访问过此炸雷
int sqr(int x){
return x * x;
}
LL get_value(int x, int y){
// x * 1000000001 + y 来映射其对应的哈希值
return 1000000001ll * x + y;
}
int get_key(int x, int y){
LL val = get_value(x, y);
int k = (val % M + M) % M;
while(h[k] != - 1 && h[k] != val){
if(++ k == M) k = 0;
}
return k;
}
void dfs(int x, int y, int r){
int key = get_key(x, y);
st[key] = true;
for(int i = x - r; i <= x + r; i ++ )
for(int j = y - r; j <= y + r; j ++ ){
int key = get_key(i, j);
if(id[key] && st[key] == false && sqr(i - x) + sqr(j - y) <= sqr(r))
dfs(i, j, cir[id[key]].r);
}
}
int main(){
memset(h, -1, sizeof(h));
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++ ){
// 输入信息, 若多个同个位置炸雷储存最大半径炸雷即可
int x, y, r;
scanf("%d%d%d", &x, &y, &r);
cir[i] = {
x, y, r};
int key = get_key(x, y);
h[key] = get_value(x, y);
if( !id[key] || cir[id[key]].r < r ) id[key] = i;
}
while(m -- ){
int x, y, r;
scanf("%d%d%d", &x, &y, &r);
//遍历排雷火箭周围所有位置的炸雷
for(int i = x - r; i <= x + r; i ++ )
for(int j = y - r; j <= y + r; j ++ ){
int key = get_key(i, j);
if(id[key] && st[key] == false && sqr(i - x) + sqr(j - y) <= sqr(r))
dfs(i, j, cir[id[key]].r);
}
}
int ans = 0;
for(int i = 1; i <= n; i ++ ){
int key = get_key(cir[i].x, cir[i].y);
if(st[key]) ans ++;
}
cout << ans;
return 0;
}