哈希算法
哈希表的存储结构
- 开放寻址法
- 拉链法
memset是按字节来初始化的,int中有四个字节,初始化成0x3f就是将每个字节都初始化成0x3f,所以每个int就是 0x3f3f3f3f
通过哈希函数h(x)
这个函数可以映射到某个位置
- x mod 10^5
- 冲突,两个不一样的数但是映射到同一个数
- 处理冲突
离散化是极其特殊的哈希方式。因为他要有序
拉链法
拉链的长度都比较短。一般没有删除
只有添加和查找某个数
删除的话就在那个位置做一个标记,开一个bool变量
做哈希的时候,数组长度,一般来说是一个质数
#include <cstdio> #include <iostream> #include <string.h> using namespace std; const int maxn = 1e5 + 3; int h[maxn], edx[maxn], ne[maxn], idx; // h[k]表示第k位数字指向的下一位地址 // ne[idx]表示第idx位数字的地址是多少 void insert(int x) { int k = (x % maxn + maxn) % maxn; edx[idx] = x; ne[idx] = h[k]; h[k] = idx; idx++; } bool find(int x) { int k = (x % maxn + maxn) % maxn; for (int i = h[k]; i != -1; i = ne[i]) // 空指针用-1来表示 if (edx[i] == x) return true; return false; } int main() { int n; char op[2]; cin >> n; memset(h, -1, sizeof h); // -1表示指向的下一个节点是空 while (n -- ) { //exit(0); int x; scanf("%s%d", op, &x); if(!strcmp(op, "I")) // *op == ‘I‘ { insert(x); } if(!strcmp(op, "Q")) { if(find(x)) puts("Yes"); else puts("No"); } } return 0; }
开放寻址法
一般要开2-3倍的位置
上厕所一个道理,找不到厕所就找下一个,下一个还找不到就再下一个。最后的还找不到就去第一个
#include <cstdio> #include <iostream> #include <string.h> using namespace std; const int maxn = 200003, null = 0x3f3f3f3f; int h[maxn]; int find(int x) { int k = (x % maxn + maxn) % maxn; while (h[k] != null && h[k] != x) // 如果相同的话就返回相同的位置 { k ++ ; if (k == maxn) k = 0; } return k; } int main() { int n; char op[2]; cin >> n; memset(h, 0x3f, sizeof h); // -1表示指向的下一个节点是空 while (n -- ) { //exit(0); int x; scanf("%s%d", op, &x); int k = find(x); if(!strcmp(op, "I")) // *op == ‘I‘ { h[k] = x; } if(!strcmp(op, "Q")) { // if (h[k] != null) puts("Yes"); // else puts("No"); // 可能是k不在范围内,只要求了mod,肯定在正常范围内 if (h[k] == null) puts("No"); else puts("Yes"); } } return 0; }
时间复杂度是\(O(1)\) 但是用到哈希表的题目都是$ O(N)$
大部分的题目都是赋值成-1,0,0x3f
字符串哈希
想做的事情就是把字符串变成数字
这样能直接通过数字来比较两个字符串是否相等
计算方法
计算某一段的哈希值的时候比如
ABDCA
我要取出CA
根据定义分别求出\(hash[i]\)
$ hash[1]=s1$
$ hash[2]=s1?p+s2$
\(hash[3]=s1?p^2+s2?p+s3\)
\(hash[4]=s1?p^3+s2?p^2+s^3?p+s4\)
$ hash[5]=s1?p4+s2?p3+s3?p^2+s4?p+s5$
现在我们想求\(s_3s_4\) 的\(hash\)值,不难得出为\(s_3 * p +s_4\) ,并且从上面观察,如果看\(hash[4]?hash[2]\)并将结果种带有\(s_1,s_2\),系数的项全部消掉,就是所求。但是由于\(p\)的阶数,不能直接消掉,所以问题就转化成,将\(hash[2]\)乘一个关于\(p\)的系数,在做差的时候将多余项消除,从而得到结果。
不难发现,对应项系数只差一个\(p^2\)
,而\(4 - 3 + 1 = 2\)(待求\(hash\)子串下标相减再加一),这样就不难推导出来此例题的求解式子。
具体方法
- 每一个字符变成一个数字
- 把其想成一串K进制的数字
注意事项
- 字母不能映射成0 AA,A,AAA 都是0。会冲突
- RP足够好,不存在冲突
- p = 131, 13331
- q = 2^64 99.99% 不存在冲突
- 不能容忍冲突,能处理冲突
好处
快速判断两个字符串是否相等(把字符串变成数字)
用公式算出字串的哈希值
h[]表示某一前缀的哈希值
#include <cstdio> #include <iostream> using namespace std; typedef unsigned long long ULL; // 用unsigned long long就不需要取模了 const int maxn = 1e5 + 10, P = 131; ULL p[maxn], h[maxn]; char str[maxn]; int n, m; // h[i] = h[i - 1] * p + str[i]; // h[r] - h[l - 1] * p[r - l + 1]; l - r之间有几个数字 ULL get(int l, int r) { return h[r] - h[l - 1] * p[r - l + 1]; } int main() { int n, m; p[0] = 1; cin >> n >> m; scanf("%s", str + 1); for (int i = 1; i <= n; i ++ ) { p[i] = p[i - 1] * P; // P进制 h[i] = h[i - 1] * P + str[i]; // 移动了一位之后,ascii码不会超过P } while (m -- ) { int l1, l2, r1, r2; cin >> l1 >> r1 >> l2 >> r2; if(get(l1, r1) == get(l2, r2)) puts("Yes"); else puts("No"); } return 0; }
KMP可以求字符串的循环节
坐在马桶上看算法