文章目录
1.暴力匹配(BF算法)
暴力匹配,也称为简单匹配算法,采用穷举的思想,从主串的第一个字符开始和模式串中第一个字符比较。
若相等,则依次比较后续字符,如果不相等,模式串回溯为第一个字符,从主串的第二个字符开始重新和模式串比较,实现如下:
#include<stdio.h>
int BF(char *s,char *t)//s为主串,t为模式串
{
int i = 0,j = 0;
while(s[i]!='\0'&&t[j]!='\0')//两个串都没有扫描完时循环
{
if(s[i]==t[j])//当前两个字符相同
{
i++;j++;//比较后续字符
}
else//当前两个字符不同
{
i=i-j+1;//扫描主串的i回退
j=0;//模式串回溯为0,从头匹配
}
}
if(t[j]=='\0')//如果j越界,表示模式串遍历完,t是s的字串
return i-j;//返回目标串中子串的起始位置
else
return 0;
}
int main()
{
char s[5]={'a','c','a','b','a'};
char t[2]={'a','b'};
printf("%d",BF(s,t));
}
算法时间复杂度分析:
1.最好的情况,即不需要回溯,一次匹配成功,复杂度为O(m)
2.最坏的情况,需要多次回溯时,复杂度为O(n×m)
3.算法平均时间复杂度为O(n×m)
2.KMP算法
该算法相比较暴力算法,有很大的改进,消除了主串指针的回溯,如下图进行字符串s和t的匹配:
用 i 和 j 来扫描字符串s和t,此时匹配失败处为 i = 3,j = 3,虽然本次匹配失败,但仍然取得了部分匹配的信息,s1s2 与 t1t2 相同。
而且这时还发现字符串t中前两个字符和中间两个字符相等,即t1t2 = t0t1,所以s1s2 = t0t1,如下图所示。
而原本第二趟匹配应该从i = 1,j=0开始匹配,现在既然有s1s2 = t0t1,那么第二趟匹配的 j = 0,j = 1 就可以直接跳过,直接从 i =3, j = 2开始第二趟匹配。
从上匹配过程可以发现,在匹配失败时,我们利用从模式串t中提取的有用信息,消除了主串指针的回溯,这种信息是对于t中的每一个字符 t[j],存在一个整数k使得字符串t开头的k个字符和 t[j] 的前面k个字符相同,如果这样的k存在多个,取最大的那个。
如上述字符串"aaab",当t[3]='b’时,b前面的一个字符"a"和开头的一个字符"a"相同,k=1,b前面的两个字符"aa"和开头的两个字符"aa"相同,即k=2,所以取最大值k=2。
对于模式串t的每个字符,都对应一个k,我们用一个next数组来保存,由模式串t求next数组的算法如下:
void GetNext(char *t,int next[])//求出next数组,消除主串指针回溯
{
int j=0,k=-1;//j扫描模式串t,k记录t[j]之前与t开头相同的字符个数
next[0]=-1;//开头字符的k值为-1
while(t[j]!='\0')
{
if(k==-1||t[j]==t[k])//扫描到相同字符时,k++
{
j++;k++;
next[j]=k;
}
else
k=next[k];//k回退
}
}
取得加速匹配的信息后,当出现匹配失败时,令 j = next[j] ,消除主串指针回溯,KMP算法如下:
#define MaxSize 50
int KMP(char *s,char *t)
{
int next[MaxSize],length;
int i=0,j=0;
GetNext(t,next);
length=strlen(t);
while(s[i]!='\0'&&j<length)
{
if(j==-1||s[i]==t[j])//j=-1或者当前两个字符相同
{
i++;
j++;
}
else
{
j=next[j];//回退到j=next[j],即模式串右滑
}
}
if(t[j]=='\0')
return i-j;
else
return 0;
}
KMP算法时间复杂度分析:设主串s长度为n,模式串t长度为m,在KMP算法中求next数组的时间复杂度为O(m),在后面的匹配中因主串s的下标不回溯,比较次数可记为n,所以KMP算法平均时间复杂度为O(n+m)。