前面介绍了kmp算法,kmp算法是不容易懂得的算法之一,而且其处理速度也并非是最好的算法,所以现在来看一个在算法的处理速度和理解上都比kmp算法更好的一种优秀算法,sunday算法。
Sunday算法是DanielM.Sunday于1990年提出的一种比BM算法搜索速度更快的算法。其核心思想是:在匹配过程中,模式串并不被要求一定要按从左向右进行比较还是从右向左进行比较,它在发现不匹配时,算法能跳过尽可能多的字符以进行下一步的匹配,从而提高了匹配效率。
我们假设来看这样的两个字符串s1= "ijdmsdkkfmbaa",s2 = "kkfm",
i j d m s d k k f m b a a
k k f m
第一次让s1的i和s2的k相比较可以看到不相等,这里不相等后该怎么移动就是sunday算法的精妙之处。在bf算法中我们让s2右移动一位,在kmp算法中我们按照前缀函数来移动,而这里我们是拿匹配项的s1的下一位来匹配,就是这里的s。很显然如果s1包含s2的话,s这个字符串必然要在s2中,但是现在没有,所以我们可以直接移动整个s2,移动后的图如下:
i j d m s d k k f m b a a
k k f m
可以看到,移动的两个字符串第一个字母仍然不相等,所以继续看当前s1的下一位,下一位是m,在原字符串s2中出现,并且出现的位置是在原字符串s2中的最后一位,所以先右移动一位,移动后的效果图如下:
i j d m s d k k f m b a a
k k f m
可以看到,这次移动后就已经完全匹配。当主串s1最右端结束以后,如果还没发现有完全匹配的话,则证明主串中没有包含子串。该算法最坏情况下的时间复杂度为O(N*M)。对于短模式串的匹配问题,该算法执行速度较快。
c语言代码实现方式如下:
# include <stdio.h>
# include <string.h>
int sunday(char *str,char *des);
int main(void){
chars1[]="ijdmsdkkfmbaa";
chars2[]="kkfm";
intnum = sunday(s1,s2);
printf("%d",num);
return0;
}
int sunday(char *str,char *des){
inti,j,pos = 0;
intnext[26] = {0};
intlen_s = strlen(str);
intlen_d = strlen(des);
//初始化的时候让每个字符的下标值为子串的长度+1
for(i= 0;i<26;i++){
next[i]= len_d + 1;
}
for(i= 0;i<len_d;i++){
next[des[i]- 'a'] = len_d - i; //字符串距离最右边的长度+1
}
/*为了方便看next数组的构造形式
for(i= 0;i<26;i++){
printf("%d",next[i]);
}
*/
while(pos< (len_s-len_d + 1)){
i= pos; //让i等于当前主串的pos值,pos就为子串的向后移动的位数
for(j=0;j<len_d;j++,i++){
if(str[i]!= des[j]){
pos= pos + next[str[pos+len_d] - 'a']; //如果不相等就跳跃
break;
}
}
if(j== len_d){
returnpos;
}
}
return-1;
}
我们来看下上面的代码的具体执行过程。首先让26个字母的值都为子串的最大长度+1,这是为了方便比较当str[i]!= des[j]时,主串的下一位在子串中没有的时候可以直接向后移动这样的位数。
s1[]="ijdmsdkkfmbaa";s2[]="kkfm"的时候,next数组的值为:{5,5,5,5,5,2,5,5,5,5,3,5,1,5,5,5,5,5,5,5,5,5,5,5,5,5},下面开始while循环,最开始进来的时候pos=0,i=0,然后进入for循环,很明显str[i]!= des[j],所以向后移动,pos = pos + next[str[pos+len_d] - 'a'];可以得到pos等于5,所以向后移动了5位。然后继续上面的代码,此时pos= 5,i=5,还是str[i] != des[j],继续pos = pos + next[str[pos+len_d] - 'a'];可以算出这里最后的pos=6,继续向后移动一位,最终得到问题的解