题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2087
KMP算法最浅显理解——一看就明白
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 char text[1005],pattern[1005];//主串,模式串 5 int perfix[1005],lena,lenb,num; //prefix数组存放的是最长公共前后缀长度 6 7 void KMP() //KMP匹配字符串算法 8 { 9 int i=0,j=-1;//预设j初始化为-1 10 perfix[0]=-1;//prefix[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀 11 while(i<lenb){ //先计算模式串的最长公共前后缀长度,打印前缀表 12 if(j==-1||pattern[i]==pattern[j])perfix[++i]=++j; //当为-1的时候,文本串和模式串同时i++,j++ 13 //j==-1表示若主串的第i个字符和模式的第1个字符不等,应从主串的第i+1个字符开始重新进行匹配 14 else j=perfix[j]; //向右移动模式串 15 } 16 i=j=0;// i是循环主串的指针位置,j是更新模式串当前指针位置,首先将j置为0,将模式串的第一个字符与主串开始比较 17 while(i<lena){ //主串与模式串的匹配 18 if(j==-1||pattern[j]==text[i]){i++;j++;} //若是j为-1或是主串与模式串当前字符相等,同时i++,j++ 19 else j=perfix[j]; //不满足条件,调整模式串的位置,表现为右移 20 if(j==lenb){num++;j=0;}//求不重复的循环次数从0开始(初始值)求重叠式的为 j=perfix[j] 21 } 22 } 23 24 int main() 25 { 26 while(cin>>text){ //a为文本串,b为模式串 27 if(strcmp(text,"#")==0)break; 28 cin>>pattern; 29 num=0,lena=strlen(text),lenb=strlen(pattern);//num是用来计数主串包含多少个模式串 30 KMP(); //KMP算法 31 cout<<num<<endl; 32 } 33 return 0; 34 }
代码过程简单分析:如上所示,我们先对模式串进行打印前缀表,然后再应用KMP算法来与主串匹配。这里给一串模式串abaabcac计算其前缀表。首先我们将前缀表prefix[0]=-1是前导出-1,待循环匹配到i时如果前面没有最长公共前后缀,j==-1加1后变为当前字符串prefix[i]==0,巧妙处理了前缀表。所以给出字符串的前缀表如下:
模式串的下标 0 1 2 3 4 5 6 7
模式串 a b a a b c a c
前缀表下标 0 1 2 3 4 5 6 7 8
最长前后缀公共长度 -1 0 0 1 1 2 0 1 0
注意上面前缀表与模式串对应的下标位置。计算好模式串的前缀表之后,开始与主串进行匹配了,用i,j分别指向主串,模式串的当前位置,当匹配失败是,指针i(指向主串)不变,指针j(指向模式串)退回到prefix[j]所指示的位置上重新进行比较,并且当指针j退至-1时,指针i和指针j需同时增1。即若主串的第i个字符和模式串的第j个字符不等,应从主串的第i+1个字符重新进行匹配。
上面给的大牛博客里讲的很清楚了,为什么j要退回到prefix[j],这里谈谈我的学习心得:prefix数组记录的是当前字符与前面组成的字符串的最长前后缀公共长度。举个栗子:仍用上面的模式串,假设现在我们要求最后的‘c‘字符下的prefix[8],怎么求呢?已知prefix[7]=1,说明‘c‘字符前面的字符串abaabcac其最长前后缀公共长度为1,这时我们加上字符‘c‘,只需比较最前面的第1个字符‘a‘后面字符‘b‘是否等于当前字符‘c‘,如果相等的话就执行prefix[7+1]=1+1=2(表示最后一个字符下与前面的字符串组成的最大公共前后缀长度是2),否则继续退回,直到-1,显然这里是不等的,所以j会退回-1,这时说明字符‘c‘没办法与第一个字符‘a‘匹配,即prefix[8]=0,退回这个算法很巧妙,不过也在情理之中。接下来进行模式串与主串的匹配,其算法和计算前缀表差不多,多了一步判断j(j是从1开始计算的)是否达到模式串尾,即j==lenb?这样就判断主串是否包含了模式串,同时用num进行计数主串中含有多少个不相交的模式串,OK,满满的收获!
解法2:这道题要求找主串中含有的模式串,那么可以运用库函数strstr()来计数num。
strstr 语法:头文件#include <string.h>
char *strstr( const char *str1, const char *str2 );
功能:函数返回一个指针,它指向字符串str2 首次出现于字符串str1中的位置,如果没有找到,返回NULL。
解题思路:通过这个函数的特点,因为返回的是地址,所以我们定义一个字符指针p来指向接受返回地址,如果找到的话,加上模式串的长度,即接下去寻找是否含有模式串,如果找不到的话,返回NULL将会退出当前循环。
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 char text[1005],pattern[1005];//主串,模式串 4 int main() 5 { 6 int len,num; 7 char *p; 8 while(cin>>text){ 9 if(strcmp(text,"#")==0)break; 10 cin>>pattern; 11 num=0,len=strlen(pattern); 12 for(p=text;p=strstr(p,pattern);num++,p+=len); 13 cout<<num<<endl; 14 } 15 return 0; 16 }