这一篇用来记录KMP算法(看过一篇帖子说这是”看毛片”算法,邪恶了,呵呵)
一、首先我们要掌握BF(Brute-Force)算法,基本思想总结:
主串 s = a b a c a b a b i = 3
模式串t = a b a b a j = 3
i=3, j=3失配时,j要返回为0,i则要从上次开始处往后移一个位置开始重新匹配。
算法思想很容易理解,以下是代码实现:
int BF(char *s, char *t, int start) { int i = start, j = 0, k; while(s[i] != '\0' && t[j] != '\0') { if(s[i] == t[j]) { i++; j++; } else { i = i - j + 1; j = 0; } } if(t[j] == '\0') k = i-j; else k = -1; return k; }
二、我目前对于KMP的理解程度不深,但是还是可以表述出来(^-^),大神莫喷。。。首先我们看上面那个例子:
主串 s = a b a c a b a b i = 3
模式串 t = a b a b a j = 3
当不匹配的时候,记住两点:
(1)主串不用返回,仍然从这个位置开始下次匹配;
(2)模式串需要返回,返回的位置由自身的信息决定;
1、先牢牢记住这两点,现在我们来理解为什么是会这样,先深入理解第一条:
首先我们来看一个简单的例子:
s = c d dc d c
t = c d c
当i=2,j=2失配时,按照BF算法我们应该比较s1和t0,但是t0 != t1,s1 = t1,所以s1 != t0,那么我们比较s1和t0还有什么意义呢?所以让我们直接比较s2和t0吧!
再看上面的那个例子:
主串s = a b a ca b a b i = 3
模式串t = a b a b j = 3
当i=3,j=3失配时,按照BF算法我们应该比较s1和t0,但是t0 != t1,s1 =t1,所以s1 != t0,
我们就会去比较s2和t0,但是t0=t2,s2=t2,所以s2=t0,那么我们就可以直接比较s3和t1了。
上面两种情况得出我们可以看出,s串的下标规律了:
主串不用返回,仍然从这个位置开始下次匹配;
2、从上面的分析中,我们看出了s串移动的规律,那么t串移动的规律呢?
对于第一种情况,回到了t0,第二种情况却回到了t1,这其中有什么规律吗?当然,这就是KMP的核心所在:
根据模式串自身的信息来决定它的回溯方式(可能专业的说法不是这样^-^,这是我自己的理解)
为此我们引入两个很关键的概念:
(1)前缀,后缀
(2)数组next[j]
前缀和后缀不用多说吧,如s = 12345,1234是前缀,2345是后缀。
Next是我们自己取名的,它就记录了我们一直所说的模式串的自身信息,next[j]和我们所说的前缀和后缀的关系是:
在模式串"t0t1...tj-1"中,其中一个以t0为首字母,另一个以tj-1为末字符,满足t0t1...tk-1=tj-ktj-k+1tj-1,且这样的相等字串是所有这种相等字串中最长的,则这个长度就是我们上面的next[j]的取值,我们规定,这样的字符串的长度小于模式串本身,所以0<k<j。
同时我们来对next做一个总结:
Max{k|0<k<j且t0t1...tk-1=tj-ktj-k+1tj-1}(坑爹连大括号都打不出来)
Next[j]= 0, 其他情况
-1, 当j = 0的时候
(有的人会问,为什么next就长成这样呢?同学,你想多了,这就是它的定义,KMP对next还有其它的定义,这还要考虑效率等方面的因素,但是我觉得这种比较好理解)
有了定义,我们来看一个例子:
j | 0 | 1 | 2 | 3 | 4 |
t[j] | a | b | a | b | a |
next[j] | -1 | 0 | 0 | 1 | 2 |
3、我们对模式串引入了两个概念,但是还没有深入探讨next[j]和模式串回溯位数的具体关系,下面我们深入看看:
(1)当next[j]= j-1时,有t0...tj-2=t1...tj-1,而si != tj失配时有si-j...si-1=t0...tj-1,所以t0...tj-2涵盖在si-j...si-1中了,下次我们直接比较si和tj-1吧!看看这个下标j-1是不是就是我们next[j]的值呢?
(2)当next[j] =j-2时,有t0...tj-3=t2...tj-1,失配时si-j...si-1=t0...tj-1,所以t0...tj-3仍然涵盖在si-j...si-1中,我们直接比较si和tj-2吧!看看这个下标j-1是不是就是我们next[j]的值呢?
(3)当next[j] = 1时,有t0=tj-1,失配时有si-j...si-1=t0...tj-1,所以t0已经等于si-1,所以我们直接比较si和t1吧。
再配上一个图:
a b a b ca b a b a i = 4
a b a b a j = 4
当j = 4时,next[j]= 2,下次从j = 2开始匹配
a b a b ca b a b a
a b a b a
s中si前的字符串涵盖了t0和t1这样的信息,直接比较si和t[next[j]]吧!
综上所述,我们对next[j]的作用深刻理解了一下
下面我们来看KMP算法的实现:
int KMP(char *s, int start, char *t, int next[]) { int i = start, j = 0, k; while(s[i] != '\0' && t[j] != '\0') { if(s[i] == t[j]) { i++; j++; } else if(j == 0) i++; else j = next[j]; } if(t[j] == '\0') k = i-j; else k = -1; return v; }
那么所谓的next应该怎么来求呢?
next的求解和KMP的思路是一样的,我就直接贴上代码吧:
void GetNext(char *s, int next[]) { int j = 1, k = 0; next[0] = -1; next[1] = 0; while(s[j] != '\0') { if(s[j] == s[k]) { next[j+1] = k+1; k++; j++; } else if(k == 0) { next[j+1] = 0; j++;} else k = next[k]; } }
加上测试函数,整个KMP的算法如下:
#include <stdio.h> void GetNext(char *s, int next[]) { int j = 1, k = 0; next[0] = -1; next[1] = 0; while(s[j] != '\0') { if(s[j] == s[k]) { next[j+1] = k+1; k++; j++; } else if(k == 0) { next[j+1] = 0; j++;} else k = next[k]; } } int KMPIndex(char *s, int start, char *t, int next[]) { int i = start, j = 0, k; while(s[i] != '\0' && t[j] != '\0') { if(s[i] == t[j]) { i++; j++; } else if(j == 0) i++; else j = next[j]; } if(t[j] == '\0') k = i-j; else k = -1; return k; } int main() { char *s = "121231456"; char *t = "1231"; int next[5] ={0}; GetNext(t, next); int m = KMPIndex(s, 0, t, next); printf("the answer is:%d\n",m); return 0; }写完了,四个小时!!!累P了,csdn的这个编辑器能不能改改啊,各种不方便