本文是对Acwing算法基础课hash表的总结
总览
用途
hash表用途是将 [ − 1 0 9 , 1 0 9 ] → [ 0 , 1 0 5 ] [-10^9, 10^9] \to [0, 10^5] [−109,109]→[0,105], 缩小范围, 一般需要将定义域上的值取mod, 取mod的这个数最好是质数, 这样可以最大程度上避免冲突
冲突及解决方案
因为有冲突的存在, 有以下两种解决办法, 也就是hash的存储方式.
hash表的存储结构分为开放寻址法和拉链法
先寻找到最近的质数, 比如寻找最接近100000
的质因数
寻找质数
#include <iostream>
using namespace std;
const int N = 1000010;
int main(){
for (int i = 100000;; i ++ ){
bool flag = true;
for (int j = 2; j * j <= i; j ++ )
if (i % j == 0){
flag = false;
break;
}
if (flag) {
cout << i << endl;
break;
}
}
return 0;
}
然后采用拉链法, 注意, 拉链法原理是先找到槽位k, 然后再往槽位的链表上添加数或者查找数.
AcWing 840. 模拟散列表
code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003;
int h[N], e[N], ne[N], idx;
int n;
void insert(int x){
int k = (x % N + N) % N; // 先找到hash后的槽位
e[idx] = x, ne[idx] = h[k], h[k] = idx ++;
}
bool check(int x){
int k = (x % N + N) % N; // 同样先找槽位
for (int i = h[k]; ~i; i = ne[i]){
int j = e[i];
if (j == x) return true;
}
return false;
}
int main(){
cin >> n;
memset(h, -1, sizeof h);
char op[2];
int x;
while (n -- ){
scanf("%s%d", op, &x);
if (op[0] == 'I'){
insert(x);
}
else {
if (check(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
开放寻址法
开放寻址法, 又称“蹲茅坑”法. 同样需要找槽位k:
int k = (x % N + N) % N;
将h
数组初始化成不在数据范围内的数,来标记当前位置没有数, 一般是0x3f3f3f3f
find(x)
要么返回null(0x3f3f3f3f), 表示hash表中不存在这个数, 要么返回x所在的位置.
数组长度也最好取成质数
AcWing 840. 模拟散列表
code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 200003, null = 0x3f3f3f3f;
int h[N];
int n;
int find(int x){
int k = (x % N + N) % N;
while (h[k] != null && h[k] != x){
// 当h[k]有人,并且不是x, 继续往后找,
// 退出循环当且仅当 坑位没人(null) 或者 找到x(k为x应该在的位置)
k ++;
if (k == N) k = 0;
}
return k;
}
int main(){
scanf("%d", &n);
memset(h, 0x3f, sizeof h);
char op[2];
int x;
while (n -- ){
scanf("%s%d", op, &x);
int k = find(x); // find 要么找到x 要么没找到x, 所以将数组初始化成0x3f, 表示null
if (op[0] == 'I'){
h[k] = x;
}else {
if (h[k] != null) puts("Yes");
else puts("No");
}
}
return 0;
}
字符串哈希
AcWing 841. 字符串哈希
分析
注意p[0] = 1
,
h[i] = h[i - 1] * P + str[i]
与h[i] = h[i - 1] * P + str[i] - 'a';
都可以, 前者就映射为ASCII码, 后者映射为0~25
code
#include <iostream>
using namespace std;
const int N = 1e5 + 10, P = 131;
typedef unsigned long long ULL;
int n, m;
char str[N];
int h[N], p[N];
ULL get(int l, int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
cin >> n >> m;
scanf("%s", 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] - 'a';
}
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;
}