KMP 算法

KMP 算法

1. 暴力匹配算法

在分析KMP算法前, 先看看暴力匹配算法是如何工作的.
暴力匹配算法的基本思想是: 从主串的第pos个字符起和模式串的第一个字符比较, 若相等,则继续逐个比较后续字符;否则从主串的下一个字符起再重新和模式串的字符比较. 依次类推, 直至模式串T中的每个字符和主串S中的一个连续的字符序列相等, 则匹配成功, 否则匹配不成功.
例如
主串S: S1 S2 S3 S4 ...... Sn; 模式串T: T1 T2 T3 T4 ...... Tn
当S[i] == T[j]时, 则i++, j++

BF.png

当S[i] != T[j]时, 则i指针回溯到S2处, j指针回到起始位置T1处再进行匹配

BF1.png

假设主串S中有m个字符, 模式串T中有n个字符, 那么暴力匹配算法的时间复杂度可以表示为O(m*n)

小思考

  1. 当主串中S[i] != T[j]时, 主串的i指针是否需要回溯到本次匹配的第一个字符的下一个位置呢?


    Back.png

由上图我们可以看出, i指针回溯的目的是为了让模式串T分别从主串S的S2,S3字符处开始匹配, 避免漏掉模式串T从主串的S2字符处或者S3字符处与主串S产生匹配的情况; 但是我们已知S1S2S3 == T1T2T3, 即 S1 == T1, S2 == T2, S3 == T3, 所以我们只需要拿T1与T2,T3进行比较, 即可判断模式串T是否会在S2或S3处开始于主串匹配, 据此我们可以通过只移动模式串的指针j而不移动主串的i指针来继续进行模式匹配的目的.
结论: 在整个模式匹配的过程中, 主串i指针可以不回溯

KMP 算法

KMP 算法相对于暴力匹配算法的改进在于: 在模式匹配的过程中, 如果出现了失配的情况, 不需要回溯主串的i指针, 而是利用已经得到的"部分匹配"的结果将模式串T向右"滑动"尽可能远的距离,再继续与主串进行比较

  • 下面以主串S, 模式串T为例, 分析当S与T失配时, 模式串T的j指针应该如何移动?


    Screen Shot 2018-05-19 at 3.16.07 PM.png

此时S[i] != T[j]
那么假设此时模式串T的指针j需要回溯到k位置再与主串S进行比较, 如图


Screen Shot 2018-05-19 at 3.38.26 PM.png

这里的k需要满足的条件是: T[0]T[1]...T[k-1] == T[j-k+1]...T[j-1]
从这里我们可以看出, 模式串T中k位置位主串S是无关的, 它是通过模式串本身的特性确定的; 由此我们在进行模式匹配前, 可以先对模式串T的各个位计算其k值; 当模式串T与主串S失配时, 我们就可以直接根据模式串T的失配位获取对应的k值, 然后让模式串T的j指针回溯到k位置再与主串S进行比较

  • 如何确定模式串T各个位的k值?
这里我们先确定k值的含义

