kmp算法 入门理解
kmp算法是用来解决字符串匹配问题的
给定一个str1字符串和str2字符串,看一下str1字符串中是否有str2字符串,这就相当于集合中的包含关系,看一下str1字符串是否包含str2字符串。
以下内容不是原创
kmp算法的基本思想:
这种算法不太容易理解,网上有很多解释,但读起来都很费劲。直到读到Jake Boxer的文章,我才真正理解这种算法。下面,我用自己的语言,试图写一篇比较好懂的KMP算法解释。
1.
首先,str1字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与str2字符串"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以str2字符串后移一位。
2.
因为B与A不匹配,str2字符串再往后移。
3.
就这样,直到字符串有一个字符,与str2字符串的第一个字符相同为止。
4.
接着比较str1字符串和str2字符串的下一个字符,还是相同。
5.
直到str1字符串有一个字符,与str2字符串对应的字符不相同为止。
6.
这时,最自然的反应是,将str2字符串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。
7.
一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。
8.
如何得到的这个表和这个表每个位置上的数表示的是什么你先不用着急直到,后面会讲,现在你会用就行
9.
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符D对应表中的数字是2,那么“搜索位置”就从最后一个字符往前跳到str2字符串的第3个位置(str2字符串往后移动4位),如下图
10.
因为空格与C不匹配,此时“搜索位置”为str2字符串的3位置,对应表中的数字为0,故搜索位置跳到str2字符串的0位置(str2字符串往后移动2位),如下图
11.
此时A与空格不匹配,”搜索位置“不变,str2往后移动1位
12.
逐位比较,直到发现C与D不匹配。查表可知,“搜索位置”往前跳到str2字符串的3位置(str2字符串往后移动4位),
13.
逐位比较,直到str2字符串的最后一位,发先str2字符串的最后一位也匹配成功,说明str1字符串中存在str2字符串,算法结束
"
next数组如何得到的:
首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
15.
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
16.
"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。str2字符串移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。
代码如下:
#include<bits/stdc++.h>
#define MAXN 9999
using namespace std;
string str1,str2;//str1为总的字符串 str2为匹配的字符串
int next[MAXN];//next[i]代表str2字符串i位置之前的最长前缀
void getnextarray()//这个函数是求next数组的
{
if(str2.size()==1)//匹配的字符串只有一个字符
{
next[0]=-1;//我们人为规定next[0]为-1,next[1]为0
next[1]=0;
return ;
}
next[0]=-1;
next[1]=0;
int i=2;//i代表填充next数组的i位置(str2字符串i位置前面的字符串的最长前缀)
int cn=0;//cn始终代表str2字符串i-1位置前面的字符串的最长前缀的下一个字符的位置
while (i<=str2.size())
{
if(str2[i-1]==str2[cn])//如果str2字符串i-1位置上的字符等于str2字符串cn位置上的字符的话,直接在next[i]的基础上加1即可
next[i++]=++cn;
else if(cn>0)//这个条件满足,说明可以往前跳,让cn往前跳
cn=next[cn];
else
next[i++]=0;//str2字符串i位置前面的字符串没有前缀
}
}
int kmp()//kmp算法,如果匹配成功,返回1,否则,返回0
{
int s1=0,s2=0;//这是两个指针,s1刚开始指向str1字符串的第一个字符,s2刚开始指向str2字符串的第一个字符
while (s1<str1.size()&&s2<str2.size())//两个指针都没有到达最后一个字符时,执行下面过程
{
if(str1[s1]==str2[s2])//str1字符串s1位置上的字符和str2字符串s2位置上的字符相等
{
s1++;//让两个指针都往后移动一位
s2++;
}
else if(next[s2]==-1)//如果程序运行到这里,说明上面的条件不满足,str1字符串s1位置上的字符和str2字符串s2位置上的字符不相等,如果这个条件满足,说明str1字符串第一个字符就不和str2字符串匹配,让s1往后移动一个位置
s1++;
else
s2=next[s2];//这是语句是kmp算法的核心内容,也是kmp算法为什么快的原因
}
if(s2==str2.size())//这个条件如果满足,说明str2字符串都已经匹配完了,并且匹配成功了,返回1
return 1;
else
return 0;
}
int main()
{
cin>>str1>>str2;
getnextarray();
if(kmp())
cout<<"匹配成功"<<endl;
else
cout<<"匹配失败"<<endl;
}