串的模式匹配算法---KMP
从这篇文章起陆续学习一些模式匹配算法,首先是由大神高纳德Knuth其学生morris, pratt提出的KMP算法。
1,KMP的基本思想
KMP算法是本科数据结构课上面讲过的字符串匹配算法,它的基本思想是:
当模式串与主串出现不匹配时,主串指针不回溯,将模式串向右滑动尽可能远的距离与主串中当前位置处的字符再次进行匹配。
2,KMP算法描述
设我们的主串为s1s2...sn,模式串为p1p2...pm。
当前正在比较的是主串中的字符si与模式串中的pj,当匹配失效后,假设主串的si应该与模式串中的pk比较,则此时有以下的关系式:
p1p2…pk-1 = si-k+1si-k+2…si-1 (1)
根据已经得到的匹配结果,有以下的关系式:
pj-k+1pj-k+2…pj-1 = si-k+1si-k+2…si-1 (2)
根据(1)和(2)可以得出:
p1p2…pk-1 = pj-k+1pj-k+2…pj-1 (3)
根据上面的式子(3),我们设有数组next,令next[j]=k,则next[j]表示当模式中的第j个字符与主串中的字符j失配时,在模式串中需要重新和主串中该字符进行比较的位置。由此可以得出next数组的定义:
next[j] = 0 (当j=1时)
next[j] = Max{k|1<k<j 且p1p2…pk-1 = pj-k+1pj-k+2…pj-1}
next[j] = 1 (其他情况)
根据上面的讨论,next数组只跟模式串有关系,它可以根据上面的next定义递归求得,求解过程如下:
1) next[1]=0
2) 当j>1时,设next[j]=k, 则存在关系: p1p2…pk-1 = pj-k+1pj-k+2…pj-1
a,如果pk=pj,则有 p1p2…pk-1pk = pj-k+1pj-k+2…pj-1pj 于是 next[j+1]=next[j]+1
b,如果pk!=pj,此时可以理解为模式串在匹配自己的第j个字符时失配,要将第j个字符跟第k个字符匹配:
b1,如果pk=pnext[k],则说明在主串中的第j+1个字符前存在一个长度为next[k]的最长子串,和模式串中从首字符起长度为next[k]的子串相等,即:p1p2…pnext[k]-1= pj-next[k]+1pj-next[k]+2…pj-1,这就是说 next[j+1]=next[k]+1
b2,如果pk!=pnext[k],则继续按照b1的思路向前寻找,直到模式pj和某个字符匹配成功。若不成功,则next[j+1]=1
3,KMP的实现
首先是构造数组next的例程:
1 void get_next(char *T, int lenT, int next[]) 2 { 3 int i = 0, j = 0; 4 next[0] = 0; 5 while(i < lenT) 6 if(j == 0 || T[i] == T[j-1]) 7 { 8 ++i; 9 ++j; 10 next[i] = j; 11 } 12 else 13 j = next[j-1]; 14 }
然后是KMP算法主求解过程:
1 int Index_KMP(char *S, int lenS, 2 char T*, int lenT, 3 int next[]) 4 { 5 int i = 0, j = 0; 6 7 while(i < lenS && j < lenT) 8 if(j == 0 || S[i] == T[j]) 9 { 10 j++; 11 i++; 12 } 13 else 14 j = next[j-1]; 15 if(j >= lenT) 16 return i-lenT; 17 return -1; 18 }
4,KMP的时间复杂度
设主串的长度为n,模式串的长度为m,则构造next的时间复杂度为O(m),KMP算法的时间复杂度为O(n),所以KMP算法的时间复杂度为O(n).