浅谈字符串哈希
哈希HASH
哈希是对于字符串的一种操作。
在日常的百度搜索什么的都是根据关键字来查找,我们可以利用 hash 来加速这个过程。
哈希的思想
哈希其实是所有字符串操作中,最简单的操作了。哈希的过程,其实可以看作对一个串的单向加密过程,并且需要保证所加的密不能高概率重复,通过这种方式来替代一些很费时间的操作。
比如,最常见的,当然就是通过哈希数组来判断几个串是否相同(洛谷P3370)。此处的操作呢,很简单,就是对于每个串,我们通过一个固定的转换方式,将相同的串使其的“加密后的值”一定相同,不同的串尽量不同。
此处有人指出:那难道不能先比对字符串长度,然后比对 ASCLL 码之和吗?事实上显然是不行的(比如 ab 和 ba ,并不是同一个串,但是如是做却会让其认为是)。这种情况就叫做hash冲突,并且在如此的单向加密哈希中,hash 冲突的情况在所难免。
而我们此处介绍的,即是最常见的一种哈希:进制哈希。进制哈希的核心便是:给出一个固定进制 base,将一个串的每一个元素看做一个进制位上的数字,所以这个串就可以看做一个 base 进制的数,那么这个数就是这个串的哈希值;则我们通过比对每个串的的哈希值,即可判断两个串是否相同。
来练练手:
直接用 unsigned long long 自然溢出取模。
code:
#include <bits/stdc++.h>
#define int unsigned long long
#define N 10010
using namespace std;
const int base = 131;
int a[N], n, ans = 1;
char s[N];
inline int Hash(char s[])
{
int len = strlen(s), res = 0;
for(int i = 0; i < len; i ++)
res = (res * base + (int)s[i]);
return res;
}
signed main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
scanf("%s", s);
a[i] = Hash(s);
}
sort(a + 1, a + n + 1);
for(int i = 1; i < n; i ++)
if(a[i] != a[i + 1]) ans ++;
cout << ans << endl;
return 0;
}
取模哈希
其实和上面的区别就是加了取模,当然也是对质数取模。
上面是用了 ull 自然溢出,但是自己取模更放心!
code:
#include <bits/stdc++.h>
#define int long long
#define P 998244353
#define N 10010
using namespace std;
const int base = 131;
int a[N], n, ans = 1;
char s[N];
inline int Hash(char s[])
{
int len = strlen(s), res = 0;
for(int i = 0; i < len; i ++)
res = (res * base + (int)s[i]) % P;
return res;
}
signed main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
scanf("%s", s);
a[i] = Hash(s);
}
sort(a + 1, a + n + 1);
for(int i = 1; i < n; i ++)
if(a[i] != a[i + 1]) ans ++;
cout << ans << endl;
return 0;
}
双哈希
和上面的单哈希的区别就是多一个用不同质数取模的哈希值,更加安全,hash 冲突的几率变得更小。
#include <bits/stdc++.h>
#define int long long
#define P1 192608173
#define P2 998244353
#define N 100010
using namespace std;
const int base = 31;
int n, ans = 1;
char s[N];
struct sb{int h1, h2;}e[N];
inline int cmp(sb a, sb b)
{
if(a.h1 == b.h1) return a.h2 < b.h2;
else return a.h1 < b.h1;
}
inline int Hash1(char s[])
{
int len = strlen(s), res = 0;
for(int i = 0; i < len; i ++)
res = (res * base + (int)s[i]) % P1;
return res;
}
inline int Hash2(char s[])
{
int len = strlen(s), res = 0;
for(int i = 0; i < len; i ++)
res = (res * base + (int)s[i]) % P2;
return res;
}
signed main()
{
cin >> n;
for(int i = 1; i <= n; i ++)
{
scanf("%s", s);
e[i].h1 = Hash1(s);
e[i].h2 = Hash2(s);
}
sort(e + 1, e + n + 1, cmp);
for(int i = 1; i < n; i ++)
if(e[i].h1 != e[i + 1].h1 && e[i].h2 != e[i + 1].h2) ans ++;
cout << ans << endl;
return 0;
}
关于效率:
自然溢出哈希快于取模单哈希快于双哈希。
主要还是在取模上面慢了。
但是双哈希的安全性更高,平常更推荐使用双哈希。
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/p/17504162.html
The heart is higher than the sky, and life is thinner than paper.