算法-字符串之模式匹配KMP算法

在字符串系列的算法中,KMP算法属于较难的一个。实际上它的代码并不多,主要一些细节的地方难以理解,再加上书上,网上实现KMP算法的方式各有不同,造成混淆也再所难免,我自己也看了不止一遍,这篇博客我会力求将我所理解的KMP算法讲清楚。不足之处,欢迎指正。

KMP算法是字符串匹配算法中比比较高效的一个,相比于常规的一个一个字符比较的方法,KMP提供了一个失配函数,依赖于模式串和字符串之前比较过的信息,得到下一次比较的位置,使得失配时不再是只移动一位,而是可以跳过多位。

算法思想:

首先,我们需要明确,KMP算法主要移动模式串来尽快找到匹配。

将模式的第j位与主串的第i位比较,j的范围是[0,np)(np模式长),i的范围是[0,ns)(ns是主串的长度),如果匹配,i,j都后移一位;如果不匹配,判断j是否为0(j=0说明第0位就不匹配),这时,模式串右滑一位,与主串的第1位比较;如果以上条件都不满足,说明失配了。失配如何处理呢,借助于失配函数

失配函数是KMP算法的关键,它是针对模式串的,由失配函数计算得到失配数组,有的失配数组中failure[i]表示模式串中当前第i个字符与前面的第failure[i]个字符相同,且满足pat[0...failure[i]]同pat[(j-failure[i])...j]是到j为止的最长公共前后缀,我觉得这种方法很难理解,参考了网上的一些博客之后,我选了另一种理解方法:失配数组failure[i]表示,模式串pat的子串pat[0...i]的最长公共前后缀的长度。

举个栗子,前后缀的概念看了这个栗子也就懂了。
字符串p:abcabd
前缀:a,ab,abc,abca,abcab
后缀:d,bd,abd,cabd,bcabd
那么,第0个字符串肯定不会有公共前后缀,到第1个字符为止,前面没有和它相同的字符,所以也没有公共前后缀,一直到第3个字符为止,存在p[0]=[3],所以此时它的公共前后缀为a,长度=1;到第4个字符时,存在p[0...1]=p[3...4],所以此时的最长公共前后缀长度是2;到第5个字符时,不存在公共前后缀了。由这个规律怎么得到失配数组的计算方法呢?现在我们先理解前后缀了最长相同前后缀,后面再结合代码来理解失配函数。

现在通过图理解KMP算法是如何工作的:

1.先计算得到失配数组:
失配数组
第一次失配
2.第一次失配

上图中主串和模式发生了失配,此时主串下标i=7,模式下标j=7。根据上面的失配数组可以得到failure[6]=4,此时模式右滑,置j=4,i不变,重新比较上次失配的地方。

第二次失配
3.第二次失配

此时不必再比较红线框里的,因为根据之前比较的结果,我们可以从失配函数中知道它们一定是匹配的,这次直接比较pat[4]和s[7],又一次失配了,这次根据failure[4]=2,将j置为2.


第三次失配

以上就是KMP算法的运算过程,根据失配数据来滑动模式,匹配主串。

代码

1.失配函数

首先我们来看如何计算失配数组,有以下几个需要注意的地方:

  1. fialure保存到当前位置为止的子串的最长公共前后缀。从模式的起始位置开始,我们首先要确定,第0个字符不存在最长公共前后缀,所以failure[0]=0。

  2. 函数中的i有两层含义。首先,i表示pat的下标,从0开始,用来和j比较当前第i位和第j为是否相等;其次i也表示到第j位为止,pat的最长公共前后缀,也就是failure[j]的值。

  3. 如果pat[j]=pat[i],说明字符串中多了一位和之前字符匹配的字符,最长相同前后缀应该在上次的基础上+1。

  4. 如果pat[j]!=pat[i],这是最难理解的地方。首先,我们要知道这样一个关 系,到第j-1为为止的最长相同前后缀如果为3,说明pat[0...2]这一子串是最长相同前后缀,那么可以得到pat[0..2]=pat[(j-1-2)...(j-1)]是相等的,我们下一次就要比较pat[3]和pat[j],如果这两个位置的字符不相等,那么我们要从上一次的最长相同前后缀得到这一次比较的的值的下标,这个值就是failure[2]。由此可知,比较不相等时,我们就应该找上一次比较的最长相同前后缀的长度,来确定这次要比较的下标。如果这里实在难以理解,建议自己画一下,这样会比较清晰一些。

/*计算失配函数*/
void fail(char *pat){
    int n = strlen(pat);
    int i, j;
    failure[0] = 0;
    i = failure[0];/*i初始化为0,表示第0个字符,又表示在模式中,到0的最长公共元素个数为0*/
    for (j = 1; j < n; j++){
        while ((pat[j] != pat[i]) && i>0){
            i = failure[i - 1];
        }
        if (pat[j] == pat[i])
            i++;
        else
            i = 0;
        failure[j] = i;
    }
}
2.KMP函数

接下来是KMP函数,这个理解就简单多了。
如果模式和主串匹配失败,需要根据失配值来滑动模式,充分利用了之前比较过的信息。

/*返回的是模式在字符串中匹配时的起始位置*/
int kmp(char *string, char *pat){
    int i = 0, j = 0;/*分别表示string,pat的下标*/
    int lens = strlen(string);
    int lenp = strlen(pat);
    while (i < lens && j < lenp){
        printf("\ti=%d j=%d\n", i, j);/*可以打印出比较的情况自己看一下*/
        if (string[i] == pat[j]){/*相等时比较后一位*/
            i++;
            j++;
        }
        else if (j == 0)
            i++;/*string中第一位和pat的第一位不等*/
        else
            j = failure[j - 1];/*第j位失配时,先由失配数组得到在模式中到j为止的最长相同
                             前后缀的长度k,此时右滑模式,跳过k位。*/
    }
    return ((j == lenp) ? i - lenp : -1);

}

总结:

写了这么多,其实KMP算法最重要的就是理解两个部分。第一,和常规的模式匹配算法相比,KMP选择移动模式来进行匹配,因为模式要比主串短的多,遍历周期也更短;第二,对失配函数的理解,求模式当前位置的最长公共前后缀,发生失配时,根据失配值来滑动模式。其实最长公共前后缀也就是找模式中的相同子串,只是这个子串是有要求的,该子串必须最长,以保证模式的滑动距离最长,该子串必须是从模式的第一位开始,也就是“前缀”,保证前缀和后缀相同,那么模式和主串失配,我们就可以将模式移动到后缀上次的位置,因为前面我们已经比较过后缀之前了部分是匹配的,且前缀和后缀是相等的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容