改进的模式匹配(KMP)算法

一、简介

在说改进的模式匹配(KMP)算法之前我们先说朴素的模式匹配:
其实很简单,就是两个字符串逐位比较。在模式匹配中:我们假定字符串P在字符串T中查找是否有匹配的。此时,称P为模式(Pattern)字符串,称T为目标(Target)字符串。
OK,我一般比较喜欢以实例说明问题。
T: a b d a b d a b c
P: a b d a b c
朴素的模式匹配算法
朴素的模式匹配算法就是用P和T依次比较,即为:
第一趟比较:

 T:        a  b  d  a  b  d  a  b  c 
 P:        a  b  d  a  b  c
           发现第6个元素(下标为5)d和c不相等,第一趟结束,此时比较了6次(6个元素)。

第二趟比较:

   T:        a  b  d  a  b  d  a  b  c 
   P:           a  b  d  a  b  c
               第一个元素就不相等,第二趟结束,此时比较了1次(1个元素)。

第三趟比较:

T:        a  b  d  a  b  d  a  b  c 
   P:              a  b  d  a  b  c
     第一个元素就不相等,第三趟结束,此时比较了1次(1个元素)。

第四趟比较:

T:        a  b  d  a  b  d  a  b  c 
   P:                 a  b  d  a  b  c
     第一个元素相等,第二个元素也相等,第三、四、五、六都相等,匹配成功,第四趟结束,此时比较了6次(6个元素)。

匹配成功,共比较14次。但是这个是我们理想状态下的匹配方案,实际中字符串的长度远远不止这些。这种算法是一种带回逆的算法,成为朴素的模式匹配算法。

改进的模式匹配(KMP)算法
KMP算法就是消除了朴素匹配的回逆,利用一个失效函数(failure function)替代直接的回逆。思路如下:
第一趟比较:
T: a b d a b d a b c
P: a b d a b c
发现第6个元素(下标为5)d和c不相等。此时,进入一个P串的处理:
此时取出P串, a b d a b c 因为是c不和d不匹配,去掉此项,获得
a b d a b
此时判断 a b d a 是否与 b d a b 相等? 不等,进入下一轮判断
此时判断 a b d 是否与 d a b 相等? 不等,进入下一轮判断
此时判断 a b 是否与 a b 相等? 相等,结束第一趟总体判断。
(先不要急,接下来我就会说为什么这样匹配和这样匹配的用途!)
然后直接拿d和字符串中不匹配的那一项进行比较。
以上就是KMP的流程,为什么要这样做?在一些串中,目标串会远远长于模式串,如果每次都江模式串和目标串一一比较。此时时间复杂度当增加,而且在模式串中会出现很多的无效匹配,相当于无用功。但是假如先在模式串中进行比较,因为模式串会远远短于目标串,所以会相当减少时间复杂度。

二、next数组的理解

对于KMP算法来说,重点就是 next数组 (也有叫覆盖函数,部分匹配表,lps数组等)。

总之就是 对模式串做预处理,而且该预处理只和 模式串(pattern)本身有关!

假设有模式串 pattern = “abababca”; 则有匹配表:

char:  | a | b | a | b | a | b | c | a |
index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:”前缀”和”后缀”。 “前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。

字符串:   "bread"

前缀: b , br, bre, brea

后缀:  read, ead, ad ,  d

关于 next数组 (也有叫覆盖函数,部分匹配表,lps数组) 的通俗解释:”部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度。以”ABCDABD”为例,

    "A"的前缀和后缀都为空集,共有元素的长度为0;
    "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
 "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
 "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
 "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
 "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
 "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

下面举个例子

pattern “AABAACAABAA”, next[] is [0, 1, 0, 1, 2, 0, 1, 2, 3, 4, 5]
pattern “ABCDE”, next[] is [0, 0, 0, 0, 0]
pattern “AAAAA”, next[] is [0, 1, 2, 3, 4]
pattern “AAABAAA”, next[] is [0, 1, 2, 0, 1, 2, 3]
pattern “AAACAAAAAC”, next[] is [0, 1, 2, 0, 1, 2, 3, 3, 3, 4]

代码如下:

/*
* function          生成部分匹配表,next数组
* param     subStr  模式串,子串
* param     next    next数组,部分匹配信息表
* param     len     next数组的长度,一般就是模式串的长度
* return            无
*/
void GetNext(const char* subStr, int* next, int len)
{
    memset(next, 0, len);
    int prefix = -1; //前缀
    int suffix = 0; //后缀
    next[0] = -1;    //第一个元素只是用来控制prefix和suffix后移的
    while (suffix < len - 1)//当比较到最后一个字符的时候退出循环
    {
        /*
        当prefix == -1的时候表示要从prefix=0,suffix=1开始比较
        若prefix != -1,表示前缀和后缀已经有重合的了,接着往后移比较
        例如:subStr="ABABABB"
            1.prefix=-1,往后移,prefix=0,suffix=1,next[1] = 0,表示字符串‘A’前缀后缀无重合

            2.prefix=0,比较subStr[0]和subStr[1]('A'和'B'),不相等,把prefix重新置为next[prefix](next[0]==-1)

            3.prefix=-1,往后移,prefix=0,suffix=2,next[2] = 0,表示字符串‘AB’前缀后缀无重合

            4.prefix=0,比较subStr[0]和subStr[2]('A'和'A'),相等,继续往后移,prefix=1,suffix=3,next[3]=1
              表示字符串"ABA"有一个字符前缀后缀相等('A'和'A')

            5.prefix=1,比较subStr[1]和subStr[3]('B'和'B'),相等,继续往后移,prefix=2,suffix=4,next[4]=2
              表示字符串"ABAB"有两个字符前缀后缀相等('AB'和'AB')

            6.prefix=2,比较subStr[2]和subStr[4]('A'和'A'),相等,继续往后移,prefix=3,suffix=5,next[5]=3
              表示字符串"ABABA"有三个字符前缀后缀相等('ABA'和'ABA')

            7.prefix=3,比较subStr[3]和subStr[5]('B'和'B'),相等,继续往后移,prefix=4,suffix=6,next[6]=4
              表示字符串"ABABAB"有四个字符前缀后缀相等('ABAB'和'ABAB')

            8.当suffix=6最后一个的时候,就不需要比较了,因为KMP算法中最后一个并无指导匹配的作用,因为一旦前6个匹配成功,最后一个
              就算不成功,用到的也是前一个的部分匹配信息,若是成功那就直接返回了,所以求next数组的时候,最后一个的信息省略  

        */
        if (prefix == -1 || subStr[prefix] == subStr[suffix])
        {
            ++prefix, ++suffix;           
            next[suffix] = prefix;
            printf("%d ", next[suffix]);  //测试用,可删除
        }
        else
            prefix = next[prefix];
    }
    printf("\n");     //测试用,可删除
}

三、KMP模式匹配算法

int Index_KMP(char *s,char *p,int pos,int next[])
/*利用模式串p的next函数,求p在主串中从第pos个字符开始的位置*/
/*若匹配成功,则返回模式串在主串中的位置(下标),否则返回-1。设模式串第一个字符的下标为0*/
{
  int i,j,slen,plen;
  i = pos-1;
  j=-1;
  slen = strlen(s);plen = strlen(p);
  while(i<slen&&j<plen){
    if(j==-1||s[i]==p[j]){++i;++j;}
        else j = next[j];
  }
  if(j>=plen)return i-plen;
  else return -1;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容