模式串匹配--KMP算法

 

  前几天百度LBS部门实习二面,让写一个字符串匹配函数,当时忘记KMP怎么写了,就默默的写了一个暴力搜索,连尝试推导一下KMP都没有,结果自然是没有过,以后面试要多和面试官交流,就算忘记了,也要让他知道你试图推导,要不然他会觉得你可能都没有听过。

  KMP是对前缀暴力搜索的改进,基于的想法其实是很朴素的。首先我们来看一下暴力搜索。

char* BF(char *src, char *pattern){
    if(src == NULL || pattern == NULL) return NULL;
    char *src_temp = src, *pattern_temp = pattern;
    while(*src_temp != ‘\0‘ && *pattern_temp != ‘\0‘){
        if(*src_temp == *pattern_temp){
            src_temp++; pattern_temp++;
        }else{
            src_temp = ++src;
            pattern_temp = pattern;
        }
    }
    if(*pattern_temp == ‘\0‘) return src;
    else return NULL;
}

  如果匹配失败,则将关键字向右滑动一个字符,从头开始匹配关键字,匹配成功则有src[i,i+1,......i+m] == p[0,1,......m]。BF的时间复杂度是O(m*n)。KMP改进的想法很朴素,能不能不是每次移动一个距离,每次多移动几个距离不就可以提高效率了么,但是每次多移动几个呢?KMP算法就是解决每次移动几个的问题。

  假设暴力搜索时关键字在匹配到p[j]字符时失败了,即src[i,i+1,......,i+j-1] == p[0,1,......j-1](1), src[i+j] != p[j],则按照暴力搜索的方法将关键字向右滑动一个字符,即从src[i+1]开始从新匹配。

但是我们假设关键串有如下特征:

p[0,1.....j-2] != p[1,2,......j-1](2),则(1)可知p[0,1......j-2] != src[i+1,i+2,......,i+j-1],所以将关键串向右滑动一个字符从src[i+1]开始匹配肯定失败,所以在我们知道(2)式的情况下,就可以直接跳过向右滑动一个字符,那到底滑动几个字符呢?如果我们知道了子串p[0,1,......next(j-1)](next(j-1)又叫做j-1的失效函数),既是p[0,1,......j-1]的最长真前缀又是p[0,1,......j-1]最长后缀,那我们就可以将关键字向右滑动j-1-next(j-1)个字符,并且认为关键串的前next(j-1)个字符已经匹配成功,继续从src[j]开始匹配(这个思想同样应用于求next(j)的算法中,后面的代码中可以看到)。所以整个KMP算法的关键就是求得每一个next(j), j = 0......m,这样就可以知道在j+1匹配失败的时候,应该将关键字向右滑动几个字符位置。可以证明KMP算法的时间复杂度是O(m+n)。

求next(j)的代码如下,相关解释会在注释中:

void next(char *pattern, int *next){
    int t = -1; next[0] = -1;
    for(int s = 0; pattern[s+1] != ‘\0‘; s++){
        while(t > -1 && pattern[t+1] != pattern[s+1]) t = next[t];// t是最长真前缀的下标,s是字符串的下标,这其实就是一个真前缀和后缀的匹配过程,匹配失败则回溯真前缀的真前缀。这也就是KMP的核心思想。
        if(pattern[s+1] == pattern[t+1]){
            t = t + 1;
            next[s+1] = t;
        }else  next[s] = -1;
    }
}

得到上面的next(j)后,就可以在O(n)的时间内扫描src字符串,以判断该关键串是否出现在其中。关键串沿着匹配字符串滑动,不断尝试将关键字的下一个字符与被匹配字符串的下一个字符匹配,逐步推进。如果在匹配了j个字符后无法匹配,那么将关键字向右滑动j-next(j)个位置,并且前next(j)个字符已经匹配成功,从src[j+1]继续匹配。

char* KMP(char *src, char *pattern){
    if(src == NULL || pattern ==  NULL) return NULL;
    int pattern_size = 0;
    for(; pattern[pattern_size] != ‘\0‘; pattern_size++)
    int* next = new int[pattern_size];
    next(patter,next);
    int s = -1;
    for(int i = 0; src[i] != ‘\0‘; i++){
        while(s > -1 && src[i] != pattern[s+1]) s = next[s];//匹配失败,则将关键串向右滑动s - next[s]个字符,并且前next[s]个字符已经匹配成功,继续从src[i]开始匹配。
        if(src[i] == pattern[s+1]){
            s++;
        }
        if(pattern[s+1] == ‘\0‘) return &src[i] - pattern_size +1;
    }
    return NULL;
}

说到KMP就不得不说AC自动机,其实理解了KMP的思想,即找出子串p[0,1,......next(j-1)],既是p[0,1,......j-1]的最长真前缀又是p[0,1,......j-1]最长后缀,也就不难理解AC自动机。AC自动机又叫Aho-Corasick算法,是Aho和Croasick对KMP算法的推广,可以在一个文本串种识别一个关键字集合中的任何关键字。由于是关键字集合,所以采用了trie-tree来存储关键字,每个节点的失效函数稍微不同于KMP的失效函数,即状态next(j)对应于最长的、既是串pattern[0,1......,j]的后缀,又是某个关键字的前缀的字符串。对于AC自动机,你可以理解为是在求失效函数和匹配过程中考虑了关键字集合的KMP或者KMP是AC自动机的特例,但是核心思想就是子串p[0,1,......next(j-1)],既是p[0,1,......j-1]的最长真前缀又是p[0,1,......j-1]最长后缀,AC自动机只是描述关键字集合的一种方法。关于AC自动机,DSQiu的博客http://dsqiu.iteye.com/blog/1700312有一个比较好的实现,有兴趣可以看一下。

