第77篇 C++实现字符串匹配(二)RK算法

第77篇 C++实现字符串匹配(二)RK算法

1.RK简单描述

这是由Rabin和Karp提出的字符串匹配(检索)算法,即称RK算法。RK算法的基本思想是按字符串的Hash值查找,即:

计算出两个字符串的Hash值,如果两个字符串hash的值不相同,则它们肯定不相同,已经没有去比较字符串内容的必要,如果它们hash后的值相同,它们不一定相同,但是可以通过比较内容去确定两个字符串是否相同。

RK算法的基本思想就是:将目标字符串target的hash值跟主串mainString中的每一个长度为target.length的子串的hash值比较。如果二者hash值不同,则它们肯定不相等;如果二者hash值相同,则再逐位比较每一个字符。

计算目标字符串的Hash值:
这里有很多形式,我暂时没有什么好的方法,直接计算字符对应的ASCII码值,当然为了避免加起来的值太大,加的是每个字符减掉’A’。
计算如下:

int targetHash = 0;
for(char c: target) {
	targetHash += c – 'A';
}

这样的话,如果字符全部是由ASCII码小于128且大于65的字符组成,最少可以计算的target的长度为:
len = 2147483647 / (127 – 65) = 34636833(接近3500万(个字符)的长度)。
最多就是2147483647(21亿个字符)了,即字符全是’B’的时候。(int为-231—231-1)

而主串长度为target.length的子串p计算初始时即是从首位0到target.length-1处的Hash值,巧妙的是如果匹配不成功,往后的计算都是减掉p的第一位,然后从主串中p末位的后一位加进来即可。即主串每次都是往后移动一位,没有回溯。

我的话说得不怎么明白,且看如下字符串。

0123456789
Sabcabcmnbc
Tbcmn

(1)计算T的hash值:Thash = ‘b’- ‘A’ + ’c’ - ‘A’ +’m’ - ‘A’ +’n’ - ‘A’ = 98 – 65 + 99 – 65 + 109 – 65 + 110 – 65 = 156;(代码中通过循环实现)
(2)计算S第一个子串P的hash值:Phash = ‘a’ - ‘A’ +’b’ - ‘A’ +’c’ - ‘A’ +’a’ - ‘A’ = 97 – 65 + 98 – 65 + 99 – 65 + 97 – 65 = 131;(注意每次都减掉’A’,不然字符串过长是,很容易溢出)。
(3)比较T和P的hash值,因为二者不相等,所以,直接移动主串,即更新P的hash值,更新:Phash -= S[0] – ‘A’,Phash += s[4] – ‘A’,即先减掉P的首位,再加上字符串P在S中后一位的hash值,上述可简化为Phash = Phash – (S[0] – ‘A’) + (s[4] – ‘A’),再化简就是Phash = Phash – S[0] + s[4] = Phash – (S[0] - s[4]),结果为Phash -= S[0] - s[4];Phash = 132;
(4)因为Thash = 156不等于132,所以Phash -= S[1] – S[5],Phash = 133;
(5)因为Thash = 156不等于133,所以Phash -= S[2] – S[6],Phash = 143;
(6)因为Thash = 156不等于143,所以Phash -= S[3] – S[7],Phash = 156;
(7)Thash = 156等于Phash = 156,且此时P = “bcmn” == T = “bcmn”,匹配成功。

可以看出过程简单,而且不需要主串回溯,就感觉很行,而且避免了不必要的比较。

2.代码解说

这是全部代码。

#include <iostream>
using namespace std;
#include <string>

//Rabin-Karp RK算法   哈希检索
int rabinKarp(string mainString, string target);
bool isMacth(string s, int i,string p,int m);
void rabinKarpTest();

int main() {
	rabinKarpTest();
	return 0;
}

//Rabin-Karp RK算法   哈希检索
int rabinKarp(string mainString, string target) {
	int mlen = mainString.size();
	int tlen = target.size();

	int targetHash = 0;
int mainSubStrHash = 0;//默认tlen <= mlen
	for(int i = 0;i < tlen;i++) {
		targetHash += (target[i] - 'A');
		mainSubStrHash += (mainString[i] - 'A');
	}

	for(int i = tlen;i < mlen + 1;i++) {
		if(targetHash == mainSubStrHash && isMacth(mainString,i - tlen,target,tlen)) {
			return i - tlen;
		}
		mainSubStrHash -= mainString[i - tlen] - mainString[i];
	}

	return -1;
}

bool isMacth(string s, int i,string p,int m) {
	for(int j = i,k = 0;j < i + m && k < m;j++, k++) {
		if(s[j] != p[k]) {
			return false;
		}
	}
	return true;
}

void rabinKarpTest() {
	string mainString;
	string target;
	int contin = 1;
	do {
		system("cls");
		cout << "当前检测的是RK(RabinKarp)算法\n" << endl;
		cout << "请输入mainString:";
		cin >> mainString;
		cout << "请输入target:";
		cin >> target;

		int result =  rabinKarp(mainString, target);
		if(result != -1) {
			cout << target << "的起始位置在" << mainString << "的" << result << "处" << endl;
		}
		else {
			cout << target << "不在" << mainString << "中" << endl;
		}

		cout << "继续检测输入1,否则输入0:";
		cin >> contin;
	} while (contin == 1);
	cout << "欢迎再次检测!" << endl;
}

先计算target的hash值以及主串第一个长度为target.length的子串的hash值,默认tlen <= mlen,因为我们可以先判断target的长度是否大于mainString的长度,大于那肯定就匹配不成功了。

int targetHash = 0;
int mainSubStrHash = 0;//默认tlen <= mlen
for(int i = 0;i < tlen;i++) {
	targetHash += (target[i] - 'A');
	mainSubStrHash += (mainString[i] - 'A');
}

下面说说为什么循环条件是mlen + 1,即主串长度加一。
比如mainString = “123”,target = “23”,
进入循环时i = 2,然后匹配是不成功的,所以更新hash值后,i++,此时 i为3,那么i就等于主串的长度了,如果条件是mlen的话,就不匹配了。

for(int i = tlen;i < mlen + 1;i++) {
	if(targetHash == mainSubStrHash && isMacth(mainString,i - tlen,target,tlen)) {
		return i - tlen;
	}
	mainSubStrHash -= mainString[i - tlen] - mainString[i];
}

3.结语

RK算法是除了BF算法之外的匹配算法,我一看就懂的,特别容易理解,而且写一遍代码之后,就已经记住了,而且原理也很清晰明了。当然了,我个人表述应该不是很明了,哈哈。
其实不会的东西我有个习惯就是先知道答案再去做去理解,这也有效果的,这也不失为一种学习方法,自己苦苦思索很久,有时候也不太明智。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大唐不良猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值