算法基础—字符串哈希

1、何为字符串哈希?

字符串哈希又称为字符串前缀哈希法,将字符串利用函数f实现不同的字符串映射到不同的整数(哈希值)。
函数 f 可以方便地帮我们判断两个字符串是否相等。

字符串哈希的性质:
1、在 Hash 函数值不一样的时候,两个字符串一定不一样;
2、在 Hash 函数值一样的时候,两个字符串在理想状态下是一样的
(但有极小概率不一样,将 Hash 函数值一样但原字符串不一样的现象称为哈希碰撞)

2、字符串哈希如何映射?

X 1 X 2 X 3 X 4 … X n − 1 X n X_1 X_2 X_3X_4 \ldots X_{n-1}X_n X1X2X3X4Xn1Xn

如以上字符串所示,我们可以采用字符的ASCII 码乘上P的次方在模上一个大整数Q来计算哈希值,公式如下:
将字符串转化为整数
h ( X 1 … X n ) = ( X 1 × P n − 1 + X 2 × P n − 2 + … + X n − 1 × P 1 + X n × P 0 ) h(X_1\ldots X_n)=(X_1 \times P^{n-1} + X_2 \times P^{n-2} + \ldots + X_{n-1} \times P^1 + X_n \times P^0) hX1Xn=(X1×Pn1+X2×Pn2++Xn1×P1+Xn×P0)
对字符串对应的整数值取模,进行哈希映射
h a s h ( X 1 … X n ) = h ( X 1 … X n ) m o d    Q hash(X_1\ldots X_n)= h(X_1\ldots X_n)\mod Q hash(X1Xn)=hX1XnmodQ

注意:
1、不能将字符映射为 0 ,在此前提下,字符映射可以随意,此处为了方便直接采用字符的ASCII 码
2、根据经验,一般将设置P 为131 或 13331 , Q为2^64时,此时我们可以认为不产生哈希碰撞的冲突。
3、取模时,用 unsigned long long 类型来存储变量。
因为unsigned long long 它能够存储的最大值是 2 64 − 1 2^{64} - 1 2641
它是一个 64 位的无符号整数,所以能够表示的数值范围是从 0 —— 2 64 − 1 0 ——2^{64} - 1 0——2641
如果h数组存储的数据超过了unsigned long long 类型的最大值,将会发生数据变量溢出了,它会回绕到 0进行存储,因此这个过程相当于对2^64取模了,就不需要用h mod Q了。

3、如何应用?

应用的字符串前缀哈希的核心在于以下两个公式:
1、利用前缀和的思想,求字符串s的前缀哈希
h [ i ] = h [ i − 1 ] × p + s [ i ] , h [ 0 ] = 0 ( 1 ) h[i] = h[i-1] \times p + s[i],h[0]=0 \quad\quad(1) h[i]=h[i1]×p+s[i]h[0]=0(1)

如图中例子所示,利用公式(1),给定字符串s,就可以通过公式求出字符串的前缀哈希,并将其保存到h数组当中
在这里插入图片描述

2、利用字符串s的前缀哈希,求区间和,求任意每一段字符串的哈希(即每段区间的哈希值)
h [ l . . . r ] = h [ r ] − h [ l − 1 ] × p ( r − l + 1 ) ( 2 ) h[l...r] = h[r] -h[l-1] \times p^{(r-l+1)} \quad\quad(2) h[l...r]=h[r]h[l1]×p(rl+1)(2)

如下图所示,利用公式2,我们可以求出给定字符串s的每段区间和,从而通过比较每段字符串的哈希是否相同来判断,两段字符串是否相等。

在这里插入图片描述

4、字符串哈希算法例题

利用上面的描述,用代码来解决 AcWing 841. 字符串哈希
AC代码如下:

#include<iostream>
using namespace std;

const int N = 100010;
typedef unsigned long long ULL;
char s[N];
//P 取131 或 13331
int P = 131;
//h数组记录哈希,p记录对应的次方
ULL h[N], p[N];
//获取某个区间字符串的哈希
ULL get(int l, int r) {

	return h[r] - h[l-1] * p[r - l + 1];

}

//构造字符串前缀哈希
void init_str_hash(char s[],int n) {
	p[0] = 1;
	for (int i = 1; i <= n; i++) {
		h[i] = h[i - 1] * P + s[i];
		//存储的是131的i次方
		p[i] = p[i - 1] * P;
	}

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> s[i];
	init_str_hash(s, n);

	while (m--) {
		int l1, r1,l2,r2;
		cin >> l1 >> r1 >> l2 >> r2;
		if (get(l1, r1) == get(l2, r2)) cout << "Yes" << '\n';
		else
			cout << "No" << '\n';
	}
	
	return 0;
}
  • 27
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值