KMP

http://blog.csdn.net/shakespeare001/article/details/51381251

0、关于KMP

KMP是用于字符匹配的一个常用算法。关于KMP概念、前缀、后缀概念参考文章中有详细介绍,这里就不做详细讨论,本文详细关注讨论KMP核心点,next数组的作用及求解思路,KMP算法的思路。

1、next数组是什么

next数组里面存放的是要查找的字符串前i个字符串的所有前缀、后缀相等的公共串中,最大的长度值。比如需要查找的一个子串ababcd,next[0]表示子串中前1个字符串即a的前缀和后缀中相等字符串的最大长度,因为a的前缀和后缀没有,故next[0] = 0;对于next[2],即先求出子串aba的前缀和后缀出来,前缀为a,ab,后缀有ba,a,相等的公共串为a,长度为1,因此next[2] = 1;依次可以求出。

2、next数组的作用是什么

    在我们用暴力解决字符串匹配的过程中,当我们主串的下一个字符和子串的下一个字符不相等时,此时,主串指针需要回退到最近的一个字符和子串第一个字符相等的下一个位置继续匹配。当后面有一个字符和子串第一个字符相等时,再来一遍上述的匹配过程。整个过程中,主串指针会回退很多遍,导致我们暴力破解的复杂度达到了O(n*m)。

我们再来考虑一下上面的情形,当我们主串的下一个字符和子串的下一个字符不相等时,我们主串前面的一些字符是和子串经过了匹配的,而且匹配成功了,如下图:

如果按照暴力方式,主串此时需要回退到第4个位置处的b处,继续进行扫描判断。为了优化这个过程,我们现在来考察子串,对于子串前q-1个字符串s,即s = abcab:

(1)如果字符串s存在着相等的前缀和后缀(即next[q-1]>0),这个时候我们不需要去回退主串指针,只要把我们的子串往后移动,使得我们字符串s中的前缀和后缀相等的那个前缀移动到后缀的位置处,因为前缀=后缀=主串中的部分串,这样我们直接就将子串定位到了可以进行下一趟比较探测的位置,而没有回退过主串指针。我们希望这个相等的前缀和后缀的长度越大越好(这也是为什么我们next数组中是求相等的前缀和后缀中最长的那个相等串的值),显然这样就可以匹配更多相同的元素。对于上面的例子来说就是,s = abcab,此时前缀和后缀相等的只有ab,也就是我们的next[q-1] = 2;这个时候,我们移动子串,使得我们的前缀ab移动到后缀ab处的位置,如下:

这样,我们就可以在不会退主串指针的情况下,继续进行下一趟探测比较了。上面子串的移动距离也很好计算,就是前面已经匹配的子串的字符数量-next[q-1],即5-2=3。

(2)如果字符串s中不存在相等的前缀和后缀(即next[q-1]=0),这个时候就更好办了,按照上面这个移动距离公式,可以计算出移动的距离是5-0 = 5,相当于直接移动到了i指针的下面。这里为什么可以这么移动,是因为这种情况下即使我们回退指针,在再次到i之前,我们也不能找到能够匹配到的字符串。

可以看到,我们next数组的作用,就是在我们匹配失败的时候,确定我们子串需要往后移动的距离,而避免我们的主串指针进行回退。这样可以保证主串在只遍历一遍的情况下找到子串。

因此KMP算法的重点就是如何快速的求出这个next数组出来。

3、求出next数组

经过上面的分析,知道next数组是只与子串有关与主串无关的,它记录的是子串到每个字符处那个公共前缀(或后缀)的最大长度。因此,我们现在主要就是要对子串来求出其next数组。

假设我们已经准备计算第i个位置字符的next值。我们可以利用前面已经计算出来的next值进行求解。假设已经求出的next[i-1] = k,即子串从开始到i-1处这段字符串中,最大的相等的前缀和后缀长度为k,如下图中的第二排所示:

这个时候,我们需要计算到第i个字符处next值,这个时候就有两种情况了(假设待查子串的字符串以str变量表示):

(1)如果str[i] == str[k],这种情况下,前缀往后再加上一个字符之后依然会和后缀往后加上一个字符相等,因为此时前缀和后缀加上的是同一个字符。因此,此时next[i] = next[i-1] + 1,即next[i] = k+1;

对于上图来说,就是前缀a...b加上一个字符d后,变为a..bd,后缀加上一个字符c后,变为a..bc(这里是不等的,只是为了说明一下)。

(2)如果str[i] != str[k],说明前、后缀分别加上一个字符扩展之后是不相同了,这个时候,a...b这一段是不能再用了,也就是next[i-1]的值没有考察意义了,也即k此时需要调整。那就只能缩小范围,前缀要往前收缩,后缀要往后收缩。因为此时,前缀和后缀是相同的字符串(即如上图中的前面k个字符串a...b,和后面k个字符串a...b是相同的,因此只要在前缀字符串中找出新的前缀和后缀,这个新的前缀=新的后缀=原来的后缀的一小部分)。如下图所示:

注意:上面的k有两层含义,一个指的是next[index]中的值,表示到第index处字符串相等前缀和后缀的最大长度;另一个是,由前一层含义可知最大相等的前缀长度为k,也就可以用这个k作为下标索引值,即前缀是从str[0]...str[k-1]处。

如上图,继续分析,如果str[i] != str[k],此时需要将k个字符串前缀a...b进行划分,先考察k-1处的next值,令k' = next[k-1],即a...b划分成了如下所示的样子:

这时,继续判断str[i] == str[k'],轮回到上面两种情况的判断,如果相等,即可以直接确定next[i]的值,不等又继续对str[0]...str[k'-1]处进行划分,依次下去,直到i遍历完子串。

因此,根据上述思路,我们可以编写出以下代码来实现:   

[java] view plain copy

private int[] getNextArray(char[] chs){  

int i;//字符数组的下标指示器  

int k;//前一个字符处的最大公共(相等)前、后缀子串的长度  

int[] next = new int[chs.length];  

for(i = 1,k = 0; i < chs.length; i++){  

while(k > 0 && chs[i] != chs[k])    //此处的k可以作为上面讲到的第一层含义理解  

k = next[k -1];      

if(chs[i] == chs[k]){  

               k++;  

           }  

           next[i] = k;  

       }  

return next;  

   }  

4、KMP实现

上面我们把一个重要问题next数组的求解问题解决了。下面就可以开始KMP的实现了,KMP的步骤或原理其实在第2点中 “next数组的作用是什么”已经体现出来了。我们对比第2点和第3点中next数组的求解,发现其实进行next数组求解的过程,类似于主串和子串进行匹配的过程,只不过是在next数组求解过程中,是子串和子串自己进行比较而已。因此整个KMP算法的代码过程如下:    

[java] view plain copy

public boolean kmp(String str1,String str2){  

char[] strA = str1.toCharArray();  

char[] strB = str2.toCharArray();  

int[] next = getNextArray(strB);    //获取需要匹配子串的next数组  

int i,k;  

for(i = 0,k = 0; i < strA.length; i++){  

while(k > 0 && strA[i] != strB[k])  

k = next[k-1];  

if(strA[i] == strB[k]){  

                k++;  

            }  

/*

             * 注意,这里和求next数组有一点区别,因为kmp里面是主串和子串进行比较,当子串最后一个元素都相等的时候,k就相当于是子串和主串相同的公共部分长度,

             * 而对于求next数组中的方法来说,相当于是自身和自身进行比较

             */  

if(k == strB.length){  

return true;  

            }  

        }  

return false;  

    }  


//获取next数组  

private int[] getNextArray(char[] chs){  

int i;//字符数组的下标指示器  

int k;//前一个字符处的最大公共(相等)前、后缀子串的长度  

int[] next = new int[chs.length];  

for(i = 1,k = 0; i < chs.length; i++){  

while(k > 0 && chs[i] != chs[k])    //此处的k可以作为上面讲到的第一层含义理解  

k = next[k -1];      

if(chs[i] == chs[k]){  

                k++;  

            }  

            next[i] = k;  

        }  

return next;  

    }  

经过测试,符合要求。

我们比较上面两个方法的代码,正如我们前面所说的那样,KMP过程和next数组求解过程非常类似,因此这两个方法非常类似,可以说,next数组求解过程就是自身匹配(或自身KMP)的过程。

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

推荐阅读更多精彩内容

  • 字符串匹配KMP算法详解 1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有...
    张晨辉Allen阅读 2,385评论 0 3
  • 数据结构 第8讲 KMP算法 讲这个算法之前,我们首先了解几个概念: 串:又称字符串,是由零个或多个字符组成的有限...
    rainchxy阅读 1,268评论 0 3
  • 数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章、这篇文章、还有这篇他们写得非常...
    sunhaiyu阅读 1,723评论 1 21
  • 文章大纲:1.KMP算法概念2.KMP算法中最核心的next[] 数组是如何生成的3.使用KMP算法 匹配字符串 ...
    柠檬乌冬面阅读 807评论 0 3
  • 概述:本文主要在代码层面上分析KMP的实现过程,如果您还不了解KMP的推导过程,请参考KMP(一) 模式匹配算法推...
    hehtao阅读 2,475评论 1 4