本蒟蒻经过一下午的挣扎终于能略微窥探到KMP算法的精妙,也是弥补了以前学数据结构的遗憾,这里记录一下蒟蒻自己理解的KMP算法,博客简略,主要是大概过一下,让自己以后能回忆起来,具体参考《大话数据结构》。
KMP的精妙之处就在于它的next数组,next[i]保存的是第\(i\)个数之前的字符串前后缀相似度,也可以理解为上一个可匹配的前缀字符串下一个字符。求next数组的算法核心就在于\(j\)的回溯,他可以回溯到之前的最长可匹配前缀字串,因为这样后缀字串就为对应的最长可匹配后缀字串,至于为什么手动模拟一遍\("ababcabababc"\)就知道了(滑稽)。
上代码:
/*求子串T的next数组*/
void get_next(string T, int *next)
{
int i = 1, j = 0;
next[1] = 0;//守字母next赋值0
while (i < T[0])//T[0]储存子串T长度
{
if (j == 0 || T[i] == T[j])
{
i++;
j++;
next[i] = j;
}
else
j = next[j];//字符不相同,回溯
}
}
求出了next数组,接下来的KMP就比较好理解了,只回溯\(j\),不回溯\(i\)
int index_KMP(string S, string T, int pos)//返回匹配到的下标
{
int i = pos;//pos为S主串开始匹配的位置
int j = 1;
int next[255];
get_next(T, next);
while (i <= S[0] && j <= T[0])//i,j小于两个字符串的大小
{
if (j == 0 || S[i] == T[j])//和传统匹配算法相比增加特判
{
i++;
j++;
}
else
j = next[j];
}
if (j > T[0])
return i - T[0];//返回S中匹配字符串的开头位置
else
return 0;//匹配失败
}
但是next数组还是有缺陷,比如主串为\("aaabcde"\),子串为\("aaaaax"\),当\(i=5,j=5\)的时候\(a,b\)不相等,那么\(j\)会从5,4,3一步一步回溯到1,造成时间的浪费,所以下面的nextval是一种提前判断回溯点,如果回溯点和原来的一样要回溯就直接回溯到最终位置了。
/*求子串T的nextval数组*/
void get_nextval(string T, int *nextval)
{
int i = 1, j = 0;
nextval[1] = 0;//守字母next赋值0
while (i < T[0])//T[0]储存子串T长度
{
if (j == 0 || T[i] == T[j])
{
i++;
j++;
if (T[j] != T[j])//当前字符的前缀字符不同
nextval[i] = j;//正常赋值
else//当前字符前缀字符相同,将前缀字符的nextval值赋给在i上的值
nextval[i] = nextval[j];
}
else
j = nextval[j];//字符不相同,回溯
}
}