标签:style blog http color io strong ar 2014 art
KMP代码:
1 int KmpSearch(char* s, char* p) 2 { 3 int i = 0; 4 int j = 0; 5 int sLen = strlen(s); 6 int pLen = strlen(p); 7 while (i < sLen && j < pLen) 8 { 9 //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++ 10 if (j == -1 || s[i] == p[j]) 11 { 12 i++; 13 j++; 14 } 15 else 16 { 17 //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j] 18 //next[j]即为j所对应的next值 19 j = next[j]; 20 } 21 } 22 if (j == pLen) 23 return i - j; 24 else 25 return -1; 26 }
拿例子来说,当S[10]跟P[6]匹配失败时,KMP不是简单的如朴素匹配那样把模式串右移一位,而是执行第②条指令:“如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]”,即j 从6变到2(后面我们将求得P[6],即字符D对应的next 值为2),所以相当于模式串向右移动的位数为j - next[j]位(j - next[j] = 6-2 = 4位)。
(1)next数组是针对pattern的,就是模式例如给文本S:BBC_ABCDAB_ABCDABCDABDE P:ABCDABD.
next数组是针对p的,
(2)求next数组,就是求出最长前缀后缀,然后左移一位,第一个为-1.
基于之前的理解,可知计算next 数组的方法可以采用递推:
举个例子,如下图,根据模式串“ABCDABD”的next 数组可知失配位置的字符D对应的next 值为2,代表字符D前有长度为2的相同前缀和后缀(这个相同的前缀后缀即为“AB”),失配后,模式串需要向右移动j - next [j] = 6 - 2 =4位。
向右移动4位后,模式串中的字符C继续跟文本串匹配。
对于pattern的前j+1个序列字符:
(这里是根据P[j]==P[k]==C,得出n[j+1]=n[j]+1=k+1=3,k=2,j从0开始)
模式串的后缀:ABDE
模式串的前缀:ABC
前缀右移两位: ABC
1 void GetNext(char* p,int next[]) 2 { 3 int pLen = strlen(p); 4 next[0] = -1; 5 int k = -1; 6 int j = 0; 7 while (j < pLen - 1) 8 { 9 //p[k]表示前缀,p[j]表示后缀 10 if (k == -1 || p[j] == p[k]) 11 { 12 ++j; 13 ++k; 14 next[j] = k; 15 } 16 else 17 { 18 k = next[k]; 19 } 20 } 21 }
(4)next数组优化
行文至此,咱们全面了解了暴力匹配的思路、KMP算法的原理、流程、流程之间的内在逻辑联系,以及next 数组的简单求解(《最大长度表》整体右移一位,然后初值赋为-1)和代码求解,最后基于《next 数组》的匹配,看似洋洋洒洒,清晰透彻,但以上忽略了一个小问题。
比如,如果用之前的next 数组方法求模式串“abab”的next 数组,可得其next 数组为-1 0 0 1(0 0 1 2整体右移一位,初值赋为-1),当它跟下图中的文本串去匹配的时候,发现b跟c失配,于是模式串右移j - next[j] = 3 - 1 =2位。
右移2位后,b又跟c失配。事实上,因为在上一步的匹配中,已经得知p[3] = b,与s[3] = c失配,而右移两位之后,让p[ next[3] ] = p[1] = b 再跟s[3]匹配时,必然失配。问题出在哪呢?
问题出在不该出现p[j] = p[ next[j] ]。为什么呢?理由是:
所以,咱们得修改下求next 数组的代码。
1 //优化过后的next 数组求法 2 void GetNextval(char* p, int next[]) 3 { 4 int pLen = strlen(p); 5 next[0] = -1; 6 int k = -1; 7 int j = 0; 8 while (j < pLen - 1) 9 { 10 //p[k]表示前缀,p[j]表示后缀 11 if (k == -1 || p[j] == p[k]) 12 { 13 ++j; 14 ++k; 15 //较之前next数组求法,改动在下面4行 16 if (p[j] != p[k]) 17 next[j] = k; //之前只有这一行 18 else 19 //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]] 20 next[j] = next[k]; 21 } 22 else 23 { 24 k = next[k]; 25 } 26 } 27 }
(5)优化版next如何迅速找到
可 能有些读者会问:原始next 数组是前缀后缀最长公共元素长度值右移一位, 然后初值赋为-1而得,那么优化后的next 数组如何快速心算出呢?实际上,只要求出了原始next 数组,那么可根据原始next 数组快速求出优化后的next 数组。还是以abab为例,如下表格所示:
(6)KMP算法时间复杂度
咱们先来回顾下KMP匹配算法的流程:
“KMP的算法流程:
我们发现如果某个字符匹配成功,模式串首字符的位置保持不动,仅仅是i++、j++;如果匹配失配,i 不变(即 i 不回溯),模式串会跳过匹配过的next [j]个字符。整个算法最坏的情况是,当模式串首字符位于i - j的位置时才匹配成功,算法结束。
所以,如果文本串的长度为n,模式串的长度为m,那么匹配过程的时间复杂度为O(n),算上计算next的O(m)时间,KMP的整体时间复杂度为O(m + n)。
标签:style blog http color io strong ar 2014 art
原文地址:http://www.cnblogs.com/zmlctt/p/3949605.html