字符串匹配算法(KMP)

时间:2023-12-26 09:21:01

字符串匹配运用很广泛,举个简单例子,我们每天登QQ时输入账号和密码,大家有没有想过账号和密码是怎样匹配的呢?登录需要多长时间和匹配算法的效率有直接的关系。

首先理解一下前缀和后缀的概念:

字符串匹配算法(KMP)

给出一个问题:现在有一个文本串S=“BBC ABCDAB ABCDABCDABDE”和一个搜索串(模式串)p="ABCDABD",要查找p在s中的位置。我们常用的一种方法就是暴力求解,暴力求解的思想是:让模式串从文本串的第一个字符开始往后匹配,假设现在文本串匹配到
i 位置,模式串匹配到 j 位置:

如果当前字符匹配成功,则 i++, j++

如果当前字符匹配不成功,i 要回溯 , j 要归零 ;

字符串匹配算法(KMP)

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

字符串匹配算法(KMP)

因为B与A不匹配,搜索词再往后移。

字符串匹配算法(KMP)

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

字符串匹配算法(KMP)

接着比较字符串和搜索词的下一个字符,还是相同。

字符串匹配算法(KMP)

直到字符串有一个字符,与搜索词对应的字符不相同为止。

字符串匹配算法(KMP)

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

相应代码如下:

#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char s[100] , p[100] ;
cin >> s >> p ;
int lens = strlen(s) , lenp = strlen(p);
int i , j , k ;
bool flag = false ;
for(i = 0 ; i < lens ; i++ ) {
for(j = 0 , k = i ; j < lenp && k < lens ; j++)
if(s[k] == p[j])
k++ ;
else
break ;
if(j == lenp) {
cout << i + 1 << endl ;
flag = true ;
break ;
}
}
if(flag == false)
cout << "匹配失败" << endl ;
return 0;
}

以上代码可以进行简化:

#include <iostream>
#include <string.h>
using namespace std;
int main()
{
char s[100] , p[100] ;
cin >> s >> p ;
int lens = strlen(s) , lenp = strlen(p);
int i = 0 , j = 0 , k = 0 ;
while(i < lens && j < lenp) {
if(s[i] == p[j]) {
i++ ;
j++ ;
}
else{
i = i - j + 1 ;
j = 0 ;
}
}
if(j == lenp)
cout << i - j + 1 << endl ;
else
cout << "匹配失败" << endl ;
return 0;
}

暴力求解有很多比较都是多余的,下面介绍一种比较快速的查找方法。

KMP算法:

Knuth-Morris-Pratt字符串查找算法,简称“KMP”算法,常用于在一个文本串中查找一个模式串出现的位置。

思想:利用模式串中在匹配过程中,不匹配字符前面那一段最长前缀后缀,尽可能减少多余的匹配。

字符串匹配算法(KMP)

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。"ABCDAB"之中有两个"AB",搜索词移动的时候,第一个"AB"向后移动4位,就可以来到第二个"AB"的位置。

首先需要对模式串进行处理,这里需要定义一个next数组,某个字符失配时,该字符对应的next 值会告诉你下一步匹配中,模式串应该跳到哪个位置。

代码雏形如下,其中next数组还未知:

#include <string.h>
using namespace std;
int main()
{
char s[100] , p[100] ;
cin >> s >> p ;
int lens = strlen(s) , lenp = strlen(p);
int i = 0 , j = 0 , k = 0 ;
while(i < lens && j < lenp) {
if(s[i] == p[j] || j == -1) {
i++ ;
j++ ;
}
else{
j = next[j] ;
}
}
if(j == lenp)
cout << i - j + 1 << endl ;
else
cout << "匹配失败" << endl ;
return 0;
}

下面求解next数组:

next数组中存储的就是当前模式串已匹配过的字符组成的字符串最长前缀后缀的长度:

字符串匹配算法(KMP)