注:最近刷题刷到KMP算法,大致印象还有但是细节有些遗忘了,因此特地回顾了大一下学期数据结构所学的内容,在此进行一下记录。
一、模式匹配
KMP算法解决的的问题是模式匹配,也就是子串定位,是串处理中最重要的运算之一,具体任务描述如下:
假设有一个主串S=“a1a2...an”, 子串T=“b1b2...bm”(m<<n),子串定位则是在主串中找到子串出现的位置。若S中没有模式为T的子串,返回值为零,称为匹配失败。
二、模式匹配的经典算法
记主串为S,子串为T。从i=1, j=1开始匹配,若匹配成功(S[i]==T[j]),则i,j分别向后移动一位;若匹配失败,j退回1,i退回i-j+2,再重新开始匹配,如下图所示:
具体算法如下:
int Index(String S, String T, int pos)
{
int i=pos, j=1;
while(i<=S[0]&&j<=T[0]){
if(S[i]==T[j]) i++,j++;
else i=i-j+2, j=1; //指针回溯
}
if(j>T[0]) return i-T[0]; //匹配成功
return 0; //匹配失败
}
因此最好的时间复杂度为O(m+n),最坏的时间复杂度为O(m*n).
三、KMP
经过观察发现当每次匹配失败之后,不需要将指针i回溯,也不需要一个一个地移动j指针,因为字符串存在一些特征,当前位置匹配失败之后如果按照最传统的方法直接后移一位的话,下一步匹配还是会失败,造成了计算资源的浪费,因此提出了KMP算法。其中指针i始终是往前走的,此外还需要保留一个next数组来判断在当前位置j匹配失败后应该移动到的位置next[j]。大致的过程如下图所示:
1、具体算法如下:
int Index_KMP(String S, String T, int pos){
int i=pos, j=1;
while(i<S[0]&&j<=T[0]){
if(j==0||S[i]==T[j]) i++,j++;
else j=next[j]; //不止向后移动一位,而是移动j-next[j]+1位
}
if(j>T[0]) return i-T[0]; //匹配成功
else
return 0;
}
2、子串next数组的求解
next数组的求解主要是与子串T[1...j-1]前后缀相同子串的长度有关,如果next[j]=k,则T[1..k-1] == T[j-k+1..j-1]T[1..k-1] == T[j-k+1],因此得到如下求解公式:
具体代码如下:
void get_next(String T, int next[])
{
j=1, next[1]=0, k=0;
while(j<T[0]){
if(k==0||T[j]==T[k]){j++;k++;next[j]=k;}
else{k=next[k]}
}
}
3、进一步改进
可能会存在这样的情况,T[j]=T[next[j]],这样如果是在T[j]处失配移动到T[next[j]]处依然会失配,因此提出如下改进方案:
void get_nextval(String T, int nextval[])
{ // 求模式串T的next函数修正值并存入数组nextval
j = 1; nextval[1] = 0; k = 0;
while (j < T[0])
{
if (k == 0 || T[j] == T[k])
{
++j; ++k;
if (T[j] != T[k]) nextval[j] = k;
else nextval[j] = nextval[k];
}
else
k = nextval[k];
}
}