KMP算法

文章出处:http://www.cnblogs.com/epsilon/archive/2013/01/16/KMP.html

今天,寒假第5天。

从昨天开始,就有些体会到内心的疲乏和懈怠了。因为重复而单调的生活很难让内心感到“爽快”,甚至在当今社会显得有些不合时宜。但这种生活也自有它的调味品,那些被人称为“有些挑战性”的算法,就能带来些欢欣、掀起些波澜。今天写的KMP算法就算是其中的一个。

上学期《数据结构》老师说到KMP时就一带而过,因为书上打了星号(还是双星呢**),当时也就没仔细看,但是KMP却在我心中树立起了“伟岸”的形象。今天终于一睹其尊容,不亦快哉?

要讲清楚KMP算法确非易事,这倒不是应为它有多复杂,其实从直观上看,KMP是相当简单的。但是要用准确的语言表述,就感觉有些抽象。

我们看一个书上的例子:

   (图1)

上面的串叫做“主串”,下面的串是用来和“主串”匹配的,叫做“模式串”。在比较过程中,在i=3,j=3时不匹配了。怎么办?一般的想法是:让i=2,j=1,再进行比较。

(图2)

但KMP算法告诉我们,不用这样。在KMP算法中i是不会走回头路的。我们看看它是怎么做的。

(图3)

KMP算法直接让模式串向右移动2位。我们此时可能并不理解为什么可以这样。我们不妨再看看KMP算法下一步怎么做。

(图4)

在第二趟的比较过程中,很“不幸”的,模式串的前四个都匹配了,但是第五个和主串出现了不同。那下一步怎么做呢?我们直观观察发现,我们把模式串向右移3位就可以了。

(图5)

我们的直观告诉我们,连图5中的j=1的a也不用比较了,直接从j=2开始就可以了(不知道你的直观是不是这样~如果不是,尝试着多看几遍图4)。为什么中间的步骤都可以省略呢?其实在运行到图4状态的时候,我们已经对主串有所了解了,那就是主串[3…6]和模式串[1…4]一定是一样的。其实,KMP算法的优势就在于利用了我们在匹配过程中,对主串形成的了解,这种了解就是模式串和主串有相同的地方。比如在运行到图4状态时,想要了解主串i=7之前的具体情况,模式串就可以回答我们。

在运行到图4之后,我们接下来该做什么呢?因为主串和模式串已经出现了不匹配,那我们就应该把模式串进行移动。常规的思路就是:把模式串向右移动1位,看看和主串是否匹配;若不匹配,就移动2位。。。直到匹配或是比较到最后。由于我们之前讲过,想要了解主串i=7之前的具体情况,模式串就可以回答我们。我们之所以能直接在图5中直接比较j=2,是因为相同是相同的(见图6划线部分)。

(图6)

是一定相同的,因为主串和模式串的这一位已经匹配,要想相同,只要相同即可。而这是模式串自存在起就已经决定的。我们是不是可以这样归纳(k的求法):对于某一个j,只需在模式串[1..j-1]中找到两个相同的子串: 模[1..k-1]和 [j-k+1..j-1](要保证k取到最大,这样才最有意义;在示例中k=2),然后我们继续比较主串的i和模式串的k就可以了。(仔细体会!)

对于模式串,它的每一位所对应的k都是确定的,示例中j=5时,k=2。我们可以用一个数组next[]来记录,其中next[j]=k。

KMP算法就是说这样,它在遇到不匹配后,不移动主串的i,而是将主串的第i位和模式串第k位继续比较。k则是在模式串确定后,计算好并保存在next[]数组中的

 

//到此处,你应该明白了KMP的基本原理,以及k怎么求。

 

对于刚才所说的内容,下面有一个数学证明:(基本抄课本上的)

定理:设主串为’s1s2…sn’,模式串为’p1p2。。。pn’。

如果主串中第i个字符和模式串中第j个字符失配,此时主串第i个字符应该与模式串第k(k<j)个字符继续比较,则模式串前k-1个字符,且不存在k>k满足下列关系式:

‘p1p2。。。pk-1’ = ‘si-k+1si-k+2…si-1

已经得到的匹配部分是:

‘pj-k+1pj-k+2。。。pj-1’ = ‘si-k+1si-k+2…si-1

联立上面两式,得到

‘p1p2。。。pk-1’ = ‘pj-k+1pj-k+2。。。pj-1

(就是说,在模式串中找到的k就是主串第i个字符应该与模式串继续比较的k。)

//证毕

 

下面说说怎么求next数组。下面是定义。

 

还是先看看课本上的例子。

(图7)

j==1时next[1]=0;j!=1时,手工算就是看看[1..j-1]最长的相同子串长度([1..k-1]和[j-k+2..j-1])。比如j=4时,前面最长的为[1]和[3],都是’a’,所以k=2。又如j=6时,最长的子串为[1..2]和[4..5],都是’ab’,所以k=3。

 

那怎么编程呢?如果对每个j都要这样一个个地算,是很费时间的。

我们发现,如果对于next[j]=k,那么next[j+1]则可以根据已经算出的值加以推算。

如果next[j]=k,则有:

‘p1p2。。。pk-1’ = ‘pj-k+1pj-k+2。。。pj-1

(1)   若pk=pj,则有

‘p1p2。。。pk’ = ‘pj-k+1pj-k+2。。。pj

也就是说,next[j+1]=k+1.

(2)   若pk!=pj,则有

‘p1p2。。。pk’ != ‘pj-k+1pj-k+2。。。pj

我们把这个模式串既看成主串,也看成模式串。相当于在比较到主串的j和模式串的k时,发现二者不匹配。根据前面说的,KMP算法会将主串的j和模式串的next[k]继续比较。

(图8)

不妨令k=next[k].

假如p[j]==p[k],那就是说:p[j]之前有:

‘p1p2。。。pk’’ != ‘pj-k’+1pj-k’+2。。。pj

(仔细体会)。

所以有:

next[j+1]=next[k]+1

假如p[j]!=p[k],那我们依次类推,令k=next[k], 看有没有[j]==p[k]成立.若不成立,重复这个操作…如果找不到这样的k(1< k<j),那么next[j+1]=1.

 

于是有下面的代码:

int GetNext(char *s,int next[])
{
    int i = 1,j = 0;
    next[1]=0;
    while(i<strlen(s)) {
        if(j==0 || s[i]==s[j]) {
            ++i;++j;
            next[i] = j;
        }
        else j = next[j];
    }
}

课本上最后对这个代码做了改进。

(图9)

如果next[j]=k,而模式串中p[k]==p[j],那么如果主串中si!=pj,就无需再比较si和pk,而直接可以比较si和pnext[k].

改进后的代码如下:

void GetNext(char t[],int nextval[])
{
    int i = 1,j = 0;
    nextval[1] = 0;
    while(i<t[0]) {
        if(j == 0 || t[i]==t[j]) {
            ++i,++j;
            if(t[i]!=t[j]) nextval[i] = j;
            else nextval[i] = nextval[j];
        } 
        else j = nextval[j];
    }
}

再回过头来,看KMP的主代码,相信已经可以看懂了。
int Index_KMP(char s[],char t[],int pos,int nextval[])
//字符数组第0位不存放数据 
{
    int i = pos, j = 1;
    while(i<strlen(s)&&j<strlen(t)) {
        if(!j || s[i]==t[j]) {
            ++i;++j;
        }
        else
            j = nextval[j];
    }
    if(j>=strlen(t)) return i-strlen(t)+1;
    else return 0;
}

已经写得很多了:)

若有疏漏、错误,欢迎批评指正,多谢!





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值