串匹配: 在主串中找子串的位置。
串: 用“包括的字符序列。例如‘abcd’(“abcd”)
空串: “(”")
子串: ‘abc’它的字串包括空串和本身,那么一共有:’’,‘a’,‘b’,‘c’,‘ab’,‘bc’,‘abc’共七个
真子串: 不包含本身,一共’’,‘a’,‘b’,‘c’,‘ab’,'bc’共六个
串的长度: ‘abc’->3
BF算法->朴素算法: 以主串的每个字符开始向后比较,直到找到匹配的位置
每次比较,子串都是从0号下标开始比较
如果相等,则两个串同步向后走 i++=j++,否则,i=i-j+1;j++
BF(朴素查找方法): 如果查找失败,必须回退到刚才起始位置的下一个,子串下标回退到0;成功返回子串在主串中下标,失败返回-1
实现:
//时间复杂度:O(n*m) 不停地回退比较
int BF(char const *s,char const *p)//s表示主串,p表示子串
{
assert(s!=NULL && p!=NULL);
int i=0;//主串的下标
int j=0;//子串的下标
while(i<strlen(s) && j<strlen(p))//i不超过主串的长度,j不超过子串的长度
{
if(s[i]==p[j])//如果两字符相等,则同时自加
{
i++;
j++;
}
else//否则,i回到主串中此轮遍历的下一个字符,j从子串的0下标开始
{
i=i-j+1;
j=0;
}
}
if(j>=strlen(p))//如果遍历结束,j的值比大于等于字串长度,则串匹配成功,返回大于等于0的数
{
return i-j;
}
return -1;//匹配不成功,返回-1
}
BF算法在遍历的时候,如果匹配不成功,需要回退到之前遍历的下一个下标,继续进行遍历,而子串下标也需要重新从0开始比较,此算法需要的时间复杂度为O(m*n),空间复杂度为O(1)。
KMP算法
依次比较子串和主串的字符,直到出现失配
当比较到此时时出现失配,如果按照BF算法,则将主串回退到1号字符“b”,将子串回退到0号字符“a”,然后重新进行比较。而KMP的算法则不要求主串回退,只要将子串回退到适当位置即可。
由图可以看出,子串中有两处与主串i前面的字符串相同。
此时:i不变,j不会回退到0位置,而是回退到k位置,k位置是需要通过数学推导得出的。
具体推导过程如下所示:
子串与主串比较,失配前的字符串相等
Sx… Si-1 == P0… Pj-1 --------------(1)
字符串相等,则字符相同,长度相等,则有:
i-1-x==j-1-0 即就是 i-x=j
(1)式可写为:Si-j… Si-1 == P0… Pj-1------(2)
由图知:主串的绿色部分和子串第一个绿色部分相等,有:
Sx… Si-1 == P0… Pk-1-------(3)
i-1-x == k-1-0 即就是 i-x=k
Si-k… Si-1 == P0… Pk-1-------(4)
由图知:主串的绿色部分和子串第二个绿色部分相等,有:
Si-k… Si-1 == Px… Pj-1-------(5)
i-1-(i-k) == j-1-x 即就是 k=j-x
Si-k… Si-1 == Pj-k… Pk-1-------(6)
由公式(4)(6)可以得出:P0…Pk-1 == Pj-k… Pj-1
得出结论:在子串中存在最长的两个相等真子串(与主串无关),其中一个串以0位置开始,另一个串以j-1位置结束,当j位置比较时失配,其回退位置(k位置)应该就是上面所述的真子串的长度。
在p串中任何一个位置都有一个与其对应的k值
将p循环中的每个位置对应的回退位置k,存储在next数组中
KMP算法实现
void GetNext(int *next,char const *p)//获取回退数组
{
int lenp=strlen(p);//获取子串长度
if(lenp<2)//如果子串长度小于2,则直接返回
{
next[0]=-1;//将0号下标值设置为-1
return ;
}
//0、1下标的值是既定的,为-1,0
next[0]=-1;//给-1后,即使++也回退到0
next[1]=0;
int i=1;//i为遍历子串的位置
int k=0;//k为next数组的值
while((i+1)<lenp)//遍历子串,从第二个开始遍历并和k下标的值进行比较计算next数组的值
{
if(k==-1 || p[i]==p[k])//k==-1或者p[i]==p[k],则next[i]的值+1
{
//next[i+1]=k+1;
//i++;
//k++;
next[++i]=++k;
}
else//若p[i]!=p[k],k回退,继续进行比较,KMP算法原理应用
{
k=next[k];
}
}
}
int KMP(const char *s,const char *p, int pos)//s表示主串,p表示子串,pos表示
{
int i=pos;//主串下标
int j=0;//子串下标
int *next =(int *)malloc(sizeof(int )* strlen(p));//开辟一个next数组,存放回退下标
assert(next!=NULL);
GetNext(next,p);//获取子串的回退数组
for(int m=0;m<strlen(p);++m)//遍历next数组,获取回退值
{
printf("%d ",next[m]);
}
printf("\n");
while(i<strlen(s) && j<strlen(p))//遍历主串和子串
{
if (j==-1 || s[i]==p[j])//如果相等,则主串下标和子串下标同时加一
{
i++;
j++;
}
else
{
//i不需要回退
//j回退到其next数组指定k位置
j=next[j];
}
}
free(next);//释放动态内存
if(j>=strlen(p))//若j大于等于子串长度,则匹配成功,返回正数
{
return i-j;
}
return -1;//否则,返回-1
}