基于hash算法的字符串匹配(本质上是预处理+查找)
什么是Hash?
Hash,散列函数,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出, 该输出就是散列值(hash值)。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间。
换句话说,在这里就是将输入的字符串转化为一个确定的数值。
一些基本概念:
1.文本串S & 模式串P
2.子串segment
字符串中任意个连续的字符组成的子序列
3.哈希值hash_value
字符串通过hash算法算得的值
如何将字符串转化为数值?
BKDR Hash
我们仿照进制数的思想,来构造hash函数获得对应的hash值。
e.g. 12345 = 1*10^4 + 2*10^3 + 3*10^2 + 4*10^1 + 5*10^0
即转化为:
long long Gethash(){
long long hash_value = 0;
for(long long i = 0;i <= P.length();i++){
hash_value = hash_value * base + P[i];
}
return hash_value;
}
提出问题:
进制过大,字符编码过大等原因导致得到的hash值爆出范围
改进方案:取模 mod
关于mod的选取:
1.mod的范围决定了hash值的值域
2.mod应该取素数
参考资料:https://www.zhihu.com/question/20806796
long long Gethash(){
long long hash_value = 0;
for(long long i = 0;i <= P.length();i++){
hash_value = (hash_value * base + P[i]) % mod;
}
return hash_value;
}
Hash Collision(哈希碰撞):
如果两个输入串的hash函数的值一样,则称这两个串是一个碰撞(Collision)。
既然是把任意长度的字符串变成固定长度的字符串,所以必有一个输出串对应无穷多个输入串,碰撞是必然存在的。
我们很难做到将碰撞的可能性完全消除,但可以通过一些方法尽可能缩小错误率。
改进方案:双哈希(分别取两次模)
在计算一个字符串的哈希值的过程中,用两个不同的 k 和两个不同的模数 m 分别运算,将这两个结果用一个二元组表示,作为哈希的结果。
即每一步迭代产生两个哈希
h1[i] = (h1[i−1] ∗ base1 + P[i]) % mod1
h2[i] = (h2[i−1] ∗ base2 + P[i]) % mod2
此时将二元组作为哈希的结果,即
hash(s[1..i])=<h1[i],h2[i]>
给定一个子串,计算其哈希,就是对该子串分别做两次哈希,然后将这两个哈希值组合为二元组的结果。
模板:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const int base = 131;
const int mod1 = 1333331;
class StringHash {
private:
string S;//文本串S
string P;//模式串P
ll P_hashvalue;//模式串P的哈希值
vector<ll> Hash_value;//存放文本串S的哈希值(%mod1)
vector<ll> Base;//存放base进制下每一位的进制数
public:
StringHash(string s, string p);//构造函数进行初始化,获得每一位的前缀哈希值和进制数
~StringHash() {}//析构函数
void GetHash();//获得模式串的哈希值
ll GetP_hash();//输出模式串的哈希值
ll SegmentHash(ll left, ll right);//获得一段子串的哈希值
void cmp();//比对函数
};
StringHash::StringHash(string s, string p) {
S = s;
P = p;
Hash_value.resize(S.length());
Base.resize(S.length());
Hash_value[0] = (ll)S[0];
Base[0] = 1;
for (ll i = 1; i < S.length(); i++) {
Hash_value[i] = (Hash_value[i - 1] * base + (ll)S[i]) % mod1;
Base[i] = Base[i - 1] * base % mod1;
}
}
void StringHash::GetHash() {
ll value = 0;
for (ll i = 0; i < P.length(); i++) {
value = (value * base + (ll)P[i]) % mod1;
}
P_hashvalue = value;
}
ll StringHash::GetP_hash() {
return P_hashvalue;
}
ll StringHash::SegmentHash(ll left, ll right) {//输入的数是下标
if (left == 0) {
return Hash_value[right];
}
else {
return ((Hash_value[right] - Hash_value[left - 1] * Base[right - left + 1]) % mod1 + mod1) % mod1;//对负数取模
}
}
void StringHash::cmp() {
GetHash();
bool res = false;
for (int i = 0; i <= S.length() - P.length(); i++) {
if (P_hashvalue == SegmentHash(i, i + P.length() - 1)) {
res = true;
cout << "Successfully matched! The index number is " << i << " ." << endl;
}
}
if(!res)cout << "Failed.The substring does not exist." << endl;
}