今天终于弄明白了如何计算KMP匹配算法中的next数组。
KMP匹配中的next数组的第i个元素j=next[i],表示模式串pattern中的前i个字符pattern[0..i-1]中,真前缀pattern[0..j-1]和真后缀pattern[i-j-2...i-1]相等,这样再进行模式匹配时,如果主串中当前字符和pattern中第i个字符匹配时unmatch,此时,不用回溯主串的指针,而是使用pattern的next[i]字符与主串的当前字符匹配。(通过画图和清晰描述)
在计算模式串的next值时,可以根据前一个元素的next计算结果得到。根据next[i-1]计算next[i]:
记j=next[i-1],则pattern[0..j-1]与pattern[i-j-3..i-2]相等,然后判断pattern[j]和pattern[i-1]是否相等,
如果pattern[j]==pattern[i-1],那么next[i]=j+1=next[i-1]+1;
如果pattern[j]!=pattern[i-1],此时可以将S‘=pattern+(i-j-3)看作主串,与模式串pattern匹配时,在pattern[j]出发生了不匹配,那么根据KMP匹配算法,应该使用pattern的next[j]与S’的当前元素(pattern的第i-1元素)进行匹配,直至找到j‘时pattern[i-1]==pattern[j’]相等,此时next[i]=j‘+1。
通过以上分析得到如下计算next数组的方法:
int* KMPGetNext(const char* pattern){
int n = strlen(pattern);
if (n==0) return NULL;
int* next = new int[n];
next[0] = -1; //next[0]总是为0,表示当模式串的第一个字符不匹配时,pattern[0]应该和主串的下一个字符进行匹配
int preMatchCount = -1;
for (int i=1; i<n; i++){
//查找与pattern[i-1]匹配的字符
while (preMatchCount>=0 && pattern[i-1]!=pattern[preMatchCount])
preMatchCount = next[preMatchCount];
preMatchCount++;
next[i] = preMatchCount;
}
return next;
}
该方法对于模式串abcabcad计算得到的next数组是:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
a | b | c | a | b | c | a | d |
-1 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
但是在上述next数组中,如pattern[3]匹配失败时,将使用pattern[0]进行再次匹配,而pattern[0]==pattern[3],显然也是匹配失败的,同样pattern[4]匹配失败时,将使用pattern[1]进行再次匹配,显然也是匹配失败的,所以可以对上述GetNext方法进行修改,减少不必要的匹配,如下:
int* KMPGetNext(const char* pattern){
int n = strlen(pattern);
if (n==0) return NULL;
int* next = new int[n];
next[0] = -1; //next[0]总是为0,表示当模式串的第一个字符不匹配时,pattern[0]应该和主串的下一个字符进行匹配
int preMatchCount = -1;
for (int i=1; i<n; i++){
//查找与pattern[i-1]匹配的字符
while (preMatchCount>=0 && pattern[i-1]!=pattern[preMatchCount])
preMatchCount = next[preMatchCount];
preMatchCount++;
if (pattern[i]==pattern[preMatchCount])
next[i] = next[preMatchCount];
else
next[i] = preMatchCount;
}
return next;
}
KMP模式匹配方法:
在主串与模式串进行匹配时,如果在patter[i]和S[j]出不匹配,那么下次使用pattern[next[i]]与S[j]进行匹配。主串的索引不需要回溯,总共时间复杂度O(n)(加上next数组的计算时间,总共时间复杂度O(m+n),m和n分别是模式串和主串的长度)
实现如下:
int KMPStringMatch(const char*S, const char* pattern){
int* next = KMPGetNext(pattern); //计算pattern的next数组
int indexOfPattern = 0; //模式串索引
int indexOfS = 0; //主串索引
while (S[indexOfS]!='\0' && pattern[indexOfPattern]!='\0'){
if (S[indexOfS] == pattern[indexOfPattern]){//主串字符和模式串字符相等时,继续匹配下一个字符
indexOfS++; indexOfPattern++;
} else {
indexOfPattern = next[indexOfPattern];
if (indexOfPattern<0){//遇到next值为-1的情况,需要特殊处理
indexOfPattern = 0;
indexOfS++;
}
}
}
delete[] next;
if (pattern[indexOfPattern] == '\0'){//模式串已经匹配到最后时,说明匹配成功
return indexOfS-strlen(pattern);
} else {
return -1;
}
}
找到字符串A在字符串B中出现的次数,可以重复使用字母,比如A: aba B: ababa, 那么返回2.
此题可使用KMP匹配的思路进行求解,模式串pattern末尾添加一个不与任何字符匹配的字符,然后计算next数组,并且在与主串匹配时,当匹配到模式串末尾时,表示匹配成功一个子串,然后从末尾字符的next开始继续进行匹配。
实现如下:
int* KMPbilityGetNext(const char* pattern){//在pattern末尾假设增加一个和任何字符都不匹配的虚字符
int n = strlen(pattern);
if (n==0) return NULL;
int* next = new int[n+1];
next[0] = -1; //next[0]总是为0,表示当模式串的第一个字符不匹配时,pattern[0]应该和主串的下一个字符进行匹配
int preMatchCount = -1;
for (int i=1; i<n; i++){
//查找与pattern[i-1]匹配的字符
while (preMatchCount>=0 && pattern[i-1]!=pattern[preMatchCount])
preMatchCount = next[preMatchCount];
preMatchCount++;
if (pattern[i]==pattern[preMatchCount])
next[i] = next[preMatchCount];
else
next[i] = preMatchCount;
}
next[n] = preMatchCount; //pattern末尾假设有一个和任何字符都不匹配的虚字符
return next;
}
int MatchCount(const char* S, const char* pattern){
int* next = KMPbilityGetNext(pattern);
int matchCount = 0; //匹配成功次数
int indexOfPattern = 0;
int indexOfS = 0;
while (S[indexOfS]!='\0'){
if (S[indexOfS] == pattern[indexOfPattern]){//主串字符和模式串字符相等时,继续匹配下一个字符
indexOfPattern++;
if (pattern[indexOfPattern]=='\0'){//匹配成功
matchCount++;
indexOfPattern = next[indexOfPattern];
} else {
indexOfS++;
}
} else {//字符匹配失败
indexOfPattern = next[indexOfPattern];
}
if (indexOfPattern<0) {//遇到next值为-1的情况,需要特殊处理
indexOfPattern = 0;
indexOfS++;
}
}
delete[] next;
return matchCount;
}