写在前面 KMP真的好难懂 😭 首先默认暴力求解咱都会,现在来水水KMP的这么一个步骤。
首先,对字符串的匹配来说,BF求解时间复杂度太高,所以有三位大牛弄出了KMP算法。以下通过我的理解来说一说KMP。
假设主串的长度为M,子串的长度为N,现在要达到的目的是在主串M中找到N所在的对应的子串下标,如果没有就返回。
KMP的核心是是针对子串,防止重复回溯,提高效率,因此对应子串中有一个Next数组,数组存放的元素为,当前下标j处子串失配时,下次从子串的next[j]下标处,开始重新匹配。
这是为啥呢,举个栗子。
假设子串T为ACACA,主串S为BACACBACACA
第一步,子串开始移动,在T[0]!=S[0],那么子串往下走,比较T[0]和S[1],相等,接着走发现,直到T[5]的时候才与S[6]失配,那么这一次,需要再从头开始匹配,让T[0]与S[2]作比较么?显然不是肉眼来看,应该将字符串右移两位,将T[2]与S[3]作对比,提高了效率。
那么怎么找到上述例子的T[2]与S[3]呢?可以发现的是,之所以上面可以将子串移动2个位置,再进行匹配,是因为字符串的前缀中,有重复的两个元素,由此可知,当子串匹配到失配的元素时,如果前面有前缀后缀相同的元素,那么就应该去到最相同的元素的末端,接着和主串中失配的元素相匹配。那么如何寻找子串中相同前缀后缀,如何描述呢,以此,就引出了next数组。
next数组的X下标对应的元素就是子串的元素X之前,前缀后缀字符串相等的最大长度。(前缀指的是字符前面的不包括最后一个字符的所有字符串,后缀反之。)
ACACA这个子串的next数组,可以通过数得到。由于字符串下标从0开始,所以next[0]被赋值为-1;当子串下标为1时,此时’C‘的前缀后缀没有相等元素,所以为0;子串下标为2时,’A‘的前缀后缀没有相等元素,所以为0;子串下标为3时,’C‘的前缀后缀分别为“A,A”,“AC,CA”,所以值为1,子串下标为4时,’A‘的前缀后缀分别为“A,C”,“AC,AC”,“ACA,CAC”,所以值为2,那么next数组的值为 -1,0,0,1,2。
那么对于Next数组的求解过程中,重要的是前缀所在的位置,因为前缀是相对固定的,而后缀则是一直在移动。
先贴代码,明天解释和排版,今天先sleep🎠
int* KMP_next(string T,int *next)
{
int next1[255] = { 0 };
int i = -1;//前缀
int j = 0;//后缀
next[0] = -1;
while (j<T.length()-1)
{
if (i==-1||T[i] == T[j])
{
i++;
j++;
//next1[j] = i;
if (T[i] == T[j])
{
next[j] =next[ i];
}
else
{
next[j] = i;
}
}
else
{
i = next[i];
}
}
return next;
}
int KMP(string S,string TT)
{
int next[255] = {0};
KMP_next(TT,next);
int pos = 0;
int T_pos =0;
int slen = S.length();
int tlen = TT.length();
while ( pos< slen && T_pos< tlen)
{
if (T_pos==-1||S[pos]== TT[T_pos])
{
pos++;
T_pos++;
}
else
{
//回溯
T_pos = next[T_pos];
}
}
if (T_pos == TT.length())//找到了子串的末尾 但是还没找到相同的串
{
cout << "在字符串的" << pos - TT.length() << "位置处找到了" << endl;
return pos - TT.length();
}
else
{
cout << "没有匹配的字符串" << endl;
return -1;
}
}
void test15()
{
cout << "请输入主串" << endl;
string S;
getline(cin,S);
cout << "请输入目标串" << endl;
string S1;
getline(cin, S1);
KMP(S, S1);
}
int main()
{
cout << "正在测试我的代码。。" << endl;
srand((unsigned int)time(NULL));
test15();
system("pause");
}