字符串哈希初探和模板题解析
一. 哈希算法回顾
哈希函数: 将关键字映射成对应的地址的函数,记为 Hash(key) = Addr。
哈希冲突: 哈希函数可能会把两个或两个以上的不同关键字映射到同⼀地址,这种情况称为哈希冲
突。
二. 字符串哈希原理和代码实现
定义⼀个把字符串映射到整数的函数 hash,这就是字符串哈希。说⽩了,就是将⼀个字符串⽤⼀个整数表⽰。
1. 字符串哈希中的哈希函数
在字符串哈希中,有⼀种冲突概率较⼩的哈希函数,将字符串映射成 p 进制 数字:hash(s) = (i = 0 -> n - 1)Σ s[i] * (p ^ (n - i - 1)) (mod M)
其中,p 通常取质数 131 或者 13331
。如果把哈希值定义为 unsigned long long 类型,在 C++ 中,溢出就会⾃动取模。
(你没有看错,字符串哈希就是背⼀个公式即可…)
但是,实际求哈希值时,我们⽤的是前缀哈希的思想来求,这样会和下⾯的多次询问⼦串哈希⼀致。
2. 前缀哈希数组
单次计算⼀个字符串的哈希值复杂度是 O(n)。如果需要多次询问⼀个字符串的⼦串的哈希值,每次重新计算效率⾮常低下。
⼀般利⽤ 前缀和思想 先预处理字符串中每个前缀的哈希值,这样的话每次就能快速求出⼦串的哈希
了。
3. 代码实现
#include <iostream>
typedef unsigned long long ULL;
const int N = 1e6 + 10, P = 13331;
char str[N];
int len;
ULL PreFix[N];//前缀哈希数组
ULL p[N];//记录 p 的 i 次⽅
//处理前缀哈希数组以及 p 的 i 次⽅数组
//处理哈希表
void init_hash()
{
PreFix[0] = 0; p[0] = 1;
for(int i = 1; i <= len; i++)
{
PreFix[i] = PreFix[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
}
// 快速求得任意区间的哈希值
ULL get_hash(int l, int r)
{
return PreFix[r] - PreFix[l - 1] * p[r - l + 1];
}
如果题⽬只是简单的求单个字符串的哈希值:
typedef unsigned long long ULL;
const int N = 1e6 + 10;
int len;
char str[N];
ULL get_hash()
{
ULL ret = 0;
for(int i = 1; i <= len; i++) {
ret = ret * p + str[i];
}
return ret;
}
三.例题详解
P3370 【模板】字符串哈希
1. 题目描述
如题,给定 N N N 个字符串(第 i i i 个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N 个字符串中共有多少个不同的字符串。
2. 输入格式
第一行包含一个整数 N N N,为字符串的个数。
接下来 N N N 行每行包含一个字符串,为所提供的字符串。
3. 输出格式
输出包含一行,包含一个整数,为不同的字符串个数。
4. 输入输出样例
输入
5
abc
aaaa
abc
abcc
12345
输出
4
5. 说明/提示
对于 100 % 100\% 100% 的数据: N ≤ 10000 N\leq 10000 N≤10000, M i ≈ 1000 M_i≈1000 Mi≈1000, M m a x ≤ 1500 Mmax\leq 1500 Mmax≤1500。
解题思路
如题可知此题为字符串模板题,亦可使用 unordered_map 或者 map
直接解决问题。
直接模拟字符串哈希的实现流程即可
#include <iostream>
#include <string>
#include <algorithm>
typedef unsigned long long ULL;
const int N = 1e4 + 10, P = 131;
int n;
ULL hash[N];
//将字符串映射成一个哈希值
ULL get_hash(std::string str)
{
ULL ret = 0;
int n = str.size();
for(int i = 0; i < n; i++) {
ret = ret * P + (ULL)str[i];
}
return ret;
}
int main(void)
{
std::cin >> n;
for(int i = 1; i <= n; i++) {
std::string str;
std::cin >> str;
hash[i] = get_hash(str);
}
//排序便于统计不重复数
std::sort(hash + 1, hash + 1 + n);
int ret = 0;
for(int i = 1; i <= n; i++) {
if(hash[i] != hash[i - 1]) {
ret++;
}
}
std::cout << ret << std::endl;
return 0;
}
如有不足之处欢迎各位指出,感谢!