时间: 2024-08-29 09:21:30

模式串匹配--KMP算法的相关文章

AC自动机——多模式串匹配的算法思想

标准KMP算法用于单一模式串的匹配,即在母串中寻求一个模式串的匹配,但是现在又存在这样的一个问题,如果同时给出多个模式串,要求找到这一系列模式串在母串存在的匹配个数,我们应该如何处理呢? 基于KMP算法,我们能够想到的一个朴素算法就是,枚举这多个模式串,然后进行多次KMP算法,这个过程中完成计数,假设这里有n个模式串,那么整个算法的复杂度大约是O(n*m),m是母串的长度,这里的时间复杂度是粗略估计,没有计算辅助数组的时间(KMP中的next数组),但是这种复杂度还是太高,没有做到KMP算法中“

字符串匹配 - KMP算法

首先大致的学习一下有限自动机字符匹配算法,然后在讨论KMP算法. 有限自动机 一个有限自动机M是一个五元组(Q,q0,A,Σ,δ),其中: Q是状态的集合, q0∈Q是初始状态, A是Q的字集,是一个接受状态集合, Σ是一个有限的输入字母表, δ是一个从Q×Σ到Q的函数,叫做转移函数. 下面定义几个相关函数: φ(w)是M在扫描字符串w后终止时的状态.函数φ有下列递归关系定义:φ(ε) = q0,φ(wa) = δ(φ(w),a), σ(x)是x的后缀中,关于P的最长前缀的长度. 字符串匹配自动

字符串匹配KMP算法的理解(详细)

1. 引言 本KMP原文最初写于2年多前的2011年12月,因当时初次接触KMP,思路混乱导致写也写得混乱.所以一直想找机会重新写下KMP,但苦于一直以来对KMP的理解始终不够,故才迟迟没有修改本文. 然近期因开了个算法班,班上专门讲解数据结构.面试.算法,才再次仔细回顾了这个KMP,在综合了一些网友的理解.以及算法班的两位讲师朋友曹博.邹博的理解之后,写了9张PPT,发在微博上.随后,一不做二不休,索性将PPT上的内容整理到了本文之中(后来文章越写越完整,所含内容早已不再是九张PPT 那样简单

数据结构与算法简记--字符串匹配KMP算法

KMP算法 比较难理解,准备有时间专门啃一下. 核心思想与BM算法一样:假设主串是 a,模式串是 b.在模式串与主串匹配的过程中,当遇到不可匹配的字符的时候,我们希望找到一些规律,可以将模式串往后多滑动几位,跳过那些肯定不会匹配的情况. 不同的是:在模式串和主串匹配的过程中,把不能匹配的那个字符仍然叫作坏字符,把已经匹配的那段字符串叫作好前缀. 关键找相等的最长匹配前缀和最长匹配后缀.有两种情况,(1)如果b[i-1]的最长前缀下一个字符与b[i]相等,则next[i]=next[i-1]+1.

Java数据结构之字符串模式匹配算法---KMP算法

本文主要的思路都是参考http://kb.cnblogs.com/page/176818/ 如有冒犯请告知,多谢. 一.KMP算法 KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其基本思想是:每当匹配过程中出现字符串比较不等时,不需回溯指针,而是利用已经得到的"部分匹配"结果将模式向右"滑动"尽可能远的一段距离,继续进行比较.显然我们首先需要获取一个"部分匹配"的结果,该结果怎么计算呢? 二.算法分析 在上一篇中讲到了BF算法,

Java数据结构之字符串模式匹配算法---KMP算法2

直接接上篇上代码: 1 //KMP算法 2 public class KMP { 3 4 // 获取next数组的方法,根据给定的字符串求 5 public static int[] getNext(String sub) { 6 7 int j = 1, k = 0; 8 int[] next = new int[sub.length()]; 9 next[0] = -1; // 这个是规定 10 next[1] = 0; // 这个也是规定 11 // 12 while (j < sub.l

字符串匹配KMP算法C++代码实现

看到了一篇关于<字符串匹配的KMP算法>(见下文)的介绍,地址:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html,这篇博客对KMP算法的解释很清晰,但缺点是没有代码的实现.所以本人根据这位大神的思路写了一下算法的C++实现. C++代码如下: #include <iostream> #include<string.h> using namesp

字符串匹配--kmp算法原理整理

kmp算法原理:求出P0···Pi的最大相同前后缀长度k: 字符串匹配是计算机的基本任务之一.举例,字符串"BBC ABCDAB ABCDABCDABDE",里面是否包含另一个字符串"ABCDABD"? 许多算法可以完成这个任务,Knuth-Morris-Pratt算法(简称KMP)是最常用的之一. KMP算法搜索如下: 1.首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的

【数据结构与算法】字符串匹配KMP算法

首先需要了解一下BF暴力匹配算法,这个算法为每一个串设置一个指针,然后两个指针同时后移,出现不匹配的情况后,主串指针回到开始后移之前的位置的下一位,模式串指针回到最开始. 对比一下KMP算法,同样是设置两个指针,然后两个指针同时后移,出现不匹配的情况后,主串指针不变,模式串指针回溯一定的距离.具体模式串指针回溯多少,是第一次看KMP算法的人比较难以理解的,其实仔细想想,模式串的前缀和后缀其实也是在做匹配,当P[K]!=P[J]时就是失配,那么前缀的指针就需要回溯,所以后k=next[k]. 代码