题目大意:给定两个字符串,a(主串) b(模式串),若在a中找到与b完全相同的一个子串,返回a中匹配到的第一个字符的位置: eg:ABABABC 返回1 BABA
解决方法: 1 bf暴力解决
2 kmp 算法
1 bf暴力解决
//暴力解决字符串模式匹配
#include<bits/stdc++.h>
using namespace std;
int main()
{
int i=0,j=0;
string s1,s2;
cin>>s1>>s2;
while(i<=s1.length())
{
if(j==s2.length())
{
cout<<i-j<<endl;
break;
}
if(s1[i]==s2[j])
i++,j++;
else {
i=i-j+1;
j=0;
}
}
if(j!=s2.length())
cout<<"not found!\n";
return 0;
}
kmp算法 :kmp算法的好处在于主串的i指针一直在往前进行,没有倒回去的过程;
下面举例讲解:求解+求nextval 数组
eg:下标从0开始
a: abcabcabd a: abcabcabd
b: abcabd 首先进行匹配 b[0],b[1]……b[4]成功匹配 b: abcabd b[5]没有成功匹配 这里怎么办呢?
引出最长公共前后缀(用pre表示),比如,pre(aba)=1 为a;pre(ababab)=4 为abab;
最长公共前后缀表示前面从 b[0]开始的一个子串 与 以b[end]j结尾的子串 最长的字符数
pre(abcab)=2 那么就将j指针指向pre[j-1] (pre[j]表示从下标为0开始以j结尾的子串的最长公共前后缀),i指针不变,也就是 将j=5->j=2 即 a: abcabcabd 然后进行匹配a: abcabcabd
b: abcabd b: abcabd
Q;那你是否会有疑惑,为什么b串前面的ab不用和前面进行匹配了呢,你一定确保相同吗?
answer:是的,一定相同,为什么呢?
当到 j指针停下的位置(即上例中的d的位置)时,0~ j-1都与 主串中的相同,pre(abcab)=2表示前面有两个字符和最后面两个字符一样,后面的两个字符与主串匹配,那么前面pre个字符也与主串匹配,那就可以放心的移动了
Q:为什么要最长公共前后缀呢?不是最长的可以吗?
answer:不可以。不是最长则可能导致原先能匹配的那部分不能匹配了
a: abaabac……
b: abaabad eg:pre(abaaba)=3 如果把它当成1来算会导致错误匹配
原先应该是这样的 a: abaabac…… 但当成1之后是这样的 a: abaabac……
b: abaabad b: abaabad
这一步求解代码如下:
讲解:分为两种情况 1.依次比较成功匹配 2.未成功匹配则移动子串j=pre[j-1](注意对j=0特殊讨论)
int solve()
{
while(i<a.length())
{
if(a[i]==b[j])
i++,j++;
else if(j>0)
j=nextval[j-1];
else i++;
}
if(j==b.length())
return i-j;
return -1;
}
求naxtval数组(递归):
直接拿例子来说 ABACABAB
第一种情况:
![]()
求此处B的pre,看前面的A,到A的pre为1,也就是前面有一个字符与到A的最后面的1个字符相同,求此处B的pre时可以充分利用起前面的pre,若b[j]与下标为pre[j-1]相同的话即(b[j]==b[pre[j-1]]),则b[j]=前一个的pre+1
第二种情况:
求此处B的pre,看前面的A,到A的pre为3,也就是前面有一个字符与到A的最后面的3个字符相同,求此处B的pre时可以充分利用起前面的pre,但发现此处下标为pre[j-i]的C不与此处B相等,这时说明没有那么长的公共子前缀了,即此处B前面的ABA不能全是公共子前缀,那我们可以知道此处B的公共子前缀比3小ABACABAB,也就是,到B的公共子前缀应该是从标红的ABA中找到的与B连续的一部分,比如:下划线的AB,求这连续的一部分不是就和求标红的ABA的公共子前缀一样嘛,标红的和标黄的也一样,而标黄的公共子前缀我们已经求出来了,也就是让pre=nextval[pre-1]再进行判断
加上代码更好理解:
void deal_next()//递归 其实不是很好理解
{
//分为两种情况 1 同则+1
//2异则查看更小的公共前缀
int i=1,pre=0;
while(i<b.length())
{
if(b[i]==b[pre])
{
pre++;//pre每次保留上一次的值
nextval[i]=pre;
i++;
}
else if(pre>0)
{//考虑 前面的字母的 公共前后缀的最长公共前后缀
pre=nextval[pre-1];//!!!!!!!!!
}
else
{
nextval[i]=pre;
i++;
}
}
}
全部代码如下:
//kmp算法 字符串匹配问题
//O(n)
#include<bits/stdc++.h>
using namespace std;
//先考虑一般情况 在考虑特殊情况(下标为0)
const int N=1e6;
string a,b;
int i=0,j=0;
int nextval[N];
void deal_next()//递归 其实不是很好理解
{
//分为两种情况 1 同则+1
//2异则查看更小的公共前缀
int i=1,pre=0;
while(i<b.length())
{
if(b[i]==b[pre])
{
pre++;//pre每次保留上一次的值
nextval[i]=pre;
i++;
}
else if(pre>0)
{//考虑 前面的字母的 公共前后缀的最长公共前后缀
pre=nextval[pre-1];//!!!!!!!!!
}
else
{
nextval[i]=pre;
i++;
}
}
}
int solve()
{
while(i<a.length())
{
if(a[i]==b[j])
i++,j++;
else if(j>0)
j=nextval[j-1];
else i++;
}
if(j==b.length())
return i-j;
return -1;
}
int main()
{
cin>>a>>b;//b是子串
deal_next();
if(solve()<0)
cout<<"not found!\n";
else cout<<solve()<<endl;
return 0;
}
请大佬指教!
最浅显易懂的 KMP 算法讲解 <<吸收于此视频!