KMP的算法分析
KMP解决问题:清除BF算法中主串S指针出现的回溯情况,即当主串S和子串T在某个字符不匹配的时侯,主串S的指针位置不变,改变子串T的指针位置,使主串和子串的字符匹配
算法思路:创建一个next数组,当出现主串字符与子串字符不匹配时,将模式串T的指针 j 移动到next[j]的位置,i位置不变(即没有回溯),进而使得主串字符和子串字符匹配,进行下一步的匹配
next数组的创建思路
按照之前的描述,当主串从第pos位置开始字符匹配,一直向右移动到第 i 位,同时子串也一直向右移动到第 j 位( i 和 j 的值并不相同,因为主串从第pos位置开始的,若pos==1,则有 i == j,若不相等,则j == i -pos +1)这时S [ i ]和T [ j ]不相同,由KMP算法,子串T的指针 j 移动到next[ j ]的位置,假设next [ j ] = k,移动到k位置后,主串和子串达到匹配后,主串继续向右移动。
现在我们分析 k 和 j ,由于子串从 j 位置移动到了k位置,所以我们可以知道主串从Pos位置到 j-1位置的字符与子串匹配,而当子串移动到k位置匹配,同样可以知道,主串从Si向左数 k 位字符都会与子串T匹配
为了方便理解,我们假设Pos=1(这并不影响算法的思路,因为Pos= i - j +1)
S1,S2…S i-1,Si…
T1,T2…Tj-1,Tj…
这是没移动前的状态,Si !=Tj
S1…Si-2,Si-1, Si…
T1…,Tk-2,Tk-1,Tk
其实S1不应该是和T1对其,但是为了使后面Si和Tk对齐,只能这样显示,注意!其实上图的S1不和T1相等
上面第二个是移动后的状态,Si==Tk
这样有什么作用呢,其实这可以帮助我们构建next数组。由于移动前,子串前 j-1个字符与主串从i位置往前数 j个字符匹配,即从Pos-j+1开始直到 i-1位置匹配。
同理,移动后,主串从Pos-k+1开始匹配直到 k 位置匹配,所以移动后从Pos-k+1到k-1都匹配(前k项都匹配,所以前k-1项肯定匹配)
由于主串是一直不变的,由于移动前后主串 i 位置前若干个字符同时与移动前后子串相同
所以我们可以得到子串前k-1个字符会和子串后k-1个字符相等(若不太理解,可以动手画一下方便理解,可以就只用一个主串,画两个相同的串代表移动前后的串,对齐主串画,这样会容易理解一点)由于next[ j ]=k,所以第 j 位的next数组值等于子串长度是j-1的真前缀子串中,真前缀子串和真后缀子串中最长相同部分的子串长度+1(注意是真前缀子串的真前后缀子串相同部分的最长子串长度加一,而且第 j 位的值是长度为j-1位的真前缀子串产生的值)
因为第 j 位要看 j-1 位的真前缀子串,但是当 j=1时,j-1=0,所以next[ 1 ]=0(这是字符串下标从1开始,若从0开始,应该是-1),同样的当j=2时需要看长度为1的真前缀子串(注意时真前缀)所以最大长度是0,然后按照next数组的创建规则,最大长度加一,所以next[ 2 ]=1(这个同样,若是字符串下标从0开始,这应该是0),这两个很特殊,所以对于任何一个子串来说,next数组的第1位和第二位都是确定的。
//KMP算法,字符串下标从0开始
//DEVC++6.0
void KMP(String S,String T,int index)
{
int i=index,j=0;
while+(i<S.len&j<T.len)
{
if(j==-1||S.ch[i]==T.ch[j]) //当Si和Tj匹配的时候,或者next数组运动到了next[1]位置
{
i++; //比较下一个字符
j++;
}
else
j=next[j]; //否则将j移动到next[j]位置
}
if(j==T.len)
printf("匹配成功!第一次出现在目标串的第%d位\n",i-T.len);
else
printf("第一次匹配失败,目标串中没有模式串!");
return ;
}
如何通过程序实现创建next数组
尽管知道了next数组的创建思路,但是这个思路用于计算机实现比较困难,所以需要用另外的方法创建next数组
不知道有没有人注意到,在求next数组的值时,next数组的值等于真前缀子串和真后缀子串中最长相同部分的子串长度+1。重新看一遍这一句话,真前缀子串和真后缀子串的最长相同部分的子串长度+1。相同部分,又涉及到了KMP算法,所以在next数组的程序实现过程也是使用了KMP算法的思路
先假设k=next[ j ],m=next[ k ],真前缀子串为S,真后缀子串为T
和上面分析的一样,我们可以假设S和T字符在前 j-1 个字符匹配,即已知next[ j ]
我们分析第 j 位的字符是否:
一,若Sj==Tj,则说明前 j 位字符匹配,最长长度为 j,所以next [ j+1 ]的值等于next[ j ]的值+1(因为前 j 项字符匹配,所以会比next[ j ]的值大一)
二,若Sj !=Tj,则需要找到他们的最长相同部分的子串长度,然后把这个长度加一的值赋给next[ j+1 ],按照KMP算法,把T串的 j 移动到next[ j ]+1位置即k+1位置(我们只知道next[ j ]的值,而且前k位字符匹配,只需比较k+1位和 j 位字符是否相等),比较Tk+1和Sj的字符是否相等,若相等,则next[ j+1 ]的值等于k+1(理由就是上面一的理由),若不相等,则继续,移动到next[ k ]+1的位置即m+1位置,若相同则next[ j+1 ]的值等于m+1,否则还是这样进行下去,直到字符匹配或者找到了next[ 1 ],而next[ j+1]的值等于字符匹配时的下标加一,或者是next[ 1 ]+1即next[ j+1]=1(这分别对应前后情况)
这就是next数组在程序中实现的算法,简而言之,就是看next[j-1]是否和next[ next[j] ]的字符匹配吗,若匹配,next[ j ]=next[ j-1 ]+1,否则j=next[ j ]这样进行下去,直到匹配或运行到next[1]位置为止
//next数组的创建
void NextCreat(String T) //只需要子串,与主串无关
{
int i=0;
int k=-1;
next[i]=-1; //next[0]为-1
while(i<T.len)
{
if(k==-1||T.ch[i]==T.ch[k]) //相等或者是运动到了next[0]位置
{
i++; //比较下一个
k++;
next[i]=k; //存储next[ i ]的值,若相等就是k+1的值,由于之前k自增1,所以这就是k,若是运动到了next[0]位置,则next[ i ]=0,也是k=-1+1,自增后的结果,怎么都是将k自增后的结果赋给next[ i ]
}
else
k=next[k]; //移动到next[ k ]位置继续比较
}
return ;
}