IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 第77篇 C++实现字符串匹配(二)RK算法 -> 正文阅读

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

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-11-19 17:52:03  更:2021-11-19 17:52:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 12:48:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码