KMP字符串匹配算法解析

kmp算法为非常经典的字符串匹配算法。下面先讲解下最原始的字符串匹配方法,也就是暴力求解。时间复杂度为O(m*n) 下面举个例子讲解下暴力大法 如  12314123123b   match为123123 首先从第一个位置str[0]开始依次与match开始进行比较 str的第0、1、2、3与match 0、1、2、3匹配,但是4与match[3]不匹配,即str[0]位置出发不与match匹配 于是接下来从str[1]开始于match[0]重新开始匹配。这样的话str每到一个位置都要与match的开始进行匹配。所以str每一个位置的时间复杂度为match.length 所以暴力大法的时间复杂度为 str.length*match.length为N*m

下面讲下kmp提速的方法 例子1 比如  1 4 3 1 4   3 1 2 3 b                       no           1 4 3 1 2 下面我们如果从match[1]继续匹配的话将会节省大量时间。 1 4 3 1 4                 3 1 2 3 b              比对该位置                   1 4 3 1 2 大家可能会问,kmp为什么会那么做,为什么对呢?等会给大家讲解下。先引入kmp算法中必备的next数组 next数组中i位置存储为0-i-1中前缀子串和后缀子串最大匹配长度。

前缀子串就是 从0开始不包含i-1的串(必须从0开始,长度小于i-2) 后缀子串就是以i-1结尾,不包含0位置的串(必须以i-1结尾,长度为i-2), 首先0位置没用前缀和后缀则为-1,1位置也没有则为0,从2开始。下面举个例子

如  1  2  3  1  2  3                           i位置   则前缀子串可以为 1,12,123,1231,12312                                        后缀子串可以为 3,23,123,3123,23123 next为 -1 0 0 0 1 2 可以看出前缀子串和后缀子串最大匹配长度为3。当然前缀和后缀可以重合 如下面的例子  1 2 3 1 2 3 1 4                     i位置  前缀为:1,12,123,1231,12312,123123                                后缀为:  1,31,231,1231,31231,231231 则最大匹配长度为4 next为 -1 0 0 0 1 2 3 1 有了next的概念之后 讲解下为什么可以提速。也就是为什么例子1中为什么kmp会跳过str[1 2 3]的比对而直接str[4]比对match[1] 这是因为在这个不检查区域中,从任何一个字符出发都
肯定不会匹配出match,为什么呢?下面解释一下。 首先我们必须假设next数组都求的正确,都是各个位置的前缀和后缀子串的最大匹配长度。 下面画图解释一下
《KMP字符串匹配算法解析》
     从str[i]开始到A处与match的B处不匹配(str[i]到A之前与match开始到B之前完全匹配)。这时我们要跳过不检查区域直接将match前移。移动为图3位置继续与图1比较。 跳过不检查区域X是因为从这个不检查区域中,从任何一个字符出发都肯定不会匹配出match。 我们假设在该区域某字符出发匹配到match字符子串d。d肯定比c长,以d为后缀所以对应match的前缀子串区域e,后缀d’。 e,d’与d不光长度相同,同时字符也完全相同。我们可以看出e比a大,也就是比b大。但是match中B的位置存的是其之前前缀与后缀最大匹配长度 但是e居然比a要大,显然不对。所以区域x不可能存在匹配出match串,所以忽略不检查区域x。match之间前移为图3位置。匹配D是否与A相等(a和b为最大匹配的前后缀)。 下面讲解下代码实现。 首先是next数组的获得

public static int[] getNext(char[] match){
        int cn=0,post=0;
        int[] next=new int[match.length];
        next[0]=-1;
        next[1]=0;
        post=2;
        while(post<match.length){
            if(match[post-1]==match[cn]){
                next[post++]=++cn;

            }else if(cn>0){
                cn=next[cn];
            }else next[post++]=0;
        }
        return next;
    }

next第一位没用前缀和后缀,初始化为-1,第二位也没有初始化为0.然后从第三位2开始求。 cn为前移标志位。post指当前位置 当cn前跳到0号位置,说明该post位置的前缀和后缀子串没有匹配,所以该位置赋值为0. 举个例子讲解下的吧 比如       0 1 2 3 4 5 6 7 8 1 2 3 1 2 3 1 4 3 首先前两位默认-1 和0 然后从post=2,cn=0开始 首先它的前一位不等于match[cn=0],并且cn=0所以next[post]=0,post+1 post=3    match[post-1]!=match[cn=0],并且cn=0,所以next[post=3]=0,post+1 post=4   match[post-1]=match[cn],故next[post]=1(前一个位置的前缀1,后缀1所以最大匹配为1),post+1 cn+1 post=5   match[post-1]=match[cn],故next[post]=2(前一个位置的前缀为12,后缀为12,最大匹配为2。实际上cn已经记录最大匹配长度),post+ cn+1 post=6  match[post-1]=match[cn],故next[post]=3 (前一个位置的前缀为123,后缀为123,最大匹配为3)post+ cn+1 post=7   match[post-1]=match[cn],故next[post]=4 (前一个位置的前缀为1231,后缀为1231,最大匹配为4)post+ cn+1 post=8   match[post-1]!=match[cn],并且cn>0,故cn前移为cn=next[cn] (前移的这个位置实际是已匹配最长前缀串的缩减,
比如该位置前已经匹配最长前缀实际为1231,在下一个位置元素2不匹配,于是把前缀缩小,缩小到该位置之前元素与post之前位置完全匹配,cn变为next[cn](=1)比对。 有点绕口,实际就是  原本4元素之的前缀1231和后缀的1231匹配了,但是3的后缀12314与前缀的12312不匹配,这时候我们不应该从头开始,而是缩减元素3之前的前缀和后缀子串,缩减为前缀12与后缀14比较,还是不相等于是继续缩减前缀为1后缀为4,仍不相等(但是cn=0了)所以next[post=8]的值为0,意思为该位置之前的前缀和后缀无相同的匹配长度
next为 -1 0 0 0 1 2 3 4 0     next数组求完,下面就是字符串匹配了,直接上代码

 public static int kmpCom(String s,String m){
    	char[] str=s.toCharArray();
    	char[] match=m.toCharArray();
    	int si=0;
    	int mi=0;
    	int[] next=new int[match.length];
    	next=getNext(match);//首先得到next数组,用上面的函数
    	while(si<str.length&&mi<match.length){
    		if(str[si]==match[mi]){
    			si++;
    			mi++;
    		}else if(next[mi]==-1){
    			si++;
    		}else{
    			mi=next[mi];
    		}
    	}
    	return mi==match.length?si-mi:-1;
    }

si为s源字符串位置标记,mi为match字符串标记 如果str[si]=match[mi],则二者都自加 若不相等并且next[mi]=-1说明该位置为不检查区域。 若不相等并且next[mi]!=-1,这时就是kmp提速的操作如图3所示,match字符串向前跳,继续比对。 kmp算法可以将暴力破解的时间复杂度从N*m降到O(N)

以上个个人的对kmp算法的理解。参考主要看的是牛客网大神左程云老师的书进行的整理。可以推荐大家看下。

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/u010485491/article/details/52032322
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