朴素的模式匹配算法
子串的定位操作通常称为串的模式匹配,是串中最重要的操作之一。
假设要从下面的主串S=”goodgoogle”中,找到T=”google”这个子串的位置,通常通过以下几个步骤:
- 主串S第一位开始,S和T前三个字母都匹配成功,但S第四个字母是d而T的是g。第一位匹配失败。
- 主串S第二位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败。
- 主串S第三位开始,主串首字母是o,要匹配的T首字母是g,匹配失败。
- 主串S第四位开始,主串S首字母是d,要匹配的T首字母是g,匹配失败。
- 主串S第五位开始,S与T,6个字母全匹配,匹配成功。
简单来说,就是对主串的每个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,知道匹配成功或全部遍历完成为止。之前使用串其他操作实现模式匹配(Index方法),现不用串其他操作,使用基本的数组实现,假设主串S和要匹配的子串T的长度均保存在S[0]和T[0]中,具体代码如下:
/*返回子串T和主串S中第pos个字符之后的位置。若不存在,则函数返回值为0*/
/*T非空,1<=pos<=StrLength(S)*/
int Index(String S,String T,int pos){
int i=pos; //i-主串S中当前位置下标,若pos不为1,从pos位置开始匹配
int j=1; //j用于子串T中当前位置的下标值
while(i <= S[0] && j <= T[0]){ //i小于S长度且j小于T长度时循环
if(S[i]==S[j]){ //两字母相同时,继续循环,向后移动
++i;
++j;
}else{ //俩字母不同时,i退回到上次匹配首位的下一位,j重置为1
i=i-j+2;
j=1;
}
}
if(j>T[0]) return i-T[0]; //若j比T的长度大时,返回首位,否则返回0
else return 0;
}
小结:最好情况的时间复杂度为 O(n+m) ,最坏情况的时间复杂度为 O[(n−m+1)∗m] 。所以,这个算法太低效了。
KMP模式匹配算法
算法原理
对于所有在子串中有与首字符相等的字符,是可以省略一部分不必要的判断步骤。朴素的模式匹配中有很多重复的遍历步骤,主串的i是需要不断回溯来完成,而分析发现,这种回溯可以是不需要的,而KMP模式匹配算法就是避免这种不必要的回溯发生。
既然i值不回溯,也就是不能变小,要考虑的变化就是j值,观察发现,T串的首字符与自身后面字符比较,发现如果有相等字符,j值的变化会不同,即,j值的变化与主串没什么关系,关键取决于T串的结构是否有重复的问题。
j值得多少取决于当前字符之前的串的前后缀的相似度。
T串各位置的j值变化定义为一个数组next,那么next的长度就是T串的长度,函数定义如下: