串的模式匹配——暴力法与KMP算法

版权声明:本文为博主原创文章,转载请附明出处^_^ https://blog.csdn.net/cprimesplus/article/details/90374592

1.BF算法(暴力法)

初始化两个数组下标,循环比较两个字符串的字符是否相等,相等则下标同时进一,比较下个元素直到匹配成功;不相等则使匹配的串下标回退到 0,被匹配的串下标回退到比较的初始位置+1,循环往复。

代码如下

#include<iostream>
#include<string>
using namespace std;
int BFMatching(string s1, string s2, int pos)
{
	int len1 = s1.length();
	int len2 = s2.length();
    if(len2 > len1 || !len2) return -1;
	int i = pos, j = 0;
	while(i<len1 && j<len2){
		if(s1[i] == s2[j]){
			j++;i++;
		}
		else{
			i = i-j+1;j = 0;
		}
	}
    if(i<=len1 && j==len2) return i-j+1;
	return -1;
}
int main()
{
	string s1 = "HarryPotter";
	string s2 = "rryP";
	cout<<BFMatching(s1, s2, 0);
	return 0;
} 

暴力法好不好呢?(都叫暴力法了能好到哪去)

我们可以举个极端的栗子,有目标串 “abbababca” , 待匹配的模式串  “abababca”想要对这个模式串进行匹配,那么最初的匹配效果如下图:

不难发现,当前面的六个元素全都匹配成功、胜利就在眼前的时候,不幸的事情发生了——i 指针挪到 a 的位置, j 指针挪到 c 的位置,此时遇到了不匹配的情况。怎么办?按照 BF算法的思想,j 指针只有回到最初的起点,i 指针也好不到哪去,只能从最初起点的下一个位置再进行比较。 

j 指针作为模式串匹配情况的度量,回退可以理解,毕竟要找出和模式串完全匹配的下标,来回比较似乎在所难免。然而 i 指针作为目标串的下标,只要匹配不成功便几乎要回退到最初的起点,这是不是有点太过分了?

那么,有没有一种方法,能够使 i 不回退呢?

这便引入了KMP算法。

扫描二维码关注公众号,回复: 6764725 查看本文章

2.KMP算法

在上面,我们讨论了 BF算法的极端环境——一种很糟糕的情况,也提出了相应的问题,即设计一种算法,使得 i 不回退的最初的起点。

假设目标串叫做 P, 模式串叫做 S,回到上面的图仔细观察,可以发现一件事:在 P 串标成了灰色的区域,前四个字符和后四个字符是相同的,均为:abab ,  而这部分,刚刚好与模式串S 的前四个字符匹配成功!

这也就意味着,我们无需将 i 回退到最初的起点。

换言之,S 串标成灰色的前四个字符,恰好是 P 串 标灰部分的后四个字符,而这四个字符因为已经比较过的缘故,无需再进行比较。此时 j 仅仅需要回退到 P[4],而 i 则保留在原来的位置,与 P[4] 之后的元素接着进行比较。

其中黄色区域为接下来待比较的部分。

将上面的做法进行总结,可以归纳为一段话(引自严蔚敏版《数据结构》):每当一趟匹配过程中出现字符比较不相等时,不需要回溯 i 指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,继续进行比较。

上面提到“尽可能远”的距离,也就是尽可能多的和模式串匹配,进而求得的长度。对于上面的图来说,也就是 4(abab).此时这距离为 4 的长度因为已经比较过的缘故(P[0] 到 p[3] 这四个字符和 P[2] 到 P[5] 这四个字符相同,而P[0] 到 p[3] 已经比较过),是可以直接跳过的。

如何确定 模式串 S 应该从哪个字符开始重新比较?这便引入了部分匹配表 PMT(Partial Match Table)。

 

PMT的求取

假设有字符串 S_0S_1...S_{n-1}S_{n}

那么字符串的前缀组成的集合有:pre =\{"S_0","S_0S_1","S_0S_1S_2",..."S_0S_1...S_{n-1}"\}

字符串后缀组成的集合有:post = \{"S_1S_2...S_n","S_2S_3...S_n"..."S_2S_1","S_1"\}

假设:i\in pre,j\in post

PST的定义:PST = MAX\{i,j\},if\ i=j

一个例子:babcab,那么前缀集合为{{b},{ba},{bab},{babc},{babca}},后缀集合为{{abcab},{bcab},{cab},{ab},{b}},此时两个集合的交集为{{ab},{b}},那么交集中含有元素的最多的为{ab},此时 PST=2

 

求取PMT的具体算法,请移步:https://blog.csdn.net/x__1998/article/details/79951598#commentsedit

PMT求取的核心思想:

对一个字符串,将它错开 一个位置进行比较,这样上面一行即在求后缀,而下面一行则是在用前缀与上面的后缀作比较。即:

KMP算法代码

#include<iostream>
#include<string>
#define MAX_SIZE 1024
using namespace std;
int next[MAX_SIZE];
void get_next(string s1)
{
	int len = s1.length();
	int i = 0, j = -1;
	next[0] = -1;                       // 初始next[0]为-1,代表0号字符前无前缀后缀匹配
	while(i < len){
		if(j==-1 || s1[i]==s1[j]){      // 如果是next[1]则为0
			i++;
			j++;
			next[i] = j;
		}
		else{
			j = next[j];                // 如果不满足前缀后缀匹配,则将j跳转到前面next指定的位置
		}
	}
}
int KMP(string s1, string s2, int pos)
{
	int len1 = s1.length();
	int len2 = s2.length();
	if(!len2 || len2>len1) return -1;
	int i = pos, j = 0;
	while(i<len1 || j<len2){
		if(j==-1 || s1[i]==s2[j]){
			i++;
			j++;
		}
		else{
			j = next[j];
		}
	}
	if(j==len2 && i<=len1) 
		return i-j+1;
	else
		return -1;
}
int main()
{
	string s1 = "ababababca";
	string s2 = "abababca";
	get_next(s2);
	cout<<KMP(s1, s2, 0)<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/cprimesplus/article/details/90374592