前言
这是本人第一次写博客,整体瑕疵很多很多,写的不是特别清楚,可能只有自己能看懂吧哈哈哈,以后会注意的。
一、KMP算法原理
问题描述: 已知字符串S和模式串P,若S中包含P,则返回起始位置的下标,反之返回-1.
例如: S = ”abcabcde"; P = "abcd";
则结果返回 3.
下面是分析过程
顺序是从左向右开始,i为S下标,J为P的下标,前三步就正常一个一个匹配,如果字符相同则i,j都向后移动一个单位,关键在于第四步两个字符不相同时,我们把P向右移动了3个单位,i指针不动 ,j变成了0,然后重新开始匹配。后面也一样,如果字符不相同那就继续移动P然后再开始匹配......最后匹配成功返回下标 i-j+1(这个动手算一下可以算出来)
这样S只遍历了一次,时间复杂度大大减少。我原理可能表达的不清楚,总之KMP算法的关键就是当字符串不匹配时P应该移动多少个位置。这就涉及字符串的前缀和后缀了。
例如 “abcda” 的前缀为 "a","ab","abc","abcd" 不包含其本身
后缀为 "a","da","cda","bcda" 不包含其本身
我们可以找到其中前缀和后缀中相同且最长的字串为“a", 其长度为1。这个1就是我们需要的数。然后我们继续看上图的第四步,此时s[I] != p[j], 那么j之前的字符串为“abc",前缀和后缀中相同且最长的字串不存在,长度为0,j就变成了0。也就是只要知道j之前的字符串的最大相同前缀后缀的长度就行了。我们把该值放在一个数组pmt里面,下面是求该值的代码:
//把字符串向右移动一个单位后与本身开始匹配
int pmt[1000];
void get_pmt(const string& p)
{
pmt[0] = 0; // 第一个字符的最长相同前缀后缀为0
for(int i = 1, j = 0; i<p.length(); i++)
{
while(j && p[i] != p[j]) j = pmt[j-1]; //如果两个字符不相同,移动字符串改变j的值
if( p[i] == p[j]) //如果相同,则得到该位置pmt[i]的值,继续向后比较
{
j++;
pmt[i] = j;
}
}
}
得到数组后我们就可以用这个数组去解题了
二、整体代码
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int pmt[1000];
void get_pmt(const string& p)
{
pmt[0] = 0; // 第一个字符的最长相同前缀后缀为0
for(int i = 1, j = 0; i<p.length(); i++)
{
while(j && p[i] != p[j]) j = pmt[j-1]; //如果不相同,移动p,这里如果j=0并且两个字符还不相同,也就默认pmt[i] = 0了
if( p[i] == p[j]) //如果相同,则得到该位置pmt[i]的值,继续向后比较
{
j++;
pmt[i] = j;
}
}
}
int kmp(const string &s,const string &p)
{
for(int i = 0, j = 0; i < s.length(); i++)
{
while(j && s[i] != p[j]) j = pmt[j-1];
if(s[i] == p[j])
{
j++; // 两者相等,继续匹配
}
if(j==p.length())
{
return i-j+1;//匹配成功,返回下标
}
}
return -1;// 未匹配成功,返回-1
}
int main()
{
string s;
string p;
getline(cin,s); //读入s
getline(cin,p); //读入 p
get_pmt(p); //获取 pmt数组
cout<<kmp(s,p)<<endl;
return 0;
}
总结
原理容易理解,关键就是求pmt数组,自己太菜了弄了一个晚上才理解。