KMP算法
KMP算法是现阶段我接触到最难理解的算法,作为暴力匹配算法的改进版,特别是其中涉及到的回溯,很难懂,本篇博客以我的理解记录KMP算法
一、KMP算法是BF算法的改进,其改进思路为:当模式串与目标串失配时,不回溯到目标串的+1位置,具体怎么做下面说
举个实例:有目标串“ABCDABCDABDE”和模式串“ABCDABD”进行匹配,当模式串”ABCDAB“和目标串的“ABCDAB”匹配完成,但'C'不等于’D‘
此时第一个字符匹配失败,按照BF算法,应该将模式串移到目标串的第二个位置进行比较,但是这次肯定是失败的,因为第二个位置在之前比较时已经
知道它和模式串中’B‘匹配,而’A‘不等于’B‘ 所以一定失败,这就是有用信息!
而如何知道’A‘不等于’B‘则是我们要求的
二、Next数组,前缀后缀和最长公共元素
如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:
可以看到,一个字符串前缀就是以第一个字符开头,不断往后”拉“的一个集合
后缀就是以最后一个字符结尾,不断往前”伸“的一个集合(用语言实在难以表述,不过对于Next数组理解这很重要)
最大公共长度就是两个集合公共元素的长度。
对于上表有
对于该表的一点解释:字母下面的数字表示一个字符串(第一个字母到该字母之间的所有字母)的最大公共长度
如B下标为2,表示字符串”ABCDAB“的公共元素”AB“长度为2
这个便是我理解的next数组。
可以看出,next数组不会突然增加,这是由next数组的性质决定的,且next[i]可以由next[i-1]推出
还是以字符串s:”ABCDABD“为例,设置一个变量k用来跟踪next的值,现假设B的最大公共元素未知,即next[5]不知道,即
而我们知道此时k=1,此时next[5]的值要根据s[5]的值来决定
1.s[k]=s[5],即”AB“="AB",此时字符串”ABCDAB“最大公共长度为2,即next[5]=next[4]+1 同时k应该+1
此时next数组为[0,0,0,1,2,?]
这是匹配的情况,对于不匹配的情况怎么办呢?下一个字符'D'就是这种情况(此时k为3)
2.s[k]!=s[6],即”C“和”D“失配了,那么此时”ABCDABD“有没有最大公共长度呢?
如果要有,则必须要满足一个条件:在k前面必须要有”D“字符!!!即”xxDxxxD“才有可能出现公共前后缀”xxD“
即现在要做的:找到k'使得s[k']='D'即可,k’=next[k-1](核心!!!!!!)
要理解next[k-1]是什么,next[k-1]是s[k-1]的最大前缀!!
即s[k]!=s[6]的时候,应该在k-1的最大前缀里面找有没有字符‘D’
此时不必担心s[k']到s[k]之间存在‘D’ 就算存在也不是我们要找的字符‘D’
(这里并没有搞懂,但是我构建了许多字符串都找不到符合要求的字符串,构建过程中我感觉这和前后缀的性质有关系,但是涉及的知识太深了无法进一步求解,这可能需要很高的数学造诣才能弄懂)
这就是我理解的KMP算法,对于其中核心并没有弄太懂,但是其构造方法和核心递归k=next[k-1]弄懂了一点,也算是有了收获
完整代码如下:
1 public static int kmpSearch(string str1, string str2,int[] next) 2 { 3 //遍历 4 for(int i=0,j=0;i<str1.Length;i++) 5 { 6 //处理str1[i]!=str2[j],调整j大小 7 while(j>0&& str1[i]!= str2[j]) 8 { 9 j = next[j - 1]; 10 } 11 if (str1[i]==str2[j]) 12 { 13 j++; 14 } 15 if(j==str2.Length) 16 { 17 return i - j + 1; 18 } 19 } 20 return -1; 21 } 22 //获取一个字符串的部分匹配值 23 public static int[] kmpNext(string dest) 24 { 25 //创建一个数组保存部分匹配值 26 int[] Next = new int[dest.Length]; 27 //dest的第一个字符串部分匹配值为0 28 Next[0] = 0; 29 for (int i = 1, j = 0; i < dest.Length; i++) 30 { 31 //当dest[i] != dest[j]时 32 while (j > 0 && dest[i] != dest[j]) 33 { 34 j = Next[j - 1]; 35 } 36 //该条件满足 部分匹配值就+1 37 if (dest[i] == dest[j]) 38 { 39 j++; 40 } 41 Next[i] = j; 42 } 43 return Next; 44 }