1、理解KMP算法:
首先注意以下一个事实:
当模式串在某一个位置失配时,【该位置之前的所有字符】已经是和主串完全匹配的,不然根本不会能够到达现在失配的这个位置。
如下图,当模式串与主串进行匹配,在比较‘a’与‘b’时,发现'a' != 'b'。但既然能到达比较‘a’这一步,就说明模式串的字符 ‘a’之前的字符一定是与主串完全匹配的。这时候如果我们已经提前知道【串1】与【串2】是相等的,那我们是不是可以直接把模式串往右推,推到模式串的【串1】与主串的【串2】重合的位置,这样模式串一下子飞快地来到这一步,这个推的距离就是我们说的next值。所以next数组其实就是考虑【模式串在每一个位置失配的情况下】能滑动的距离,next数组是完全与主串无关的。而滑动多远是看【模式串失配点之前字符串】的特点的,比如例子中【‘a’ 之前的字符串】的特点就是它的【最前面的一些字符组成的字符串“串1”】与【最后面的一些字符串组成的“串2”】是相等的,因而直接用“串1”代替“串2”再进行比较。
2、求next数组:
我们手动地求next数组其实就是直接看模式串【失配点之前的字符串】的最前面与最后面相等的字符串到底可以有多长。但在程序上实现时求next数组其实也用到KMP算法的思想。
首先,第一个位置的前面没有字符串,即【第一个位置之前的字符串】长度为0,所以第一个位置的next值不可能超过0(因为【某个位置之前的字符串--str】的最长前缀串的长度必须得是不能超过str的长度的),故令它为-1即可。同理,可以直接令next[1] = 0;
下面是一般情况(比如我们要求下图绿色位置的next值):
最理想的情况是(下图第一种情况):有str[i] == str[j],这时i,j都往右移动(此时i指向绿色位置),则有next[i] = j,因为其实匹配的【最长前缀串“串1”】与【最长后缀串“串2”】都只是简单的增加多了一个字符。
但很多情况不会是第一种情况那么完美(下图第二种情况):这时有str[i] != str[j](即图中'b' != 'a') ,但是我们可以想办法转成第一种情况。这时我们退而求其次,利用“串11” == “串12” == “串21” == “串22”,可以知道,“串11”与“串22”是相等的。虽然“串12”后面那个字符是'a'而不是'b',但是说不定“串11”后面那个字符恰巧就是'b'呢,所以把j移动到“串11”的下一个位置,这样又可以下一次比较了,直到出现情况1时【绿色为置的next值】才真正被求出来。而这个j的移动方法,其实就是令j = next[j].
3、代码实现
//#include "pch.h" //VS环境下需要这句
#include <iostream>
#include <string>
using namespace std;
//主串类,每次接收匹配串时都要先求匹配串的next数组
class MyString {
private:
//主串:
string mainStr;
//获取模式串的next数组的函数:
int* getNext(string patStr) {
//next数组:
int* next;
int i, j;
//申请内存
next = new int[patStr.length()];
//初始化
i = 0, j = -1, next[0] = -1;
//开始求除了第一个位置之外的next值,i指向想要求next的位置
while (i < patStr.length()-1) {
//图中情况1:(包含着j退回到-1的情况)
if (j == -1 || patStr[i] == patStr[j]) {
i++;
j++;
//一定注意,是在i++,j++之后才有以下这句:
next[i] = j;
}
//图中情况2:
else {
j = next[j];
}
}
//返回结果:
return next;
}
public:
MyString(string mainStrM) {
mainStr = mainStrM;
}
int findByKMP(string patStr) {
int* next;
int i, j;
int result;
//成功获取所有next
next = getNext(patStr);
//输出next:
cout << "模式串的next: ";
for (i = 0; i < patStr.length(); i++) {
cout << next[i] << " ";
}
cout << endl;
//以下是通过next进行查找的代码
//i指向主串,j指向模式串
i = 0, j = 0;
while (true) {
//模式串的长度已经不足以出现匹配的情况,直接结束
if (i - j + patStr.length() > mainStr.length()) {
result = -1;
break;
}
else {
//当前字符能够匹配,或者j已经退回-1了,i,j都要往右走
if (j == -1 || mainStr[i] == patStr[j]) {
i++, j++;
//检查匹配的字符是否是模式串的最后一个字符,如果是,匹配成功
if (j == patStr.length()) {
result = i - patStr.length() + 1;
break;
}
}
//不能匹配时,i不动,模式串往右滑动,即j更新为next[j]
else {
j = next[j];
}
}
}
//回收内存:
delete[] next;
return result;
}
};
int main() {
//测试次数
int times;
//主串与模式串
string mainStr, patStr;
//查找结果
int result;
cout << "输入测试次数:";
cin >> times;
while (times--) {
cout << "输入主串与模式串:";
cin >> mainStr >> patStr;
//构造主串对象
MyString ms(mainStr);
cout << "主串:" << mainStr << " 模式串:" << patStr << endl;
//进行查找:
result = ms.findByKMP(patStr);
//输出结果:
cout << "查找结果: "<< result << endl << endl << endl;
}
return 0;
}