背景&暴力搜索法略
看了好多文章和视频和书,这个视频属于帮我捅破一层窗户纸的那个。https://www.bilibili.com/video/av11866460
输入:主字符串s,模式字符串p。
输出:在s中,p第一次出现的位置或-1。
最长公共前后缀序列
这个概念有点拗口,拆开来看:
- 对于模式字符串p,如果存在一个真子串str,str既是p的前缀,又是p的后缀,则str是p的公共前后缀。如:aacdaa的公共前后缀既可以是a,也可以是aa;注意aa的公共前后缀只有a而不包括aa。
- p的所有公共前后缀中,最长的那个子串就叫最长公共前后缀。比如:aacdaa的最长公共前后缀就是aa,而abcdhjhjabcd的公共前后缀只有abcd,所以最长公共前后缀也只有abcd。
- 对于p的每个元素p[i],令v[i]表示p[0]~p[i-1]的最长公共前后缀的长度,令v[0]为-1;则能得到最长公共前后缀序列。比如ababc的最长公共前后缀序列c就是[-1,0,0,1,2]。显然v[1]必然是0。
代码如下:
vector<int> common(string s) {
vector<int> v;
v.push_back(-1);
for (int i = 1;i < s.size();i++) {
for (int j = i - 1;j >= 0;j--) {
if (s.substr(0, j) == s.substr(i - j, j)) {
v.push_back(j);
break;
}
else if (j == 0)v.push_back(0);
}
}
return v;
}
注:《算法第四版》中把这一过程抽象成一个状态机,这也是可以理解的
移动
移动的过程就很容易了,主串t和模式串p从i=0开始扫,遇到不匹配的就将模式串的指针调整至最长公共前后缀序列的索引i处——next[i]。直至最后。
int kmp(string pattern, string target) {
vector<int> next = common(pattern);
int posP = 0, posT = 0;
int lengthP = pattern.size();
int lengthT = target.size();
while (posP < lengthP && posT < lengthT) { //对两串扫描
if (posP == -1 || pattern[posP] == target[posT]) { //对应字符匹配
posP++;
posT++;
}
else posP = next[posP]; //失配时,用next数组值选择下一次匹配的位置
}
if (posP < lengthP) return -1; //匹配失败
else return posT - lengthP; //匹配成功
}
测试shell
int main()
{
string pattern = "ababc";
string target = "duababeababc";
cout << kmp(pattern, target);
return 0;
}