模式匹配之KMP算法

   前面讲到过BF算法,虽然简单,但是效率比较低,KMP算法对此做了很大改进,该算法是由Knuth,Morris,Pratt同时设计的,所以简称KMP算法
   为什么说BF算法效率低呢?比如s="34343434345",t="345",在位置3时,s中字符为‘3’,t中为'5',不匹配,所以又重新从s中第2个位置,t中第1个位置开始匹配,匹配失败,又从s中第3....
   可以看到,在s中后面子串"345"之前的匹配中很多都是不必要的,这无疑大大增加运行的次数,这些回溯都是不必要的
   假设在某次si和tj匹配失败后,i不回溯,串t向右"移动"到某个位置,使得tk对准si继续向右进行。

这里写图片描述

   首先,匹配失败因为si处的字符与tj处的字符不匹配,那么tj前面的j-1个字符都是匹配的,所以:
"t1t2t3...t(j-1)"="s(i-j+1)s(i-j+2)...s(i-1)"  //1式
//t前面j-1个字符     //si前面j-1个字符
然后t向右"移动"到某个位置,使得tk对准si,那么tk前的k-1个字符是匹配的,所以
"t1t2t3...t(k-1)"="s(i-k+1)s(i-k+2)...s(i-1)"  //2式
 k肯定是小于j的,对吧,(之前是t串位置j处对准si,然后t向右"移动",使得tk对准si,所以k<j)那么从子串t的位置j-1往前的k-1个字符和s的位置i-1前的k-1个字符相等(k<j,既然j-1个字符都相等了,那么k-1个肯定也相等)所以
    "t(j-k+1)t(j-k+2)...t(j-1)"="s(i-k+1)s(i-k+2)...s(i-1)" //3式

由2式和3式可以得到:

"t1t2t3...t(k-1)"="t(j-k+1)t(j-k+2)...t(j-1)" //4式

这说明了什么呢?说明在某次si和tj匹配失败后,如果串t中满足前k-1个字符组成的串=串t中j-1位置前(包括j-1)k-1个字符组成的串,就可以将串t向右”移动”使得si对准tk,然后继续进行比较

next数组:

 串中的每一个tj都对应一个k值,由上面的分析可知,k值仅仅取决于串t本身,与s无关,用next数组来存储tj对应的k值,next数组具有以下性质:
    1: next[j]是一个整数,且0<=next[j]<j
    2: 为了是t右移但不丢失任何匹配成功的可能,当存在多个满足4式的k值时,
    应选取最大的,使得t向右"移动"的距离最短,移动的字符为j-next[j]个
    3:如果在tj前不存在满足4式的子串,定义next[j]=1,即用t1和si比较
因此,next数组定义如下:
 当j=1时:
      next[j]=0

 当存在k满足4式时:
       next[j]=k(若有多个k,选最大的)

  当不存在上面的k时:
        next[j]=1

比如有子串t=“abcaababc”,其next数组为:
next[]={0,1,1,122,3,2,3}

接下来就是KMP算法的执行过程了,初始时i=j=1;
如果si==tj,则i和j分别+1,如果si!=tj,则i不变,j回溯到next[j],然后再比较,如果si==tj,则i,j分别加1,继续匹配,以此类推,如果遇到next[j]==0,那么i,j也要分别增加1
以下是主串”aabcbabcaabcaababc”,子串”abcaababc”的匹配过程
这里写图片描述

   怎么样,确实省去了很多不必要的比较吧,接下来就是实现了,那么如何求子串的next数组呢?
   由定义知:
       next[1]=0;

   设next[j]=k,即有:
        "t1t2...t(k-1)"="t(j-k+1)...t(j-1)"

   那么next[j+1]等于什么呢?分两种情况:
    1): 如果tk==tj,则:
        "t1t2...t(k-1)tk"="t(j-k+1)...t(j-1)tj"
        则next[j+1]=next[j]+1

    2):如果tk!=tj,即:
         "t1t2...t(k-1)tk"!="t(j-k+1)...t(j-1)tj"
           此时可以将求next值的问题看成一个模式匹配问题,整个串即是主串又是
           子串,在当前匹配过程中,已有next[j]=k,则应将串向右移动,使得第next[k]个字符和"主串"的第j个字符比较,如果存在K',那么
           next[j+1]=next[k]+1
          否则next[j+1]=1;

求解next数组需要好好体会

getNext函数:

void getNext(string t,int (&next)[20])
{
    int i = 1,j=0;
    next[1] = 0;
    /*
    假设next[j]=k,即"t1t2...t(k-1)"="t(j-k+1)t(j-k+2)...t(j-1)"
    那么next[j+1]等于多少呢?
        1):如果tk=tj,那么:
                "t1t2...t(k-1)tk"="t(j-k+1)t(j-k+2)...t(j-1)tj"
              所以next[j+1]=next[j]+1
        2)如果tk!=tj,可以得到
               next[j+1]=next[k]+1]或者为1
    */
    while (i < t.length())
    {
        if (j == 0 || t[i-1] == t[j-1])
        {
            ++i;
            ++j;
            next[i] = j;
        }
        else
            j = next[j];
    }
}

KMP算法:

/*
模式匹配之KMP算法
*/
# include<iostream>
# include<string>

using namespace std;

void getNext(string t,int (&next)[20]);
int patternMatch_KMP(string s, string t,int (&next)[20]);

int main()
{
    string s;
    string t;

    cout << "请输入主串:";
    cin >> s;
    cout << "请输入子串:";
    cin >> t;
    int next[20];
    getNext(t,next);
    int result = patternMatch_KMP(s, t,next);
    if (result == -1)
        cout << endl << "匹配失败" << endl;
    else cout << endl << "子串在主串中的位置为:" << result << endl;

    cout << endl << "子串的next数组为:" << endl;
    for (int i = 1;i <= t.length();i++)
        cout << next[i] << endl;


    return 0;
}

int patternMatch_KMP(string s, string t, int (&next)[20])//返回子串t在串s第一次出现的位置(从1开始),若t不是s的子串
                                       //返回-1
{
    int i = 1, j = 1;
    while (i <= s.length() && j <= t.length())//两个串都没扫描完
    {
        if ( j== 0||(s[i - 1] == t[j - 1]))//该位置上字符相等或者j==0,就比较下一个字符
        {
            i++;
            j++;
        }
        else
        {
            j = next[j]; //i不变
        }

    }
    if (j > t.length())
        return (i - t.length());
    return -1;
}

void getNext(string t,int (&next)[20])
{
    int i = 1,j=0;
    next[1] = 0;
    /*
    假设next[j]=k,即"t1t2...t(k-1)"="t(j-k+1)t(j-k+2)...t(j-1)"
    那么next[j+1]等于多少呢?
        1):如果tk=tj,那么:
                "t1t2...t(k-1)tk"="t(j-k+1)t(j-k+2)...t(j-1)tj"
              所以next[j+1]=next[j]+1
        2)如果tk!=tj,可以得到
               next[j+1]=next[k]+1]或者为1
    */
    while (i < t.length())
    {
        if (j == 0 || t[i-1] == t[j-1])
        {
            ++i;
            ++j;
            next[i] = j;
        }
        else
            j = next[j];
    }
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值