滚动哈希(Rabin_Karp)算法

Rabin_Karp算法利用哈希的特性,求出子串的哈希值,可以用来寻找某串是否在字符串中出现以及个数。

例如 给出文本串 aaabbc 模式串 ab

询问 模式串  是否在 文本串中 出现过  ,可以求出文本串中长度为2的子串的哈希值,

即 aa aa ab bb bc ,再求出 模式串ab的哈希值, 通过比较哈希值判断是否出现过。

求哈希值时,若按位求子串哈希值,复杂度为n * m。

若使用Rabin_Karp算法,可以降为 n + m。

就以求 abcaabc 的长度k为 3 的子串 为例

定义 r = p^k​​​​​​​​​​​​

abc的哈希值为 s[0] * p * p + s[1] * p + s[2];

bca的哈希值为 s[1] * p * p + s[2] * p + s[3];

显然bca的哈希值可以由s[0] * p * p * p + s[1] * p * p + s[2] * p + s[3] - s[0] * p * p * p

即h[2] * p + s[3] - s[3 - 3] * r 得到;

caa的哈希值为 s[2] * p * p + s[3] * p + s[4];

可以由 s[1] * p * p * p + s[2] * p * p + s[3] * p + s[4] - s[1] * p * p * p

即h[3] * p + s[4] - s[4 - 3] * r 得到;

这里就可以推出Rabin_Karp算法的原理;

h = h * p + s[i] - s[i - k] * r; (i为该子串最后位置的下标)

实现时,先计算出r, 即是p 的 k次方(子串的长度);

然后计算下一组时 去掉前一位再加上后面一位;

这时复杂度即为n + m;

至于撞哈希, emm看运气吧

以一道例题为例

POJ -1200 Crazy Search

http://poj.org/problem?id=1200

题意是给出一个n, 一个m, 再给出一个字符串,询问在字符串中长度为n的不同子串个数;

m为给出的串中,不同字符的个数,emmm 没啥用;

直接上代码了

#include<iostream>
#include<vector>
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
vector<ull>v;
int main(){
	int n, m;
	cin >> n >> m;
	string s;
	cin >> s;
	int len = s.size();
	ull p = 1e9 + 9, r = 1, h = 0;
	for(int i = 0; i < n && i < len; i++)  // 先求前k位,算出r
		h = h * p + s[i], r *= p;
	if(len >= n)    // 如果原串长度大于k, 即先前求的k位是其子串, 加入集合
		v.push_back(h); 
	for(int i = n; i < len; i++)  // 计算k位以后的子串, 过程即是上面的介绍。
		h = h * p + s[i] - s[i - n] * r, v.push_back(h);
	sort(v.begin(), v.end());  // 排序为了下面的去重
	v.erase(unique(v.begin(), v.end()), v.end());   // 去重
	cout << v.size() << endl;   // 输出大小 即是答案
	return 0;	
}

再来一道实践的, 找出模式串在文本串中出现的个数。

emmm, 听起来像kmp;

ZZULIOJ 1270: 剪花布条

http://acm.zzuli.edu.cn/problem.php?id=1270

#include<iostream>
#include<vector>
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
vector<ull>v;
int main(){
	string s, b;
	while (cin >> s, s != "#")
	{
		cin >> b;
		int len = s.size();
		int k = b.size();
		int cnt = 0;
		ull p = 1e9 + 9, r = 1, h = 0, c = 0;
		for(int i = 0; i < len && i < k; i++)   
			h = h * p + s[i], r *= p;
		if(len >= k)
			v.push_back(h);
		for(int i = k; i < len; i++)
			h = h * p + s[i] - s[i - k] * r, v.push_back(h); // 与上面的算法相同求出文本串每个位置的哈希值
		for(int i = 0; i < k; i++) // 求出模式串的哈希值
			c = c * p + b[i];
		for(int i = 0; i < v.size(); i++)
		{
			if(v[i] == c) // 如果当前位置的哈希值等于模式串, 即是相同,
				cnt++, i += k - 1;  // 注意这题是不能共用同一个字符, 所以i 要加上k, 后面又i++了, 所以i += k - 1;
		}
		cout << cnt << endl;
		v.clear();
	}
	return 0;	
}

看懂了可以再找几道题练练

 

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值