一.KMP算法的介绍
KMP算法(Knuth–Morris–Pratt Algorithm):这是一种利用已匹配的信息来跳过不必要的比较的字符串匹配算法,它的核心思想是计算模式串的前缀函数,表示模式串的每个前缀子串中,最长的既是前缀又是后缀的字符串的长度。然后在匹配过程中,如果发现不匹配,就根据前缀函数来决定模式串应该向右移动多少位,从而避免重复比较已匹配的部分。这种算法的时间复杂度是O(m+n)。
以下算法均采用主串T="acbccadbacbacc",模式串P="acbacc"。
KMP算法伪代码:
int KMP(char *T,char *P){
int T_len=len(T); //文本串T的长度
int P_len=len(P); //模式串P的长度
int i=0,j=0;
while(i<T_len&&j<P_len){
if(T[i]==P[j]){
i++;
j++;
}else{
j=next[j]; //失配,指针回退到对应next数组对应的下标元素
}
}
return j<P_len?-1:i-j; //成功返回下标,反之-1;
}
KMP算法的演示过程:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
文本串 | a | c | b | c | c | a | d | b | a | c | b | a | c | c |
第一次 | a | c | b | a | c | c | ||||||||
第二次 | a | c | b | a | c | c | ||||||||
第三次 | a | c | b | a | c | c | ||||||||
第四次 | a | c | b | a | c | c | ||||||||
第五次 | a | c | b | a | c | c | ||||||||
第六次 | a | c | b | a | c | c | ||||||||
第七次 | a | c | b | a | c | c |
二.BM算法的介绍
BM算法(Boyer–Moore Algorithm):这是一种从右往左匹配的字符串匹配算法,它的核心思想是利用模式串中的信息来跳过尽可能多的主串字符,从而达到快速匹配的目的。BM算法主要使用了两种规则:坏字符规则和好后缀规则。坏字符规则是指,如果在某个位置发现不匹配,就将模式串中最靠右的与主串中该字符相同的字符与之对齐,如果没有这样的字符,就跳过整个模式串。好后缀规则是指,如果在某个位置发现不匹配,就将模式串中与已匹配的后缀子串相同的最长子串与之对齐,如果没有这样的子串,就找到已匹配的后缀子串的最长前缀,使之与模式串的前缀对齐。这种算法的最坏时间复杂度是O(m*n)。
当然BM算法还有跟KMP算法一样。还有好后缀规则,改进算法思想中没有提及,感兴趣的同学可以自行查阅。
BM算法的演示过程:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
文本串 | a | c | b | c | c | a | d | b | a | c | b | a | c | c |
第一次 | a | c | b | a | c | c | ||||||||
第二次 | a | c | b | a | c | c | ||||||||
第三次 | a | c | b | a | c | c | ||||||||
第四次 | a | c | b | a | c | c |
三.KMP算法的改进思路
KMP算法解决了BF算法中文本指针i的回溯问题,但它仍有局限性。在KMP算法中,指针i每次只能向前移动一位,且模式串的跳转距离总是小于当前的j值。这意味着当字符不匹配的可能性较时,j值通常较小,导致KMP算法的总体匹配效率并不理想。为了提高效率,研究者们提出了改进的KMP算法,如改进的next数组计算方法,这些改进旨在减少不必要的比较,使算法更加高效。改进算法在KMP算法不仅保证主串T指针i不回溯的问题,还能保证主串每次比较结束后,尽可能多的移动。改进算法的最坏时间复杂度为O(m+n),理论上也优于BM算法。
改进算法的执行过程描述如下:
首先,需要先对模式串进行处理,得到next数组和BM算法坏字符跳转距离数组BMc。
当发生不匹配时,第一个步骤是先调用KMP算法进行移动,移动之后再次比较。第二个步骤就是调用BM算法的坏字符跳转规则,使主串T再次移动。重复执行上述两个步骤,直到主串结束或匹配成功。
改进算法的流程图如下:
改进算法的主体伪代码描述:
while(i<T.length){
if(j==-1||T[i]==P[j]){
if(匹配成功){
i++;j=0;//找到一个匹配,寻找下一个
}
else{i++;j++;}
}
else{//计算模式串移动next[j]后末字符在文本T中的
位置
tLast=T[i+P_len-next[j]-1];
//T该位置的文本字符与P末字符不匹配
if(tLast!=PLast){
j=0;
i=i+坏字符跳转;
}else{j=next[j]}//按照KMP继续匹配
}
}
改进KMP算法的一般思路是增加预处理,最大程度利用部分匹配值,减少字符的比较次数。
改进算法的演示过程如下:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
文本串 | a | c | b | c | c | a | d | b | a | c | b | a | c | c |
第一次 | a | c | b | a | c | c | ||||||||
第二次 | a | c | b | a | c | c | ||||||||
第三次 | a | c | b | a | c | c |
在第一次遇到不匹配时,先计算出该位置的文本字符 为 tLast = T[i + m - next[ j] - 1] = T[3 + 6 - 0 - 1] = T[8] = ‘a’, 模式串的末字符为c,a与c不匹配,则用字符a进行坏字符匹 配,第一步next数组跳的距离为 j - next[ j] = 3 - next[3] = 3, 第二步用bmBc数组跳的距离 bmBc[a] = 2 ,两步的跳跃距 离和就是i移动的距离,即i值等于i + bmBc(a) - next[ j] = 5, 此时 j 值重新赋值为0,再进行下一次窗口的匹配,如表 4所示;若两字符匹配,则按照KMP的匹配过程进行比 较,即 j 值等于 next[ j] 值,再进行下一轮的比较。