KMP算法 字符串匹配

来源:互联网 发布:天天向上网络作家专场 编辑:程序博客网 时间:2024/05/02 00:21

(如果您已经了解过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:c a b a a b a a b c a c a a b c
T: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开始):

123456789101112131415
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所示,下面先给出代码:

12345678910111213
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] ;

循环上面的过程。

原创粉丝点击