StringHash字符串哈希

基于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

f(P)=\sum_{i=1}^{l}P[i]*base^{l-i}

即转化为:

h[i] = h[i - 1]*base+P[i]

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;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值