您的位置:首页 > 其它

KMP算法 字符串匹配个人理解

2014-04-09 20:11 197 查看


KMP算法 字符串匹配个人理解

最近很纠结,看了一个多星期,终于基本理解KMP算法,今天写下此文,献给那些依然纠结在KMP中的兄弟同胞们!

(如果您已经了解过KMP算法的请跳过这一段)刚刚进来的童鞋可能还不明白什么是KMP算法,现在在这里先大体的说以下,KMP算法其实就是字符串匹配的算法,和C语言的一个函数strstr();功能一样,引用严蔚敏教授的例子,主串 S:acabaabaabcacaabc 模式串 T:abaabcac。用T去匹配S,返回T串在S串中的位置。对于这个问题,很容易想到常规的匹配方法,T串匹配每一个S串的位置都匹配一次(这里就不具体说明,大家一定都很容易想到)。但是这种算法在最坏情况下的时间复杂程度是O(n*m) n、m是两个字符串的串长。接下来我要介绍D.E.Knuth
与 V.R.Pratt 和 J.H.Morris 同时发现的,我们称KMP算法,其时间复杂程度是O(n+m)。

KMP算法和常规算法不同的就是指针回朔的问题,还是用一个例子说明这个问题。

S:a c a b a a b a a b c a c a a b c

T:a b a a b c a c

N:0 1 1 2 2 3 1 2 < -- next[1..8]

第一趟:

指针 i = 1 –> i = 2

S:a c a
b a a b a a b c a c a a b c

T:a b a
a b c a c

指针 j = 1 –> j = 2

j = 2的时候不匹配,next[2] = 1,j指针回朔到1

第二趟:

指针 i = 2

S:a c a
b a a b a a b c a c a a b c

T: a b
a a b c a c

指针 j = 1

j = 1的时候不匹配,next[1] = 0,i、j同时加1

第三趟:

指针 i = 3 –> i = 8

S:a c a b a a b a a
b c a c a a b c

T: a b a a b c a
c

指针 j = 1 –> j = 6

j = 6 时候不匹配,next[6] = 3,j指针回朔到3

第四趟:

指针 i = 8 –> i = 14

S:a c a b a a b a a b c a c a a
b c

T: a b a a b c a c

指针 j = 3 –> j = 9

j走到末尾,匹配完成!

看到这里,相信大多是人都有点糊涂了,没关系,听我慢慢说,我们先分析一个这个匹配过程,从上面看,KMP算法在这个例子上只走了4 趟,如果是常规方法,要6趟才能完成。KMP算法中,还有一个非常非常重要的就是i 指针不回朔,下面将详细讲解是如何实现的。

要做到i指针不回朔,我们还的先对T串进行一下预处理,这里,我们引入一个next函数(计算公式如图1),这个函数值记录的是当匹配出现字符串不相等的时候,i指针不回朔,j指针回朔的位置,也就相当于让T串向右滑动一定的距离后,继续比较。现在对照next值,再重新看一遍是不是稍稍明白了一点了呢?



(图
1)

看到这里,你的脑子里一定在问,为什么next函数可以跳过这么多不需要的匹配过程呢?这个其实就就是KMP的核心所在,我也是困扰在这上面一个星期之久。KMP算法用了一个非常非常简单的原理,比如,已知字符A B C: ( A != B && A == C ) ==> B != C 。很容易理解吧。

接下来我将详细介绍如何利用上面的那条性质进行字符串匹配,还是举上面的例子:

S:a c a b a a b a a
b c a c a a b c

T: a b a a
b
c a c

当出现不匹配的时候,我们让 j 指针回朔到第一个a b后面,因为 c 前面 a
b
等于 最前面的 a b,next函数保存的就是满足T[1..n] == T[j-1-n] n最大时 next[j] = n + 1 。

指针回朔后变成这个样子:

S:a c a b a a b a a
b c a c a a b c

T: a
b
a a b c a
c

到现在为止,如果你大致明白KMP的原理和比较过程,那么我5个小时的工作还算有些成果。根据上面的过程,我们很容易写出KMP算法的主函数,下面给出C代码(注意C语言字符串下标从0开始):

int Index_KMP(char* s, char* t, int* next) {
int i = 0, j = 0;
int l1 = strlen(s);
int l2 = strlen(t);
while (i < l1 && j < l2) {
if (next[j] == -1 || s[i] == t[j]) {
i++;
j++;
} else {
j = next[j];
}
}
if (j <= l2) return i - l2;
else return -1;
} //Index_KMP


上面的代码是在知道next函数的基础上的,我们怎么求next的函数呢?

计算方法如上面图1所示,下面先给出代码:

void get_next(char* t, int* next)
{
int i = 0, j = -1;  // j记录已匹配的个数
int l2 = strlen(t);
next[0] = -1;
while (i < strlen(t)) {
if (j == -1 || t[i] == t[j]) {
i++;
j++;
next[i] = j;
} else j = next[j];
}
} //get_next


说实话这段代码我也纠结了很久,最后还是被严蔚敏教授一语惊醒梦中人,计算next的值是递推的过程!分析如下:

已知:next[0] = -1;

假设:next[j] = k; 又 T[j] = T[k];

推出:next[j + 1] = k + 1 ;

或者

已知:next[0] = -1;

假设:next[j] = k; 又 T[j] != T[k];

推出:k = next[j] ;

循环上面的过程。

花了6个小时,写到这里,KMP算法基本介绍完毕,下期预告:nextval,KMP算法的改进。

作者: Yes2Me |
可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明

网址: http://yes2.me/archives/289
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: