KMP

来源:互联网 发布:蓝月亮网络营销策划 编辑:程序博客网 时间:2024/05/16 05:19
                   Kmp详解[    朴素匹配算法    咱们先来看朴素匹配算法。假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置如果当前字符匹配成功,即S[i+j] == P[j],则i 不变,j++,继续匹配下一个字符;如果失配,即S[i+j]! = P[j],令i++,j = 0,即每次匹配失败时,模式串P相对于文本串S向右移动一位。    换言之,只要模式串匹配失败,那就往右边移动一位,简单直接,也干脆暴力。    假定文本串S为“acaabc”,模式串P 为“aab”,那么模式串去匹配文本串的整个过程如下图所示:    那KMP做了什么改进呢?KMP其实是在一步步往后匹配的过程中,后面的匹配会设法利用前面的匹配信息,从而减少不必要的匹配。KMP算法原理    咱们首先给出KMP算法的结论:假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置如果当前字符匹配成功,即S[i] == P[j],令i++,j++,继续匹配下一个字符;如果失配,即S[i] != P[j],令 i 不变,j = next[j],模式串P相对于文本串S向右移动了至少1位(换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数:j - next[j] > =1)步骤①寻找最长前缀、后缀对于Pj = p0 p1 ...pj-1 pj,寻找模式串Pj中长度最大且相等的前缀和后缀即寻找满足条件的最大的k,使得p0 p1 ...pk-1 pk = pj-k pj-k+1...pj-1 pj。也就是说,k是模式串中各个子串的前缀后缀的公共元素的长度,所以求最大的k,就是看某个子串的哪个前缀后缀的公共元素最多。举个例子,如果给定的模式串为“abaabcaba”,那么它的各个子串的前缀后缀的公共元素的最大长度值如下表格所示:②求next数组根据第①步骤中求得的各个前缀后缀的公共元素的最大长度求得next 数组,相当于前者右移一位且初值赋为-1,如下表格所示:③匹配失配,模式串向右移动的位数为:j - next[j]           注:j 是模式串中失配字符的位置,且 j 从0开始计数。    接下来,分别具体阐述这3个步骤。寻找最长前缀后缀    如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:    也就是说,原字符串对应的各个前缀后缀的公共元素的最大长度表为(下简称《最大长度表》):基于《最大长度表》匹配    因为模式串中首尾可能会有重复的字符,故可得出下述结论:失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值    下面,咱们就结合之前的《最大长度表》和上述结论,进行字符串的匹配。如果给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,如下图所示:        1. 当模式串最后一个字符D跟文本串匹配时失配,显而易见,模式串需要向右移动。但向右移动多少位呢?因为此时已经匹配的字符数为6个(ABCDAB),然后根据《最大长度表》可得字符B对应的长度值为2,所以根据之前的结论,可知需要向右移动6 - 2 = 4 位。2. 模式串向右移动4位后,发现C处再度失配,因为此时已经匹配了2个字符(AB),且上一个字符B对应的最大长度值为0,所以向右移动:2 - 0 =2 位。           3. A与空格失配,向右移动1 位。4. 继续比较,发现D与C 失配,故向右移动的位数为:已匹配的字符数6减去上一位字符B对应的最大长度2,即向右移动6 - 2 = 4 位。           5. 经历第4步后,发现匹配成功,过程结束。          最大长度表引出next 数组    由上文,我们已经知道,字符串“ABCDABD”各个前缀后缀的最大公共元素长度分别为:    而且,根据这个表可以得出下述结论失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值    上文利用这个表和结论进行匹配时,我们发现,当匹配到一个字符失配时,其实没必要考虑当前失配的字符,更何况我们每次失配时,都是看的失配字符的上一位字符对应的最大长度值。如此,便引出了next 数组。    给定字符串“ABCDABD”,可求得它的next 数组如下:    把next 数组跟之前求得的最大长度表对比后,不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。意识到了这一点,你会惊呼原来next 数组的求解竟然如此简单!从而有失配时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值    而后,你会发现,无论是基于《最大长度表》的匹配,还是基于next 数组的匹配,两者得出来的向右移动的位数是一样的。不信的话,咱们可以来看看具体过程。基于《next 数组》匹配    下面,我们来基于next 数组进行匹配。1. 匹配到字符D时失配,由于 j 从0开始计数,故数到失配的字符D时 j 为6,且字符D对应的next 值为2,所以向右移动的位数为:j - next[j] = 6 - 2 =4 位2. 向右移动4位后,C再次失配,向右移动:j - next[j] = 2 - 0 = 2 位            3. 移动两位之后,A 跟空格不匹配,再次后移1 位4. D处失配,向右移动 j - next[j] = 6 - 2 = 4 位           5. 匹配成功,过程结束。              匹配过程一模一样。也从侧面佐证了,next 数组确实是只要将各个最大前缀后缀的公共元素的长度值右移一位,且把初值赋为-1 即可。基于《最大长度表》与基于《next 数组》等价    其实,利用next 数组进行匹配失配时,模式串向右移动 j - next [ j ] 位,等价于已匹配字符数 - 失配字符的上一位字符所对应的最大长度值。为什么呢?1j 从0开始计数,那么当数到失配字符时,j 的数值就是已匹配的字符数;2由于next 数组是由最大长度值表整体向右移动一位(且初值赋为-1)得到的,那么失配字符的上一位字符所对应的最大长度值,即为当前失配字符的next 值。    那为何本文不直接利用next 数组进行匹配呢?因为next 数组不好求,而一个字符串的前缀后缀的公共元素的最大长度值很容易求,例如若给定字符串“ababa”,要你求其next 数组,则乍一看,无从求起,而要你求其前缀后缀公共元素的最大长度,则很容易得出是:0 0 1 2 3,如下表格所示:        然后这5个数字 全部整体右移一位,且初值赋为-1,即得到其next 数组:-1 0 0 1 2。next 数组的理解    next 负责把模式串向前移动,且当第j位不匹配的时候,用第next[j]位和主串匹配,就像打了张“表”。此外,next 也可以看作有限状态自动机的状态,在已经读了多少字符的情况下,失配后,前面读的若干个字符是有用的。

0 0
原创粉丝点击