k指的是当模式串中指针j所指向的字符与主串中指针i所指向的字符失配时, 在模式串T需要重新和主串S中i指针指向的字符进行比较的字符的位置
设next(j) = k, 则模式串存在下列关系:
T[0]T[1]...T[k-1] == T[j-k+1]...T[j-1]
其中k为满足1<k<j的某个值, 此时next(j + 1) = ?可能有两种情况:
(1) T[k] = T[j]
表明在模式串中T[0]T[1]...T[k] == T[j-k+1]...T[j], 则next(j + 1) = next(j) + 1
(2) T[k] != T[j]
表明在模式串中T[0]T[1]...T[k] != T[j-k+1]...T[j], 此时可以把求next的函数值的问题看成是一个模式匹配的问题, 整个模式串既是主串也是模式串, 而当前的匹配过程中, 已有T[0]T[1]...T[k] == T[j-k+1]...T[j], T[k] != T[j], 现将模式右滑至以模式中的第next(k)个字符和主串中的第j个字符比较, 如果next(k) = k', 且T[j] = T[k'], 则说明在主串中第j+1个字符前存在一个长度为k'的最长子串和模式串中从首字符起长度为k'的子串相等,即T[0]T[1]...T[k'] == T[j-k'+1]...T[j], 此时next(j + 1) = next(k) + 1; 同理如果T[j] != T[k'], 则继续进行上述过程直至T[j]和模式中某个字符匹配成功或者不存在任何k'满足条件,则next(j + 1) = 1.

总结

  1. 当j = 1时, next(j) = 0; 此时主串i指针右移一位, 即i++; 模式串从主串的第i位开始匹配
  2. 当j = 0时, next(j) = 1; 此时模式串j指针回溯到第一位再与主串第i位进行匹配
  3. 当j = Max{k|1 < k < j, 且T[0]T[1]...T[k-1] == T[j-k+1]...T[j-1]}时, 模式串指针j回溯到next(j)处再与主串第i位进行匹配

KMP算法实现

//
//  main.c
//  KMP
//
//  Created by 肖仲文 on 2018/6/3.
//  Copyright © 2018 肖仲文. All rights reserved.
//

#include <stdio.h>

#define MAXSTRLEN 255
typedef unsigned char SString[MAXSTRLEN + 1]; // 0号单元存放串的长度

/**
 生成一个值为chars的串

 @param T 串
 @param chars 字符串常量
 */
void strAssign(SString T, const char *chars) {
    
    if (chars == NULL) {
        return;
    }
    
    int index = 0;
    while (*(chars + index) != '\0') {
        T[index + 1] = *(chars + index);
        index++;
    }
    T[0] = index;
}

/**
 求模式串T的next函数值并保存到数组next

 @param T 模式串
 @param next next值数组
 */
void get_next(SString T, int next[]) {
    int i = 1;
    int j = 0;
    next[i] = j;
    while (i < T[0]) {
        if (j == 0 || T[i] == T[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}

/**
 KMP算法

 @param S 主串
 @param T 模式串
 @param next 模式串的next值数组
 @return 模式串与主串匹配时的位置, 不匹配返回0
 */
int index_kmp (SString S, SString T, int next[]) {
    int i = 1;
    int j = 1;
    while (i <= S[0] && j <= T[0]) {
        if (j == 0 || S[i] == T[j]) {
            i++;
            j++;
        } else {
            j = next[j];
        }
    }
    if (j > T[0]) {
        return i - T[0];
    } else {
        return 0;
    }
}

int main(int argc, const char * argv[]) {
    
    // 定义主串和模式串
    SString mainStr, patternStr;
    
    // 初始化主串和模式串
    strAssign(mainStr, "ababcabcacbab");
    strAssign(patternStr, "abcac");
    
    // next值
    int next[patternStr[0] + 1];
    get_next(patternStr, next);
    
    // kmp
    int pos = index_kmp(mainStr, patternStr, next);
    printf("pos = %d\n", pos);
    
    return 0;
}

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

推荐阅读更多精彩内容

  • 数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章、这篇文章、还有这篇他们写得非常...
    sunhaiyu阅读 1,729评论 1 21
  • 字符串匹配KMP算法详解 1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有...
    张晨辉Allen阅读 2,390评论 0 3
  • 数据结构 第8讲 KMP算法 讲这个算法之前,我们首先了解几个概念: 串:又称字符串,是由零个或多个字符组成的有限...
    rainchxy阅读 1,278评论 0 3
  • 引言 字符串匹配一直是计算机科学领域研究和应用的热门领域,算法的改进研究一直是一个十分困难的课题。作为字符串匹配中...
    潮汐行者阅读 1,629评论 2 6
  • 文章大纲:1.KMP算法概念2.KMP算法中最核心的next[] 数组是如何生成的3.使用KMP算法 匹配字符串 ...
    柠檬乌冬面阅读 810评论 0 3