串匹配:BF朴素查找算法和KMP算法实现推导

串匹配: 在主串中找子串的位置。
串: 用“包括的字符序列。例如‘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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值