本文不介绍KMP具体思想: 只用代码注释自己的实现思路
next数组的核心思想在于, 当主串字符与子串字符失配时
判断主串指针字符前有多少字符 是 可以跳过的.
///
假设主串为ad abcad abcab 模式串为 abcab, 字符指针范围为[0, str.Length - 1]
当主串与模式串匹配时 主串中abcad部分的末尾d 与 模式串abcab末尾的b不符
此时根据模式串b的前一个字符a的next可得知 abca 的最长公共前后缀 为 a = a 即1
也就说明 abcad 中 有1个字符a 即b左侧字符 是可以跳过匹配的.
其主要原因在于: abcad 与 abcab 匹配进行到了 d 与 b, 前四个字符 abca 由于是判断相同的, 故模式串可跳跃最长公共前(后) 缀重新匹配.
///
此时模式串的指针 从abcab的末尾b跳回夹中b 进行重新匹配(4->1(next[4 - 1]))
///
同理 由于夹中b仍与主串字符d不符 则根据夹中b前一个字符a的next进行指针跳回
a的next = 0 故lesser = (1->0(next[1 - 1])) 再重新匹配
///
假设子串第一个字符仍与主串不符 则主串指针跳跃+1
/// </summary>
/// <param name="haystack">参与匹配的主串</param>
/// <param name="needle">需要匹配的模式串</param>
/// <returns> </returns>
public static int KMP_(string haystack, string needle)
{
// 最长公共前后缀数组
int[] next = GetKMP_Next(needle);
// 用于指向主串匹配位置的指针
int main = 0;
// 用于指向模式串匹配位置的指针
int lesser = 0;
/*
理论上 当lesser = str.Length - 1时 匹配成功 应该退出返回起始坐标
反之返回 -1 表示未匹配成功
*/
// 当子串完全匹配或主串遍历完毕后跳出循环
while(main < haystack.Length && lesser < needle.Length)
{
// 当字符匹配相同时 同时跳跃指针
if (haystack[main] == needle[lesser])
{
main++;
lesser++;
}
else
{
// 字符失配 且 子串指针指向第一位时 则主串跳跃
if (lesser == 0) main++;
else
{
// lesser指向的是当前匹配的子串字符 但要根据该字符的前一个字符的next进行重新指向
lesser = next[lesser - 1];
}
}
}
if (lesser == needle.Length)
return main - needle.Length;
else return -1;
}
/// <summary>
/// KMP的最大难点在于如何寻找 模式串的 最长公共前后缀
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static int[] GetKMP_Next(string str)
{
int[] next = new int[str.Length];
// 前缀指针
int main = 0;
// 后缀指针
int lesser = 1;
/*
* main < lesser <= str.Length - 1
* main 始终小于 lesser
*/
next[main] = 0; // 第一个字符的最长公共前后缀必定为0
while (lesser < str.Length)
{
// 当两个字符相同时
// lesser 指向当前匹配字符
if (str[main] == str[lesser])
{
next[lesser++] = ++main;
// 为什么要加++main 因为在本代码里 next表示的是可跳跃字符的长度 此轮比较已经成功
// 需要注意的是 指针是从0开始的 所以跳跃一个字符长度 本质上是将指针跳跃到1
}
else if(main == 0)
{
// 当 main == 0 且 字符失配时
next[lesser++] = main;
}
else
{
/* 当字符不相同时 要跳回指针
* 跳回位置原理与KMP_相同 都是因为有最长公共前后缀
* 假设 str 为 bbccdabbd 其 next 应该为 010000120
* 理论上 当l指向最后一个字符d时 main应该等于2 即指向c
* 由于d!=c 所以m应该根据 next[main-1] 即1 跳回至1
* 在跳回前会发现 bbc与bbd的前两个字符是相同的 这也侧面说明原理与KMP_思想一样
*/
main = next[--main];
}
}
return next;
}