数据结构 - 字符串的模式匹配

什么叫做模式匹配呢?其实就是看字符串S中是否有子串T,那么T就叫匹配串。
我们平时查找方式是逐个匹配,那么时间复杂度就是O(n*m).
比如s=’abcabde’,t=’abd’
那么比较的时候s[1]=t[1],所以接下来比较s[2]和t[2],结果s[2]=t[2],那么接下来就比较s[3]和t[3],这一次比较匹配不上。
然后就是普通匹配方法比较耗时的地方:比较s[2]和t[1]继续上面那样的流程。这样就可以看到,最坏的情况需要比较O(n*m)次。
画一下匹配过程:
1 abcabde
   abd      匹配失败
2 abcabde
    a        匹配失败
3 abcabde
      a      匹配失败
4 abcabde
        abd 匹配成功
我们看到比了4次。那么怎么用程序来实现呢?

function CommonIndex(const Sub,Dest:string):Integer ;
var
  i,j:Integer;
  Len1,Len2:Integer;
begin
  Len1:=Length(Sub);
  Len2:=Length(Dest);
  Result:=0;
  if Len1>Len2 then
    Exit;
  j:=1;
  i:=1;
  //判断过程
  while (I<= Len2 - Len1 + 1)and(J <= Len1) do
    if Dest[J] = Sub[I] then
    begin
      Inc(I);
      Inc(J);
    end
    else
    begin
      J:=1;     //sub回溯
      I:=I-J+2; //Dest回溯
    end;
  //判断是否查找成功
  if J = Len1 then
    Result:=I-J+1;
end;

接下来看看C怎么实现的:

int CommonIndex(char* sub,char* dest){
    int i=0,j=0;
    while (dest[i]&&sub[j]){
        if (dest[i]==sub[j]){
            ++i;
            ++j;
        }else{
            j=0;
            i-=j-1;
        }
    }//endwhile
    if (!sub[j])
        return i-j+1;
    else
        return 0;
}

就像上面注释那样,每次匹配失败的话,都需要把两个字符串都回溯,所以这里就必要浪费时间了。
整个算法的时间复杂度是O(m*n)
我们再回来看看上面的匹配过程。
1 abcabde
   abd      匹配失败
在匹配到c和d这里,我们发现不用再去比较b和a 以及c和a了,因为肯定不能匹配。所以现在我们只需要回溯sub字符串。
看看改进后的匹配过程:
1 abcabde
   abd      匹配失败
2 abcabde
       abd  匹配成功
但是Sub每次匹配失败后回溯多少呢?这就是这个KMP算法里面精髓的地方。
我们先看看代码吧!

function KMPIndex(const Sub,Dest:String):Integer;
var
  i,j:Integer;
  len1,len2:Integer;
  next:array of Integer;
  procedure MakeNext;//自过程生产Next表
  begin
    i:=1;
    j:=0;
    next[1]:=0;
    while i<=len1 do
      if(j=0)or (sub[i]=sub[j])then
      begin
        Inc(i);
        Inc(j);
        next[i]:=j;
      end
      else
        j:=next[j];
  end;
begin
  len1:=Length(sub);
  len2:=Length(Dest);
  Result:=0;
  if len1>len2 then
    Exit;
  SetLength(next,len1 + 1); //动态数组是从0开始的,我们要从1开始,所以这里+1,多一个元素
  //生成Sub的回溯表。
  MakeNext;
  try
    i:=1;
    j:=1;
    while (i<=len1)and(j<=len2) do
      if (i=0)or(Sub[i] = Dest[j]) then  //匹配成功
      begin
        inc(i);
        Inc(j);
      end
      else
        i:=next[i]; //失败,只有Sub回溯一下
    if i > len1 then
      Result:=j - i + 1;
  finally
    SetLength(next,0);
  end;
end;

那么C的代码会是怎么样呢?

int KMPIndex(char* sub,char* dest){
    int len = 0;
    //计算sub的长度
    for(;sub[len];++len);
    //动态分配next数组
    int* next = (int*)malloc(len *sizeof(int));
    //生成next数组
    int i = 0,k = -1;
    //*(next++) = 0;
    next[0] = -1;
    while (i<len)
        if (k==-1||sub[i]==sub[k]){
            ++i;
            ++k;
            //*(next++ )= k;
            next[i] = k;
        }else{
            k = next[k];
        }
    //现在next数组生成了 ,接下来就是匹配比较了。
    i = 0;
    k = 0;
    while (dest[i]&&sub[k]){
        if (k==-1||dest[i] ==sub[k]){
            ++i;
            ++k;
        }else
            k=next[k];
    }//endwhile
    //比较完了释放next空间
    free(next);
    //判断是否匹配成功
    if (!sub[k])
        return i - k + 1;
    else
        return 0;
}

我们看看时间复杂度吧。理论来说应该大于O(n+m),小于O(n*m).在实际过程中近似O(n+m).
从上面的代码中我们可以看见next的第i个元素是靠比较第i-1个得到的。

while (i<len)
        if (k==-1||sub[i]==sub[k]){
            ++i;
            ++k;
            //*(next++ )= k;
            next[i] = k;
        }else{
            k = next[k];
        }
while i<=len1 do
      if(j=0)or (sub[i]=sub[j])then
      begin
        Inc(i);
        Inc(j);
        next[i]:=j;
      end
      else
        j:=next[j];

如果字符是从1开始的,那么next[0] = 0,如果字符从0开始的,那么next[0]=-1;
那么我们手动来计算一下 'abababd’的next数组字,假设字符串从1开始。
a b a b a b d
0  //第一个直接等于0
0 1 //第二个要靠第一个去判断。第一个字符为a,对应next[1]值为0,那么结束比较。next[2]=1
0 1 1 //第三个要靠第二个去判断。第二个字符为b,对应的k值为1,而第一个字符是a,由于b不等于a,继续取第一个字符a对应的k值,k为0比较结束,next[3]=1;
0 1 1 2 //第四个要靠第三个。第三个字符是a,对应的k值为1,而第一个字符是a,由于a=a,比较结束next[4]=1+1=2
0 1 1 2 3 //第五个要靠第四个。第四个字符是b,对应k值是2,而第二个字符是b,由于b=b,比较结束next[5]=2+1=3
0 1 1 2 3 4 //同理。第五个字符是a,对应的k值是3,而第三个字符是a,由于a=a,比较结束,所有next[6] = 3+1 = 4;
0 1 1 2 3 4 5 //第六个字符是b,对应的k是4,而第四个字符是b,由于b=b,比较结束,所以next[7] = 4 + 1 = 5;
整个next数组就是 0 1 1 2 3 4 5
不知道大家对上面这个求的过程看明白了没有。
当然这个生成next的过程在特殊情况是有错误的。所以就有了改进的地方,nextval。这个我们下回再说。有条件的朋友可以自己看看书。
书中自有黄金屋,书中自有颜如玉。
金钱与女人都出自书本。。。哈哈,难怪古代文人那么喜欢逛青楼。。。蛋疼。!!
等着我的nextval 谢谢

转载于:https://www.cnblogs.com/huangjacky/archive/2010/12/25/1916892.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值