KMP算法详解(与最长公共子序列)

来源:互联网 发布:淘宝网二手手机商城 编辑:程序博客网 时间:2024/05/17 06:04

一:在介绍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)!='\0')    {        j = 0;        while(*(ori+i)!='\0'&&*(des+j)!='\0'&&*(ori+i)==*(des+j))        {            i++;            j++;        }        if(*(des+j)=='\0')            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]!='\0')    {        if(j==-1 || s[i]==p[j])        {            i++;            j++;        }        else        {            j = next[j];// 消除指针回溯        }        if(p[j] == '\0')            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;}//


1 0
原创粉丝点击