看算法过程总有个经历,每次都以为自己真正理解了,隔一段时间看,发现自己并没有理解地那么透彻。kmp算法是大二时学的,这次复习数据结构重新看了一下,用简单到复杂的思路,将这个算法过程贴下。下面以goodgoodstudy这个字符串为例。
假设我要在一篇文章找"goodgoodstudy",首先最容易想到的模式匹配算法是:
算法1:
int foo( string foo , string bar , int pos )
{
string::size_type i = pos , j = 0 ;
while ( i < foo.size() && j < bar.size() )
{
if ( foo[i] == bar[j] )
{
i++ , j++ ;
}
else
{
i = i - j ; // 退回
j = 0 ; // 子串游标为0
}
}
if ( j > bar.size()){
return i-j ;
}
else
return -1 ;
}
这个算法简单,但也有用,可以快速写出程序,用来验证其他复杂算法的正确性。kmp算法利用先前比较的结果,而不必回退主串的游标。kmp增加了一个next[]数组,如果将算法1也看成next[]数组,由于每次不匹配时,子字符串回退到1,那么字符串"goodgoodstudy"的next[]为13个1。
但是为什么kmp算法中next[1]=0,而不是1?
因为每次kmp算法不匹配时,根据next[ ... next[] .. ] 这样一直回溯时,若回溯到起点时,如果next[1]=1,则会陷入一个死循环,故next[1]=0,可以终止循环。
kmp算法:
int kmp( string foo , string bar , int pos )
{
string::size_type i = pos , j = 0 ;
j--; //从0开始
while ( i < foo.size() && j < bar.size() )
{
if ( (j == -1 ) || (foo[i] == bar[j]) ) //这里string字符下标从0开始 故终止选-1
{
i++ , j++ ;
}
else
{
// i = i - j ;
j = next[j] ; // 子串游标为0
}
}
if ( j > bar.size()){
return i-j ;
}
else
return -1 ;
}
问题二:怎么求next[]数组?
这其实是一个同类型的问题,next[0]=-1,那么next[1]呢,使用next[1]时表示主串和子串没有匹配,这时字符串foo[1]和foo[0]]若相等,则next[1] =next[0]+1=0。同理比较到子串k,则可以求出next[k],那么next[k+1],若foo[k+1]与next[k]相等,则next[k+1]=next[k]+1,否则next[k+1]则要继续与next[ next[] ] 比较 ,直到next终止。但是可以注意到利用next一次次的迭代计算,最后next值终止,那么我们计算next值可以直接将该值赋-1,从而提前结束迭代。