目录
一:简介
KMP算法是一种字符串匹配算法,可以在 O(n+m)
的时间复杂度内实现两个字符串的匹配
。
所谓字符串匹配,是这样一种问题:“字符串 P 是否为字符串 S 的子串?如果是,它出现在 S 的哪些位置?” 其中 S 称为主串;P称为模式串。
如果暴力就是O(n*m),两个for循环,ij同时动。
二:实现
1.暴力 O(n*m)
法一【stl】:调用substr
int match(string text, string pattern) {
int n = text.length(), m = pattern.length();
for (int k = 0; k < n - m + 1; k+=1) {
if (text.substr(k, m) == pattern) {
return k;
}
}
return -1;
}
法二【正常】:
//暴力匹配法
public static int violenceMatch(String s1,String s2){
char[] str1=s1.toCharArray();
char[] str2=s2.toCharArray();
int i=0;
int j=0;
while (i<str1.length && j<str2.length){
if(str1[i]==str2[j]){
i++;
j++;
}else {
//没匹配到就把i移动到刚开始和j匹配的后一位
i = i - j + 1;
j = 0;
}
}
if(j==str2.length){
return i-j;
}
return -1;
}
2.kmp O(n*m)
#include <bits/stdc++.h>
using namespace std;
#define MAXLEN 225
typedef struct
{
char ch[MAXLEN];
int length;
} String;
void get_next(String T,int next[])
{
int i=1,j=0; //i领先一个位置
next[1]=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j]) //如果j=0时,意味着当前i无法和模式串匹配,i需要向后移动
{
i++;
j++; //i++相当于next[j]赋值给next[j+1],j++相当于移动到可以直接匹配的位置
next[i]=j;
}
else
{
j=next[j]; //回退
}
}
}
int Index_KMP(String S,String T,int next[])
{
int i=1,j=1;
while(i<=S.length&&j<=T.length)
{
if(j==0||S.ch[i]==T.ch[j])
{
i++,j++;//匹配成功,i和j向后移动
}
else
{
j=next[j];//j回调可以直接匹配的位置
}
}
if(j>T.length) return i-T.length; //不需要+1,因为while1循环退出之前i++,用来while循环判断了
else return 0;
}
int main()
{
String s1,s2; //s1母串,s2子串
char a[MAXLEN],b[MAXLEN];
cout<<"输入母串:"<<endl; //aabaabaabaabaabaaaa
cin>>s1.ch+1;
s1.length=strlen(s1.ch+1);
cout<<"输入子串:"<<endl;//aabaabaaaa
cin>>s2.ch+1;
s2.length=strlen(s2.ch+1);
int next[MAXLEN];
get_next(s2,next);
cout<<"next数组如下"<<endl;
for(int i=1;i<=s2.length;i++)
{
cout<<next[i]<<" ";
}
cout<<endl;
printf("匹配位置:%d\n",Index_KMP(s1,s2,next));
return 0;
}
为什么cin>>a+1
char a[10];
cin >> a + 1;
什么意思呢,也就是说这是一个指针运算,a表示的是指针char*,由于>>操作优先级低于+,所以先进行+1运算,a+1表示的是&a[1]这个地址,所以说cin>>a+1也就是从地址&a[1]处开始写输入,相当于舍弃了a[0]这个值。果然a[0]处乱码,由于从a[1]开始写,于是只有a[1], a[2], a[3]保存着我们输入的有意义的字符。
而上文中next数组要求从next[1]开始,所以cin>>s1+1; 那么strlen中s1也要加1或者总length-1;
三:next数组的解释
- j是短字符串的指针,i是长字符串;
- next数组只看短字符串,next[j]=1到j-1的最长前后相等缀+1;
- 默认next[1]=0;next[2]=1;
如下图:j=4,j前面即j-1到1的子串为aba,最长前后相等缀为a即1,next[4]=1+1=2
j=6,j前面即j-1到1的子串为abaab,最长前后相等缀为ab即2,next[6]=2+1=3
四:优化
puls优化nextval[j]数组:
当已经出现过一个字母时,eg:a,则令nextval[3]=next[1]=0
eg:b,则令nextval[4]=next[2]=1
j指针就不用重复遍历已出现的a、b了
这里写的是直接改好的val数组
#include <bits/stdc++.h>
using namespace std;
#define MAXLEN 225
typedef struct
{
char ch[MAXLEN];
int length;
} String;
int Index_KMP(String S,String T,int next[])
{
int i=1,j=1;
while(i<=S.length&&j<=T.length)
{
if(j==0||S.ch[i]==T.ch[j])
{
i++,j++;//匹配成功,i和j向后移动
}
else
{
j=next[j];//j回调可以直接匹配的位置
}
}
if(j>T.length) return i-T.length; //不需要+1,因为while1循环退出之前i++,用来while循环判断了
else return 0;
}
void get_nextval(String T,int nextval[])
{
int i=1,j=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j])
{
i++,j++;
if(T.ch[i]!=T.ch[j])
{
nextval[i]=j; //如果不相等正常赋值
}
else
{
nextval[i]=nextval[j]; //相等时
}
}
else
{
j=nextval[j];
}
}
}
int main()
{
String s1,s2; //s1母串,s2子串
char a[MAXLEN],b[MAXLEN];
cout<<"输入母串:"<<endl; //aaabaaaab
cin>>s1.ch+1;
s1.length=strlen(s1.ch+1);
cout<<"输入子串:"<<endl;//aaaab
cin>>s2.ch+1;
s2.length=strlen(s2.ch+1);
int next[MAXLEN];
get_nextval(s2,next);
cout<<"nextval数组如下"<<endl;
for(int i=1; i<=s2.length; i++)
{
cout<<next[i]<<" ";
}
cout<<endl;
printf("匹配位置:%d\n",Index_KMP(s1,s2,next));
return 0;
}