KMP字串匹配-入门

1、串的定义

这里所说的串指的是字串,就是字符串,当然不是烧烤串。计算机的字串是用编码形式保存的,通常的ASCII码,Unicode编码,中文的GBK等等。对于一个字串,定义为s="a1a2a3....an",相应的对于另一个字串t=b1b2b2b3....bm,当且仅当n=m,且a1=b1,a2=b2,a3=b3....,an=bm,则两串相等。同时,当满足如下条件时s<t

  1. n< m , 且a1=b1(i=1,2,3....n);
  2. 有k<=min(m,n),使ai=bi(i=1,2,3,4,5...k-1),ak<bk;(这种情况会根据编码计算,可能不同语言有不同处理)

2、朴素的模式匹配算法

模式匹配最先能想到的就是在一片文章中找到指定的单词,如文本的检索,引申到单词的统计。我们最简单想到的就是将材料文本当做一个很长的串,然后用对应的单词去匹配,具体是按照单词的字符一个个匹配,如果匹配到,继续,直到单词尾部,这是最理想的情况,否则,单词首字符再去比较文本第二个字符,依次直到获得结果。归纳如下:

  1. 匹配到,检索词,文本串同时推进;
  2. 未匹配到,检索词再去执行1,从文本串上次匹配的下一个字符开始;
    朴素模式匹配算法的时间复杂度为最好为O(m+n),最差是O((n-m+1)*m)
    代码如下
    c++
//不存在匹配,返回0,长度存在t[0],s[0]
//s 待匹配,t匹配字串
//t 不空
int index(String s, String t, int pos){
int i = pos;
int j = 1;
while( i <= s[0] && j <= t[0]){
      if(s[i] == t[j]){
            ++i;
            ++j;  
      }else{
        i = i - j + 2;  //从下一个位置开始
        j = 1;  //回到首位比较
      }
  }
   if( j > t[0]){
      return i - t[0];
    }else{
      return 0;
    }
} 

java

public class StringPattern {
    public static void main(String[] args) {
        String s = "dsfandkfnadsognoadsngasfsdbvonasdobvnadsodfnasonfo3n2n";
        String p = "";
        System.out.println(index(s, p));
    }

    private static int index(String s, String p) {
        if (s == null || p == null || "".equals(p)) {
            return -1;
        }
        int i = 0, j = 0;
        while (i < s.length() && j < p.length()) {
            if (s.charAt(i) == p.charAt(j)) {
                i++;
                j++;
            } else {
                i = i - j + 1;
                j = 0;
            }
        }

        if (j >= p.length()) {
            return i - j;
        }

        return -1;
    }
}

3、KMP模式匹配

KMP算法是D.E.Knuth、J.H.Morris以及V.R.Pratt的发明,可以避免重复遍历的算法,大大提高匹配效率。KMP有些难以理解,这种算法是典型的通过对客观规律的总结,来归纳出一个合理的算法,再通过编程实现和优化的结果。我们看到的只有分析过程和算法结果,并对这个结果加以运用。我尽量简洁的来表述这个算法,会对一些难点进行重点强调和解释。本文是KMP的初级介绍,后面会专门介绍KMP的优化方案。

3.1 KMP原理

首先看下朴素匹配可以优化的点。例如:在S=abcdefgab中匹配T=abcdex,朴素算法首先从a一直匹配到f-x,第6位。然后下次匹配再从T-a去匹配S-b,一直到T-a匹配S-c,一直到再匹配到S-f,T-a。重点:这里我们能够发现,字串T中,首字母a与他之后的字符都不相等,因此之后关于T-a和S中随后字符的比较没有必要进行,这是因为在第一次比较就知道S中往后的都是不等的。推理如下:
T(a-e)匹配到S(a-e),由于T(a-x)不等,即T(a-e)到S(a-e)不等直接跳到T(a)匹配S(f)
图示

KMP匹配1

