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

1.BF算法(暴力法)

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

代码如下

#include
#include
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

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

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

串的模式匹配——暴力法与KMP算法_第1张图片

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

串的模式匹配——暴力法与KMP算法_第2张图片

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

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

这便引入了KMP算法。

 

2.KMP算法

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

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

串的模式匹配——暴力法与KMP算法_第3张图片

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

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

串的模式匹配——暴力法与KMP算法_第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 =\{

字符串后缀组成的集合有:post = \{

假设: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算法_第5张图片

KMP算法代码

#include
#include
#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

 

你可能感兴趣的:(数据结构)