详解KMP算法(字符串匹配算法)(高效寻找子串)(一个一个匹配,推己及人,根据己和别串匹配的值,确定他串下一次应该匹配的位置,根据己串求出text[ ],就是下一次应该母串确定的位置)

https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html

3.3.1 寻找最长前缀后缀

    如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

    也就是说,原模式串子串对应的各个前缀后缀的公共元素的最大长度表为(下简称《最大长度表》):

 

3.3.2 基于《最大长度表》匹配

    因为模式串中首尾可能会有重复的字符,故可得出下述结论:

失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值

    下面,咱们就结合之前的《最大长度表》和上述结论,进行字符串的匹配。如果给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,如下图所示:

        

  • 1. 因为模式串中的字符A跟文本串中的字符B、B、C、空格一开始就不匹配,所以不必考虑结论,直接将模式串不断的右移一位即可,直到模式串中的字符A跟文本串的第5个字符A匹配成功:

  • 2. 继续往后匹配,当模式串最后一个字符D跟文本串匹配时失配,显而易见,模式串需要向右移动。但向右移动多少位呢?因为此时已经匹配的字符数为6个(ABCDAB),然后根据《最大长度表》可得失配字符D的上一位字符B对应的长度值为2,所以根据之前的结论,可知需要向右移动6 - 2 = 4 位。

  • 3. 模式串向右移动4位后,发现C处再度失配,因为此时已经匹配了2个字符(AB),且上一位字符B对应的最大长度值为0,所以向右移动:2 - 0 =2 位。

           

  • 4. A与空格失配,向右移动1 位。

  • 5. 继续比较,发现D与C 失配,故向右移动的位数为:已匹配的字符数6减去上一位字符B对应的最大长度2,即向右移动6 - 2 = 4 位

           

  • 6. 经历第5步后,发现匹配成功,过程结束。

          

    通过上述匹配过程可以看出,问题的关键就是寻找模式串中最大长度的相同前缀和后缀,找到了模式串中每个字符之前的前缀和后缀公共部分的最大长度后,便可基于此匹配。而这个最大长度便正是next 数组要表达的含义。

3.3.3 根据《最大长度表》求next 数组

    由上文,我们已经知道,字符串“ABCDABD”各个前缀后缀的最大公共元素长度分别为:

 

 

    而且,根据这个表可以得出下述结论

 

  • 失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值

    上文利用这个表和结论进行匹配时,我们发现,当匹配到一个字符失配时,其实没必要考虑当前失配的字符,更何况我们每次失配时,都是看的失配字符的上一位字符对应的最大长度值。如此,便引出了next 数组。

    给定字符串“ABCDABD”,可求得它的next 数组如下:

 

   把next 数组跟之前求得的最大长度表对比后,不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。意识到了这一点,你会惊呼原来next 数组的求解竟然如此简单:就是找最大对称长度的前缀后缀,然后整体右移一位,初值赋为-1(当然,你也可以直接计算某个字符对应的next值,就是看这个字符之前的字符串中有多大长度的相同前缀后缀)。

    换言之,对于给定的模式串:ABCDABD,它的最大长度表及next 数组分别如下:

    根据最大长度表求出了next 数组后,从而有

 

失配时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值

    而后,你会发现,无论是基于《最大长度表》的匹配,还是基于next 数组的匹配,两者得出来的向右移动的位数是一样的。为什么呢?因为:

  • 根据《最大长度表》,失配时,模式串向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的最大长度值

  • 而根据《next 数组》,失配时,模式串向右移动的位数 = 失配字符的位置 - 失配字符对应的next 值

    • 其中,从0开始计数时,失配字符的位置 = 已经匹配的字符数(失配字符不计数),而失配字符对应的next 值 = 失配字符的上一位字符的最大长度值,两相比较,结果必然完全一致。

    所以,你可以把《最大长度表》看做是next 数组的雏形,甚至就把它当做next 数组也是可以的,区别不过是怎么用的问题。

 

