字符串匹配

来源:互联网 发布:淘宝网护腰带 编辑:程序博客网 时间:2024/05/22 04:56

字符串匹配又叫模式匹配。也就是说给一个字符串p,其长度为m;给出一个正文字符串s,其长度为n,通常情况下n的长度要大于m,在正文中找到这个字符串p就是模式匹配问题

1、字符串匹配的简单算法(BF算法)

C代码:

两种实现方式:

1.通过设置标志变量flag

例如:

#include<stdio.h>
#include<string.h>
int search(const char *src, const char *patn, int slen, int plen);

void main()
{
 char *src = "ababacbac";
 char *patn = "ac";
 int pos=0, slen=0, plen=0;
 slen = strlen(src);
 plen = strlen(patn);
 pos = search(src,patn,slen,plen);
 printf("%d",pos);
}

int search(const char *src, const char *patn, int slen, int plen)
{
 int i = 0, flag = 0, j = 0, k = 0;
 while((i<slen)&&(flag != 1))
 {
  k = i;
  j = 0;
  while((j<plen)&&(src[k]==patn[j]))
  {
   ++k;
   ++j;
  }
  if(j==plen)
   flag = 1;
  else
   ++i;
 }
 if(flag == 1)
  return i;
 else
  return -1;
}

//设置flag,当完全匹配的时候flag==1,跳出循环,此时i的值就是模式串在住串中的位置。

2.通过回溯的方法

int search(const char *src, const char *patn, int slen, int plen)
{
 int i = 0, j = 0;
 while((i<slen)&&(j<plen))
 {
  if(src[i]==patn[j])
  {
   ++i;
   ++j;
  }
  else
  {
   i = i-j+1;
   j = 0;
  }
 }
 if(j==plen)
  return i-plen;
 else
  return -1;
}

//当发现失配时将i回退,再重新比较。

//返回的函数值为-1,说明查找失败;否则说明查找成功,且返回的函数值为模式p的第一个字符在s中的位置

该算法的缺点是:当比较过程中发现不匹配时,模式p沿着正文s向后移动一个位置。使得先前已经比较过的正文字符还需要进行比较,增加了比较次数,而且会引起反复存取正文字符串的操作。

2、KMP算法

基本思想是:找到一种模式p沿主串s向后移动的规则,以便使得主串s中失去匹配的字符以前的字符不再参与比较,即只从当前失去匹配的字符开始与模式p中的字符继续一次进行比较,并且又不能错过模式发现的机会。

KMP算法的关键是:当模式串p[j]和主串s[i]失配时,求得在失配字符之前模式串中字符前缀和后缀相同的最大长度k(即求"T0~Tk-1"=="Tj-k~Tj-1"),这个长度就是下回用来与s[i]进行比较的模式串字符中的下标,即p[k],这样就避免了主串的回溯,减小了时间复杂度。

现在问题是这个k如何求(也就是说对于模式串中不同字符的失配,这个k是多少)?

这里出现了next数组,next数组就是用来记录k值的。我们用next[j]=k来方便解释其含义,即当p[j]失配时,下一次比较用p[k]来替换p[j],继续与主串进行比较。next数组的值可以由下式表示

当j=0时,此时也就是说p[0]与主串失配,这个时候只能将主串向后移动一位

当j=1时,此时也就是说p[1]与主串失配,这个时候next[1]=0,主串不动,模式串右移也就是后移一位

当j>=2时,就要看k值了

至于为什么用p[k]来替换p[j],可以从下图看出来

S0,S1,S2,...,Si-j,Si-j+1...............,Si-1,Si, Si+1,....,Sn

                     T0,T1,.....Tj-k,........,Tj-1,Tj, ..........

                                     T0,  T1,..,Tk-1,Tk,..........

因为"T0~Tk-1"=="Tj-k~Tj-1",显然用Tk来与Si进行比较。

next数组可以用递推的方式求得

设next[j]=k,这表明模式串中存在下面的关"T0~Tk-1"=="Tj-k~Tj-1",其中k为满足1<k<j的某个值,并且不可能存在k'>k满足上面的等式。此时的next[j+1]=?可能有两种情况:

(1)若Tk=Tj则表明在模式串中有

                          "T0~Tk"=="Tj-k~Tj",所以

                                                                   next[j+1]=k+1;

(2)若Tk!=Tj,此时我们的目的仍然是要求对于模式串"T0~Tj",使得该部分的前后缀相等的最大长度值。于是我们将模式串即当成主串,又当成模式串,也就是如下图所示:

主串                     T0,T1,.....Tj-k,........,Tj-1,Tj, ..........

模式串                                 T0,  T1,..,Tk-1,Tk,..........

Tk!=Tj时,我们用Tnext[k]来代替Tk,如果Tnext[k]与Tj仍然不等,我们就让Tnext[next[k]]来代替Tnext[k]直到出现一个k'使得Tk'=Tj,此时next[j+1]=k'+1,所以写成递推的形式为:

                                                                  next[j+1]=next[j]+1;

 

求next数组的C代码:

//plen是模式串的长度,
int *next_construction(const char *src, const char *patn, int plen, int *next)
{
 int j=0, k=-1;
 next[0] = -1;
 while(j<plen-1)                             // j<plen-1的原因是在对next[j]赋值的时候先执行了++j
 {
  if(k==-1||patn[j]==patn[k])          //k==-1之后求得next[1]=0
  {
   ++k;
   ++j;
   if(patn[j]!=patn[k])
    next[j]=k;
   else
    next[j]=next[k];
  }
  else
   k=next[k];
 }
 return next;
}

求得next数组以后就可以写KMP算法了

int KMP_search(const char *src, const char *patn, int slen, int plen, int *next)
{
 int i=0,j=0;
 while((i<slen)&&(j<plen))
 {
  if(j==-1||src[i]==patn[j])
  {
   ++i;
   ++j;
  }
  else
  {
   j=next[j];
  }
 }
 if(j==plen)
  return i-plen;
 else
  return -1;
}

//观察发现,KMP的代码与BF的代码很相似,只是在局部有细微差别

KMP算法的复杂度是O(strlen(S)+strlen(P))

BF算法的复杂度是O(strlen(S)*strlen(P))

 

KMP算法为什么是线性的需要用到有限自动机的理论,该理论还不是很清楚。需要继续学习

0 0