zoukankan      html  css  js  c++  java
  • KMP该算法解释(最长公共子)

    一个:介绍KMP算法之前,首先解释一下BF算法

    (1)BF算法(传统的匹配算法,是最简单的算法)

     BF算法是一种常见的模式匹配算法,BF该算法的思想是目标字符串S模式串的第一个字符P的第一个字符,以匹配,如果相等,然后去比较S第二个字和P;若不相等。则比較S的第二个字符和P的第一个字符。依次比較下去。直到得出最后的匹配结果。
       
    (2)举例说明:
        S:  ababcababa
        P:  ababa
      BF算法匹配的过程例如以下
               i=0                  i=1                       i=2                 i=3                   i=4
      第一趟:ababcababa         第二趟:ababcababa      第三趟:ababcababa    第四趟:ababcababa    第五趟:ababcababa
        ababa                         ababa                 ababa                 ababa                   ababa
        j=0                           j=1                   j=2                   j=3                     j=4(i和j回溯)

        i=1                           i=2                   i=3                   i=4                     i=3 
     第六趟:ababcababa         第七趟:ababcababa       第八趟:ababcababa     第九趟:ababcababa   第十趟:ababcababa
        ababa                   ababa                        ababa               ababa                   ababa
        j=0                     j=0                          j=1                 j=2(i和j回溯)           j=0

        i=4                     i=5                          i=6                 i=7                     i=8
    第十一趟:ababcababa       第十二趟:ababcababa    第十三趟:ababcababa   第十四趟:ababcababa   第十五趟:ababcababa
           ababa                    ababa               ababa                    ababa                ababa
           j=0                       j=0                j=1                      j=2                  j=3
     
        i=9
    第十六趟:ababcababa
     ababa
     j=4(匹配成功)

    事实上在上面的匹配过程中,有非常多比較是多余的。在第五趟匹配失败的时候,在第六趟。i能够保持不变。j值为2。由于在前面匹配的过程中,对于串S,已知s0s1s2s3=p0p1p2p3。又由于p0!=p1!。所以第六趟的匹配是多余的。又由于p0==p2,p1==p3,所以第七趟和第八趟的匹配也是多余的。

    在KMP算法中就省略了这些多余的匹配。

    (3)BF代码:

    int BFMatch(char* ori,char *des)
    {
        int i,j;
        i = 0;
        while(*(ori+i)!='')
        {
            j = 0;
            while(*(ori+i)!=''&&*(des+j)!=''&&*(ori+i)==*(des+j))
            {
                i++;
                j++;
            }
            if(*(des+j)=='')
                return i-j;// 返回匹配成功后的src中的開始下标
            i = i-j+1;// 回溯到,这次匹配的src中的開始位置的下一个位置
        }
        return -1;
    }



    二:KMP算法

    (1)KMP算法之所以叫做KMP算法是由于这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。

    事实上KMP算法与BF算法的差别就在于KMP算法巧妙的

    消除了指针i的回溯问题。仅仅需确定下次匹配j的位置就可以。使得问题的复杂度由O(mn)下降到O(m+n)。

    在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于同样字符序列的前缀。

    对于next[]数组的定义例如以下:
     1) next[j] = -1  j = 0
     2) next[j] = max(k): 0<k<j   P[0...k-1]=P[j-k,j-1]
     3) next[j] = 0  其它

     如:
     P      a    b   a    b   a
     j      0    1   2    3   4
     next    -1   0   0    1   2
     
    即next[j]=k>0时,表示P[0...k-1]=P[j-k,j-1]

    因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,假设next[j]>=0,则目标串的指针i不变。将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比較。

    (2)KMP算法通过next数组能够知道目标串中下一个字符是否有必要被检測,这个next数组就是用所谓的“前缀函数(一般数据结构书中的getNext函数)”来存储的。

     这个函数可以反映出现失配情况时,系统应该跳过多少无用字符(也即模式串应该向右滑动多长距离)而进行下一次检測

     一是这个前缀函数的求法。

     二是在得到前缀函数之后。怎么运用这个函数所反映的有效信息避免不必要的检測。

      以下介绍《部分匹配表》是怎样产生的。

      首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的所有头部组合。"后缀"指除了第一个字符以外,一个字符串的所有尾部组合。

      "部分匹配值"就是"前缀"和"后缀"的最长的共同拥有元素的长度。以"ABCDABD"为例,

      - "A"的前缀和后缀都为空集,共同拥有元素的长度为0。
      - "AB"的前缀为[A],后缀为[B],共同拥有元素的长度为0;
      - "ABC"的前缀为[A, AB]。后缀为[BC, C]。共同拥有元素的长度0。
      - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共同拥有元素的长度为0;
      - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A]。共同拥有元素为"A",长度为1;
      - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B]。共同拥有元素为"AB",长度为2;

      - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共同拥有元素的长度为0。

    (3) 代码例如以下:

    #include <iostream>
    #include <cstring>
    
    using namespace std;
    const int MAX_SIZE = 64;
    
    void getNext(char *p,int next[])
    {
        int j,k;
        next[0] = -1;
        j = 0;
        k = -1;
        while(j<strlen(p))
        {
            if(k==-1 || p[j]==p[k])
            {
                j++;
                k++;
                next[j] = k;
            }
            else
                k = next[k];
        }
        int i;
        for(i=1;i<=j;i++)
            cout << next[i] << ",";
    }
    
    int KMPMatch(char *s,char *p)
    {
        int next[MAX_SIZE];
        int i,j;
        i = 0;
        j = 0;
        getNext(p,next);
        while(s[i]!='')
        {
            if(j==-1 || s[i]==p[j])
            {
                i++;
                j++;
            }
            else
            {
                j = next[j];// 消除指针回溯
            }
            if(p[j] == '')
                return i-j;
        }
        return -1;
    }
    
    int main()
    {
        char ori[MAX_SIZE],des[MAX_SIZE];
        cout << "请输入两个字符串进行匹配:" << endl;
        cin >> ori >> des;
        //cout << "匹配结果:" << BFMatch(ori,des) << endl;
        cout << "匹配结果:" << KMPMatch(ori,des) << endl;
        return 0;
    }

    (4)总结:KMP是用来匹配test字符串是否是目标串的子串,相当于全然匹配。即測试字符串是否在目标字符串中出现过

    三:最长公共子序列(LONGEST COMMEN SUBSEQUENCE)

    (1)子序列:不要求连续的,子串是要求连续的。

     (2)代码例如以下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    
    using namespace std;
    const int MAX_SIZE = 100;
    int LCSLength(char *s1,char *s2,const int &len1,const int &len2,int lcs[][MAX_SIZE],int b[][MAX_SIZE])
    {
        int i,j;
        for(i=0;i<=len1;i++)
            lcs[0][i] = 0;
        for(j=1;j<=len2;j++)
            lcs[j][0] = 0;
        for(i=1;i<=len1;i++)
        {
            for(j=1;j<=len2;j++)
            {
                if(s1[i-1] == s2[j-1])
                {
                    lcs[i][j] = lcs[i-1][j-1] + 1;
                    b[i][j] = 0;
                }
                else if(lcs[i-1][j] >= lcs[i][j-1])
                {
                    lcs[i][j] = lcs[i-1][j];
                    b[i][j] = 1;
                }
                else
                {
                    lcs[i][j] = lcs[i][j-1];
                    b[i][j] = -1;
                }
            }
        }
        return lcs[len1][len2];
    }
    
    void PrintLCS(char *s1,int b[][MAX_SIZE],int i,int j)
    {
        if(i==1 || j==0)
            return;// 一定要与返回啊啊
        if(b[i][j] == 0)
        {
            PrintLCS(s1,b,i-1,j-1);
            cout << s1[i-1];
        }
        else if(b[i][j] == 1)
        {
            PrintLCS(s1,b,i-1,j);
        }
        else
        {
            PrintLCS(s1,b,i,j-1);
        }
    }
    int main()
    {
        int len1,len2;
        int lcs[MAX_SIZE][MAX_SIZE],b[MAX_SIZE][MAX_SIZE];
        char s1[MAX_SIZE],s2[MAX_SIZE];
        int ans;
        while(cin >> s1 >> s2)
        {
            len1 = strlen(s1);
            len2 = strlen(s2);
            ans = LCSLength(s1,s2,len1,len2,lcs,b);
            cout << ans << endl;
            PrintLCS(s1,b,len1,len2);
            cout << endl;
        }
        return 0;
    }//


    版权声明:本文博客原创文章。博客,未经同意,不得转载。

  • 相关阅读:
    Linq语句jion on后指定多个条件
    sql判断日期是否为当前季度
    Linq to json
    .net压缩文件夹
    .net代码修改webconfig
    .net正则IP加端口,并返回IP加端口
    获取汉字拼音的首字母(获取助记码)
    .net获取程序根目录
    关闭IPV6
    mysql查看日志
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4686680.html
Copyright © 2011-2022 走看看