zoukankan      html  css  js  c++  java
  • 数据结构 字符串的模式匹配

    什么叫做模式匹配呢?其实就是看字符串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 谢谢

  • 相关阅读:
    时间:UTC; GMT; DST; CST
    python解析XML:之二 (ElementTree)
    python解析XML:之一
    Wiki使用
    java基础:java环境,第一个Java程序,java的数组
    Oracle记录(二) SQLPlus命令
    Oracle记录(一)Oracle简介与安装
    DIY ESXI虚拟化服务器再度升级ESXI6.0 (U盘安装Esxi)
    【VMware虚拟化解决方案】 基于VMware虚拟化平台VDI整体性能分析与优化
    vmware workstation 网络管理
  • 原文地址:https://www.cnblogs.com/huangjacky/p/1916892.html
Copyright © 2011-2022 走看看