最近在学算法,在看KMP算法时总感觉不怎么明白,网上也看了那么多关于KMP算法的介绍,还是没怎么看懂,最后索性按照自己的理解,然后写出代码,主要是改变了next求解的定义。看了其他大牛在说KMP算法前都先说下BF算法,我也跟随前辈,先说下BF算法。
一.BF算法
BF算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串P的第一个字符进行匹配,若相等,则继续比较S的第二个字符和P的第二个字符;若不相等,则比较S的第二个字符和P的第一个字符,依次比较下去,直到得出最后的匹配结果。
举例说明:
S: ababcababa
P: ababa
BF算法匹配的步骤如下
i=0 i=1 i=2 i=3 i=4
第一趟:ababcababa 第二趟:ababcababa 第三趟:ababcababa 第四趟:ababcababa 第五趟:ababcababa
ababa ababa ababa ababa ababa
j=0 j=1 j=2 j=3 j=4(i和j回溯)
i=1 i=2 i=3 i=4 i=3
第六趟:ababcababa 第七趟:ababcababa 第八趟:ababcababa 第九趟:ababcababa 第十趟:ababcababa
ababa ababa ababa ababa ababa
j=0 j=0 j=1 j=2(i和j回溯) j=0
i=4 i=5 i=6 i=7 i=8
第十一趟:ababcababa 第十二趟:ababcababa 第十三趟:ababcababa 第十四趟:ababcababa 第十五趟:ababcababa
ababa ababa ababa ababa ababa
j=0 j=0 j=1 j=2 j=3
i=9
第十六趟:ababcababa
ababa
j=4(匹配成功)
代码实现:
int BFMatch(char *s,char *p)
{
int i,j;
i=0;
while(i<strlen(s))
{
j=0;
while(s[i]==p[j]&&j<strlen(p))
{
i++;
j++;
}
if(j==strlen(p))
return i-strlen(p);
i=i-j+1; //指针i回溯
}
return -1;
}
二.KMP算法
KMP算法之所以叫做KMP算法是因为这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题,只需确定下次匹配j的位置即可,使得问题的复杂度由O(mn)下降到O(m+n)。
在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于相同字符序列的前缀。
我对对于next[]数组的定义如下:
1) next[j] = -1 j = 0
2) next[j] = max(k): 0<=k<j P[0...k-1]=P[j-k,j-1]
相信很多算法中都是这样定义的
1) next[j] = -1 j = 0
2) next[j] = max(k): 0<=k<j P[0...k-1]=P[j-k,j-1]
3) next[j] = 0 其他
我觉得3和2其实可以合并的这样,如果前缀和后缀的相似元素为0个我们就说next[j]=0,这样在程序设计上更简单些如:
P a b a b a
j 0 1 2 3 4
next -1 0 0 1 2
即next[j]=k>0时,表示P[0...k-1]=P[j-k,j-1]
因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。
代码实现如下:
int KMP(char *Text,char * Pattern)
{
if(!Text || !Pattern || Pattern[0] == '/0' || Text[0] == '/0')
return -1;
int len = 0;
const char *c = Pattern;
while(*c++ != 0)
{
++len;
}
int * next = new int[len+1];
get_next(Pattern,next);
int index = 0,i=0,j=0;
while(Text[i] != 0 && Pattern[j] != 0)
{
if (Text[i] == Pattern[j])
{
++i;
++j;
}
else
{
index += j-next[j];
if(next[j] != -1)
j = next[j]; //若当前匹配不成功则找到下次开始匹配的位置
else
{
j=0; //如果一开始就匹配不成功则向后移
++i;
}
}
}
delete []next;
if(Pattern[j] == 0)
return index;
else
return -1;
}
根据定义next[0]=-1,
若有next[j]=k;即表示为:
在0<=k<j-1中,当m=0:1:k俊满足P[j-1-m] = P[m],即k为重复元素最多的个数
因此可以这样去实现:
void get_next(char * P,int * next)
{
int k,j;
j = 1;
next[0] = -1; //第一个next的值始终为-1;其实也可以写做其他标记,仅仅是表现起始位置而已
while(P[j] != 0)
{
k = 0;
while(k<j-1)
{
if(P[j-1-k] == P[k])
{
++k; //在P[0,...,j-1]中找相似的部分的个数,如ababc中c之前相似的为2个,这也是KMP在下一次移动时移动的位数
}
else
break; //找出其中最大的相似
}
next[j] = k;
j++; //j后移,每一个位置的都找出来
}
}
上述是我对KMP算法的理解,主要是改变了对next求解的定义,以及代码的实现。本人最近在看KMP算法看到很多大牛关于KMP算法的解释,文中借鉴了很多,但在学习的过程中感觉next数组晦涩难懂,代码也不易看懂,所以就自己定义了next的求解,由于是菜鸟所以不知道这样的next的定义有漏洞没,欢迎指正。