void getnext()
{
    int j=-1;
    next[0]=-1;
    for(int i=1;i<lena;i++)//next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作
    {
        
        while(j != -1 && s[i]!=s[j+1] )
        {
            j=next[j];//返回数组,每次当出现不匹配时,就返回之前匹配的数值里面
        }
        if(s[i] == s[j+1])
        {
            j++;
        }
        next[i]=j;
    }
}

 

int kmp(char *text, char *pattern)
{
    int ans=0;
    int n=strlen(text),m=strlen(pattern);
    getnext(pattern);
    int j=-1;
    for(int i=0; i<n; i++)
    {

        while( j != -1 && text[i]!=pattern[j+1] )
        {
            j=next[j];
        }
        if(text[i] == pattern[j+1])
            j++;

        if(j==m-1)
        {
            //cout<<true<<endl;
            ans++;
            
            j=next[j];//后退一步,i++,接着匹配,当做为最后一个匹配失败,继续向后匹配
        }
    }

    return ans;
}

 

next[i]是子串s[0......i]的最长相等前后缀的前缀的最后一位的下标

//next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作,当 j + 1 失配后,j应该回退到的位置!

例如:

i 0  1  2  3

  a  b  a  b

          a  b  a  b

j    -1  0  1  2  3    我们得知,前后ab相等,当匹配到 i=4 时,出现不匹配时,i=next[i]也就是说返回到s[1]这个位置来继续匹配算法

#include <stdio.h>
#include <string.h>
#include<iostream>
#include <algorithm>
using std::min;
using namespace std;
//int lena, lenb;
char *a, *b;

int next[1010];
void read()
{
    //scanf("%s", a );
    a="ababaab";
    b="aaaaaababaab";
    //lena = strlen(a);
    //lenb = strlen(b);
}

void getnext(char *s)
{
    int lena = strlen(s);
    int j=-1;
    next[0]=-1;
    for(int i=1;i<lena;i++)//next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作
    {

        while(j != -1 && s[i]!=s[j+1] )
        {
            j=next[j];//返回数组,每次当出现不匹配时,就返回之前匹配的数值里面
        }
        if(s[i] == s[j+1])
        {
            j++;
        }
        next[i]=j;
    }
}
bool kmp(char *text, char *pattern)
{
    int n=strlen(text),m=strlen(pattern);
    getnext(pattern);
    int j=-1;
    for(int i=0;i<n;i++)
    {

        while( j != -1 && text[i]!=pattern[j+1] )
        {
            j=next[j];
        }
        if(text[i] == pattern[j+1])
            j++;

        if(j==m-1)
        {
            cout<<true<<endl;
            return true;
        }
    }

    return false;
}

int main()
{
    read();
    //getnext(a);
    kmp(b,a);
    return 0;
}

next的再优化!!!1

void getnext(char *s)
{
    int lena = strlen(s);
    int j=-1;
    next[0]=-1;
    for(int i=1; i<lena; i++) //next数组代表前缀和后缀相等的长度是多少,当数组匹配错误时,可以进行返回操作
    {

        while(j != -1 && s[i]!=s[j+1] )
        {
            j=next[j];//返回数组,每次当出现不匹配时,就返回之前匹配的数值里面
        }
        if(s[i] == s[j+1])
        {
            j++;
        }

        if( j==-1 || s[i+1] != s[j+1] )//j==-1不能再回退
            next[i]=j;
        else
            next[i]=next[j];//再回退
    }
}

此时nextval[i]含义为当i+1匹配失位后,i应当回退到的最佳位置

 

i 0  1  2  3  4

  a  b  a  b  a  b

          a  b  a  b  a  b

j    -1  0  1  2  3   4

 

if( j==-1 || s[i+1] != s[j+1] )  //j==-1不能再回退
            next[i]=j;
        else
            next[i]=next[j];//再回退

假设abab与母串已经匹配失败,到了b时不成功了!

通常回退到i=0,值为a处,但是结果也是GG!

因为结果与b匹配失败也会与后面拉上来的b匹配失败!只有不相同时回退才有效!

ababab这里我们看见next[-1,-1,-1,-1,-1,3]

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值