由于我们得到这种经验,如果匹配的串T和目标串S有连续相同的元素,则跳过比较,且这种情况会导致T中j值的变化。所以对于另一种情况如S=abcabaabca,T=abcabx,由于T=(ab)c(ab)x,这里第一次j=1,后面j直接为3,即比较到c的位置,j的取值表示了当期字符之前的串在串中的相似度,相似度越高,j越大。我们将T各个位置上j值的变化表示为一个next的数组保存,抽象成函数表示为:
next[j] = \begin{equation} \begin{cases} 0,j=1\\ \max\{k|1<k<j\},且p_1...p_{k-1}=p_{j-k+1}...p_{j-1},表示有相同字串,存在p1=p4,值为k集合最大值\\ 1 , 其他情况 \end{cases} \end{equation}

3.2 next数组推理和使用

  1. T=abcdex
    j=1,next[1]=0;j=2,子串为a,其他情况next[2]=1;当j=3,子串ab,a!=b,按公式为其他情况,next[3]=1,最终的结果next[j]=011111
  2. T=abcabx
    j=1,next[1]=0;j=2,next[2]=1,其他情况;j=3,子串ab,a!=b,其他情况,next[3]=1;j=4,子串bc,b!=c,其他情况,next[4]=1;j=5,子串abca,a和租后一个a相同,且有p1pk-1=p5-k+1p5-1,有p1=p4,因此k=2,next[5]=3;当j=6,子串(ab)c(ab),有首ab=尾ab,p1p2=p4p5即p1pk-1=p6-k+1p5,得到next[6]=3
    因此得到n个相等k值就是n+1,注意一个例子中k值取最终最大的值。

KMP的时间复杂度为O(m+n),和朴素模式匹配最好的情况一致。

3.3 算法实现

c++

//获取匹配串next数组
void get_next(String t, int *index){
  int i,j;
  i=1;
  j=0;
  next[1]=0;
  while( i < t[0]) {
     if( j==0 || t[i] == t[j]){
         ++i;
         ++j;
         next[i] = j;
     }else{
       j = next[j];//没有相同,j保持前值
    }    
  }

}


int index_kmp(String s ,String t, int pos){
    int i = pos;
    int j = 1;
    int next[255];
    get_index(t,next);
     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];
    return 0;
}

可以看到和朴素算法差别不大,主要在于next数组的区别
java

public class StringPattern {
    public static void main(String[] args) {
        String s = "abcdexfgab";
        String p = "exf";

        System.out.println(index(s, p));
        System.out.println(indexKmp(s,p));
    }

    private static int index(String s, String p) {
        if (s == null || p == null || "".equals(p)) {
            return -1;
        }
        int i = 0, j = 0;
        while (i < s.length() && j < p.length()) {
            if (s.charAt(i) == p.charAt(j)) {
                i++;
                j++;
            } else {
                i = i - j + 1;
                j = 0;
            }
        }

        if (j >= p.length()) {
            return i - j;
        }

        return -1;
    }


    private static int indexKmp(String s, String p) {
        if (s == null || p == null || "".equals(p)) {
            return -1;
        }
        int i = 0, j = 0;
        int[] next = nextArray(p);
        while (i < s.length() && j < p.length()) {
            if (j == -1 || s.charAt(i) == p.charAt(j)) {
                i++;
                j++;
            } else {
                j = next[j];
            }
        }

        if (j >= p.length()) {
            return i - j;
        }

        return -1;
    }

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

推荐阅读更多精彩内容

  • 一、正则表达式的用途(搜索和替换) 1.1.正则表达式(regular expression,简称regex)是一...
    IIronMan阅读 10,114评论 0 14
  • 我们经常看很多心灵鸡汤类的文章,特别是关于成功说之类。 幸福的人有千万种,伤心的人都差不多。成功的人也有千万种,失...
    杰云飘逸阅读 291评论 0 1
  • 今天跟朋友交流,我把自己的顾虑和真实想法都说了出来,当我看到他的眼神的时候,我感受到了他眼神的变化,我看到了有信任...
    晓茂阅读 571评论 4 6
  • 最近刚刚看完《月亮与六便士》,分享一些书里比较喜欢的句子及自己的小心情。 一个人只要干了大家意料之外的事情,他的同...
    生半前的我阅读 1,046评论 2 2
  • 这次,近距离看到了国宝大熊猫。熊猫头部和身体毛色黑白相间分明,但黑非纯黑,白也不是纯白,而是黑中透褐,白中...
    清风徐来_徐丽丽阅读 352评论 0 1