标签:
KMP算法是字符串匹配处理中一种非常高效的算法,它的时间复杂度可以达到O(N+M),远优于普通匹配的O(NxM)。它最早是由Knuth,Morris,Pratt共同提出。
普通的字符串匹配,假设从母串的A位置开始匹配,在某个位置B当母串和子串失配的时候匹配的起点会回溯到A+1处重新开始。而从A到B的中有一些位置的字符的信息已经获知,此时是可以提前知道从A之后的哪个位置重新开始匹配是有可能成功(或者哪些位置开始匹配必然失败).
比如,以上的字符串匹配,母串从index 0开始,当匹配到index = 5的时候失配,此时一般的字符串匹配是将母串回溯到index = 1重新开始,此时可以发现匹配的第一个即失效。其实,如果需要从index=1开始进行重新匹配,则需要子串中[0, 4]的部分和母串中[1,5]的部分相同,由于之前母串的[0,4]和子串的[0,4]匹配,这说明需要 子串中[0,4]的部分和[1,5]的部分相同,这只需要子串的信息即可获知。
那么,如果利用可以获知的子串的信息,就不需要母串回溯到index=1。如图中所示,如果知道失配之前的子串[0,4]部分中前缀DE和后缀DE相同(最长相同的前后缀),则只需要将母串指针回溯到 index=3(3 = 5 - 2)即可重新开始匹配,且能够保证从此处之前开始匹配肯定不能匹配成功。
因为如果在index=i(i< 3)开始匹配能够成功,则说明子串中[0, 4 - i] = 母串中[i, 4] = 子串中[i, 4],而i < 3则可知区间[0, 4-i]的长度大于2,即大于已知的最长相同前后缀长度2,矛盾!
KMP思想
KMP的思想就是利用可以提前获知的子串中的前后缀信息,来减少回溯距离,得到下一次重新开始匹配的位置。这需要对子串中每个位置i都确定子串中[0, i]相同最长前缀和后缀的长度。在KMP中是使用一个next数组来保存。
KMP匹配流程
(1)获得子串每个位置i确定的子子串[0,i]中的最长相同前后缀长度,用next数组保存
(2)母串当前匹配点为pos(pos开始为0),pos对应子串的位置索引为 p,当匹配到子串位置p时候失配,则查找子串next数组的,更新下次的母串匹配点为 pos = pos + p - next[p-1] ,同时p变为 p = next[p-1]
(3)回溯之后,重新开始匹配,直到匹配成功或者到母串结束仍未成功,则返回失败
next数组
next数组保存每个位置确定的子串的最长前后缀的长度。其生成的时候按照index从0开始增加,当到达index = i的时候,可以根据index = 0, 1, 2.. i - 1的next相关信息得到。
如上图所示,当前index = i,可以知道sub_str中,[0, next[i-1]-1]部分和[i - next[i-1], i - 1]部分是相同的,即图中的黄色字体区域。此时若:
(1) sub_str[next[i-1]] == sub_str[i]
显然,next[i] = next[i-1] + 1
(2)sub_str[next[i-1]] != sub_str[i]
可以确定next[i]的值必定不大于next[i-1],记p = next[i] - 1,假设next[i] > next[i-1],p >= next[i-1],则字符串中[0, p]和[i-p, i]区域相同(即图中绿色方块区域),且[0, p]包含[0,next[i-1]-1],[i-p,i]包含[i-next[i-1], i-1],则[0, p-1]和[i-p, i-1]相同(即图中的[0, p-1]和[P‘-i-1]的黄色字体区域),此时p> next[i-1],这与next[i-1]为[0,i]区域内最长相同前后缀长度矛盾!!
因此,next[i]位于[0, next[i-1]-1]中,即j = next[i-1]-1,还可以证明 next[i]不大于next[j].
假设next[i]大于next[j],记p = next[i]-1. 此时图中的括号标记起来的D区域和E区域相同。由于[0, next[i-1]-1]和[i-next[i-1], i-1]相同(即图中的F和G区域),则图中的A区域和B区域就相同(因为A和B分别相对于F和G只是减少了最后一个字符),而B和C是相同的,则A区域和C区域就是相同的。此时,在区域[0, j]中A和C相同,导致p称为最长相同前后缀长度,且p > next[j]。这与最大相同前后缀长度next[j]矛盾!!!
因此,next[i]的位置只能在[0, next[j]]之间,此时判断sub_str[next[j]]是否等于sub_str[i],若相同,则next[i] = next[j] + 1,否则继续将j移动到next[j-1]...
(1) next数组的生成
void GenerateNext(const char* sub_str, int* next, int len){ next[0] = 0; //next[i]表示sub_str[0, i]中最长相同前后缀的长度 for (int i = 1; i < len; i++){ int j = next[i - 1]; while (sub_str[j] != sub_str[i]){ //不断回溯,每次利用next数组来确定下一个需要判断的位置 if (j == 0){ j = -1; break; } j = next[j - 1]; } if (j < 0) next[i] = 0; else next[i] = j + 1; } }
(2) kmp匹配
//返回母串中有多少个子串 int Kmp(char* mas_str, const char* sub_str, int* next, int len){ int count = 0; char* pos = mas_str; //母串中当前检查的位置 int i = 0; //子串中当前检查的位置 char* end = mas_str + strlen(mas_str); while (pos + len - i <= end){ while (i < len && *(pos + i) == sub_str[i]){ i++; } if (i == len){ count++; } if (i == 0){ //i == 0时,没有next[i-1],单独处理 pos++; } else{ pos += (i - next[i - 1]); //利用next数组进行更新 i = next[i - 1]; } } return count; }
标签:
原文地址:http://www.cnblogs.com/gtarcoder/p/4813092.html