(我们这里不啰嗦各种各样的理论,我们为的就是理解算法。)
所谓KMP算法,就是说现在给我们两个字符串,让我们来完成匹配(匹配==一一对应)
假设这里给我们两个字符串分别为:
abcxefabcd(S)
abcd(P)
这里显而易见,在S串的末尾部分就能和P串一一对应,也就是说能匹配上,用朴素的匹配算法,我们要进行多少步呢?我们需要7次匹配才能得到最终的结果。
abcxefabcd(S)->abcxefabcd(S)->abcxefabcd(S)->abcxefabcd(S)->.......
abcd(P) #abcd(P) ##abcd(P) ###abcd(P)
依次类推,我们最终能得到匹配:abcxefabcd(S)
##### abcd(P)
然而我们知道,在第一种匹配的时候:其中因为有s[0,1,2]==p[0,1,2],其实我们就知道,第二步,第三步都是可以省略掉的,直接进行:
就可以了。既然这样,我们就可以省略掉多余的步骤,然而KMP算法的实现,就是用来去掉这些个繁杂的步骤的。既然我们知道朴素匹配的方法是不断的移动P串来实现匹配尝试的,那么我们这里的KMP算法也延续朴素匹配算法的精髓,不动主串,动模式串。
我们这里知道,如果当前匹配过程中。(i为主串位子j为模式串位子)如果s[i]!=p[j],并且s[某位子为起点一直到i-1]==p[0到j-1].并且p[1]!=p[1,2,3,4...j-1]的情况下,我们这里的j就可以变成0重新用模式串的第一个字符和s[i]进行重新匹配尝试了。
但是如果其中有p[1]==p[1,2,3....j-1]的情况存在要怎么办呢?
如果是这种情况:
abcabcabx (S)
abcabx (p)
第一次匹配的情况是这样的:
其实正确的操作步骤应该是省略朴素匹配中的两步然后进行这样的匹配才对:
那么我们如何去判断到底P串要走到哪里呢?这时我们需要一个回溯数组next【】来判断P到底应该回溯到哪里。如果有了s[i]!=s[j]的时候,我们的i不变,j要变成next[j]即j=next[j].那么我们应该如何去确定next数组呢?我们这里先不研究其中的推导过,我直接教给大家如何去确定里边的值。我们知道了next数组是干什么的,还知道了如何去确定值,其实就足够了。
这里我们给出几个典型的字符串(当然是P串,模式串),来具体计算其中的值。
1.abcdex.
2.abcabx.
3.ababaaaba.
1.我们这里看的出来,字符a后边没有任何和他相同的字符。我们这里直接j=next[j]=0就行了。所以next数组中的值我们是这样确定的:-1 0 0 0 0 0
2.这个例子我详细说一下:(这里形成的子串我们规定是不包括p[i]自己的)
i=0 next[i]=-1;
i=1 形成的子串是a.next[i]=0;
i=2 形成的子串是ab.next[i]=0;
i=3形成的子串是abc next[i]=0;
i=4形成的子串是abca.这里我们先规定一个不成文的定理:现在的子串长度是4,前缀最大子串长度是3,后缀最长子串长度也是3.前缀子串的第一个字符是子串的第一个字符,后缀子串的最后一个字符是子串的最后一个字符,next[i]=前缀子穿==后缀子串的最大长度缀子串长度;所以这里前缀a==后最a==长度1.所以这里我们规定next[i]=1;
i=形成的子串是abcab,前缀ab==后缀ab==长度2 next[i]=2;
所以最终的值是这样确定的:-1 0 0 0 1 2;
作为测验,大家可以试一试最后一个稍微有点难度的串的值的确定,答案是这样的:-1 0 0 1 2 3 1 1 2.
我们这里确定了如何确定next数组中的值,那么我们需要用代码实现:
void set_naxt()//子串的next数组,如果只看代码的话,很难得出值的确定方法。 { int i=0,j=-1; next[0]=-1; while(i<lenb) { if(j==-1||b[i]==b[j]) { i++; j++; next[i]=j; } else j=next[j]; } }既然我们确定了next数组,我们也就知道了模式串在s[i]!=s[j]的时候j要如何变化了。所以这里我们也直接上kmp函数的代码:
int kmp() { int i=0,j=0; set_naxt(); while(i<lena) { if(j==-1||a[i]==b[j])//这里j=-1的情况就是第一个字符不匹配的情况。 { i++;j++; } else j=next[j];//如果匹配不上,这里回溯j, if(j==lenb)//如果匹配成功了。 return i-j+1;//回溯j最终的位子。 } return -1; }
感谢大家的支持,竟然把这么烂的讲解真的看到了最后一句话,感谢你的支持。