问题:
现在有两个字符串A和B,问你在A中是否有B,有几个??
其实刚开始遇到这个问题的时候,我觉得挺简单的呀!依次循环过去查找不就可以了,
当然这样做肯
定能实现,而且程序编写简单粗暴,两个循环就解决了。但是这种方法的
时间复杂度是O(M*N),
M是字符串A的大小,N是字符串B的大小,这样时间复杂度
就太高了,我们需要一个时间复杂度低的
算法,这样KMP算法就孕育而生了,算法的时
间复杂度为O(M+N)。
KMP算法基本原理
算法的第一步就是建立一个next数组,这个数组有啥用,这个后面解释。
next数组表示的是字符串B的前
缀和后缀最大相等的数量,这句话什么意思??举个例子吧。
假设A=“abaabaabbabaaabaabbabaab”
B=”abaabbabaab”
对于前i个数,假设i=4,这字符串“abaa”的前缀是”a”,“ab”,”aba”;
后缀是”a”,”aa”,”baa”,其中
前缀和后缀相同的字符串有一组,是“a”,所
以最大相同的字符串字符数为1,故next[i-1=3]=1(数
组是从0开始计数,所
以这里是i-1);根据这个规律我们可以写出数组B的next={0,0,1,1,2,0,1,2,3,4,5};
算法的第二步就是利用字符串B对字符串A逐步右移,在A中移动的位置用
j来表示,B中移动的位置
用k来表示,以上面例子来解释这个算法,当j=0,k=0
时,A字符串的‘a’和B字符串的’a’两个字符相
等,则j++;k++;j=1,k=1时,
A字符串的‘b’和B字符串的’b’两个字符相等,继续j++,k++;依次类
推到i=5,j=5时
,A字符串的‘a’和B字符串的’b’两个字符不相等。这时候j=next[j-1]=2,继续将A
数组的
第i个字符串与B数组的第j个字符串相比较,此时A字符串的i=5为‘a’,B字符
串j=2(此时j的值已经发生了
改变)的值为‘a’,两个字符串相同,则i++,j++;这时候A字符串的数组为’a’,B字符串的数组为‘a’,重
复上述过程。最后知道i的值与A字符串长度相等的时候循环结束。这里需要注意的一点是,当比较字符与
模板字符一直不相等,就比如假设上述例子i=5时遇到的字符不是’a’,而是‘c’,则j=2的字符也不相等,
则j=next[j-1]=0;当j=0时的字符也和‘c’不相等,如果遇到这种情况不处理的话就会出现溢出的情况,所
以我们需要对其进行处理。当我们j=0时还是与第i个字符不相等时,我们就应该跳过这个字符串,即i++;
在编程的时候这一点尤其要注意。
介绍完该算法的整个过程,现在来分析一下它的可行性,以及为什么用产生一个next数组??这个数
组有什么作用??如果你不考虑算法的层面,仅仅从数学层面上来做这道题目,我们肯定第1个字母进行比
较,然后在第6个字符的时候断开了,然后你会立即从第4个字符开始进行比较,你这样做的原理是字符串
A第6个字符前面两个字符与字符串B的前两个字符相同,所以我们选择第4个字符开始,这时候你再想想next
数组产生的原理,有没有发现点什么??
我们在第6个字符的地方断的,说明A和B前5个字符相同,next[5-1](表示第5个数)表示前缀和后缀最
大相同的字符串数目,这时候是2。这说明B字符串的第1和第2的字符肯定和A数组中第4和第5个字符相等,
如果这样的话我们只需要直接B数组的第三个元素与A数组的第6个元素比较就可以了。现在知道next数组的
作用了吧!!!
下面是我对该算法写的c代码,仅仅参考,有错误请指教:
#include"stdio.h"
#include"string.h"
#include"malloc.h"
#define N 20 //定义字符串模板长度
#define N1 50 //定义需要匹配的字符串的长度
int *get_array(char *B); //得到next数组
void main()
{
char B[N]="aba"; //定义模板字符串
char A[N1]="abaabaabbabaaabaabbabaab"; //定义需要匹配的字符串
int *next; //定义next数组
int i=0,j=0,flag=0;
next=(int *)malloc(strlen(B)*sizeof(int)); //给next数组开辟空间
next=get_array(B); //得到next数组的值
while(i0) //防止一个字符一直找不到匹配导致溢出
{
j=next[j-1];
}
else //如果有一个字符一直找不到匹配,则进行下一个字符重新匹配
{
i++;
j=0;
}
}
}
if(flag) //判断是否存在该模板字符串
{
printf("exist the number and exist number is %d ",flag);
}
else
{
printf("not exist the number");
}
getchar();
}
int *get_array(char *B)
{
int L=strlen(B); //L代表模板字符串的长度
int key=0; //用来记录前缀和后缀相同的数目
int *next; //找到前缀和后缀相同的数目最大值
next=(int *)malloc(L*sizeof(int));
for(int i=0;i<strlen(B);i++)
{
next[i]=0; //给next数组赋初值
}
for(int i=0;i<L;i++) //查找前i个字符前缀和后缀的数目
{
int max=0; //每次对其归0
for(int j=0;j<i;j++) //找出前i个字符的前缀和后缀
{
int flag=0; //归0复位
for(int k=0;k<=j;k++) //对前i个字符的前缀和后缀依次比较进行匹配
{
if(B[k]!=B[i-j+k]) //如果前缀和后缀不相同
{
flag=1; //标志位,表明该前缀和后缀不相同
break;
}
}
if(flag==0) //如果前缀和后缀相同
{
key=j+1; //记录下前缀的数目
if(key>max) //判断是否最大
{
max=key;
}
}
next[i]=max; //将最大数目的前缀赋值给next数组
}
}
return next;
}