KMP算法

 在一个较长的字符串中寻找一个较小的字符串,使用传统暴力求解,会多出很多不必要的计算步骤,而KMP算法可以有效解决这一点

目录

 先看代码:

介绍KMP算法

理解next数组

怎么求next数组?

 首先在分析相等时的情况:

现在来分析一下不相等时:


 先看代码:

void getNext(int* next,string s)
{
	int j=0;
    next[0]=0;
	for(int i=0;i<s.size();i++)
        {
        	while(j>0&&s[i]!=s[j])
                {
                    j=next[j-1];
                }
            if(s[i]==s[j])
            {
                j++;
                next[i]=j;
            }
        }
}
int strSTr(string a,string b)
{
	int next[b.size()];
	getNext(next,b);
	int j=0;
    for(int i=0;i<b.size();i++)
        {
        	while(j>0&&b[j]!=a[j])//这一步有点像递归
            {
                j=next[j-1];
            }
            if(a[j]==b[i])
            {
                j++;
            }
            if(j==b.size())
            {
                return (i-b.size()+1);
            }
            
        }
	return -1;
    
}

 


介绍KMP算法

aabaabaaf

aabaaf

逐个匹配,匹配到f时,b与f不相等,此时看f前一位的next数组元素(next会在后面说明),并在子串中跳到对应数字位置,继续匹配。

在f前一位的next数组元素是f前面子串(aabaa)的最长前后缀,即2,这时候说明这个子串的开始两位与末尾两位一模一样,这说明了什么?在文本串中,我们匹配到了第六位' b ',' b '的前面就是这个子串("aabaa")的末尾"aa",我们刚刚通过next数组知道了"aabaa"的前两位与末尾两位相等,所以此时直接将aabaaf的第三位对齐aabaabaaf的第六位即可(因为末尾两位与开头两位相等,所以不用比较),为什么KMP不用再到模式串的起点那再次遍历呢,因为只有当子串的最后一位也匹配成功后,整个就算匹配完成,只有中途有一位匹配失败,则已经匹配了的全部前功尽弃没有作用,此时直接跟着后面匹配失败的那个位置重新匹配即可,但是不用重新匹配子串的全部,将相等的前后缀后一位开始匹配可以省不少步骤。

所以代码是这样的

for(int i=0;i<b.size();i++)
        {
        	while(j>0&&b[j]!=a[j])
            {
                j=next[j-1];
            }
            if(a[j]==b[i])
            {
                j++;
            }
            if(j==b.size())
            {
                return (i-b.size()+1);
            }
            
        }

但是我们注意到,在匹配失败的时候,我们用了while,为什么呢?让我们思考一下,此时是不是跳转到了相同前缀的后一位进行比较,但是这个比较也可能不相等,如果不相等,那么就和我们当初模式串f与文本串的b发生冲突是一样的了。(有点像递归......)

我们再往下面的代码看,如果匹配成功,那么j++,注意:j是对于子串而言的,j会随着匹配的进行,匹配成功则++,匹配失败则跳到前面可以省略比较步骤的相同前后缀的后一位,从那一位接着开始比较,通过这样循环往复,最终当j等于子串的size()后,即说明j是一路匹配成功来的,即在文本串中找到了与模式串相等的那一部分

最后,我们return(i-b.size()+1)即子串在模式串相等部分的起始位置

 


理解next数组

next数组是相对于子串而言的,在实际比对过程中,aabaaf的任意一个位置都可能发生匹配冲突,而任意一个位置发生冲突后,我们都要去看他前一位的next数组的元素,什么意思呢?

aab的第三位b发生冲突,此时我们看前一位a的next数组,即aa的最长前后缀;

aaba的第四位a发生冲突,此时我们看前一位b的next数组,即aab的最长前后缀;

aabaa的第五位a发生冲突,此时我们看a的前一位(第四位)a的next数组,即aaba的最长前后缀。

所以在实际匹配过程中,任意一位都有可能发生冲突,我们都需要立刻知道其前一位的next数组,所以next数组需要提前把每一个子串的最长前后缀准备好

即我们要求

a

aa

aab

aaba

aabaa

aabaaf

的最长前后缀。


怎么求next数组?

void getNext(int* next,string s)
{
	int j=0;
    next[0]=0;
	for(int i=1;i<s.size();i++)
        {
        	while(j>0&&s[i]!=s[j])
                {
                    j=next[j-1];
                }
            if(s[i]==s[j])
            {
                j++;
                next[i]=j;
            }
        }
}

我们注意到这里有一个for循环,里面i在不断++,其中i每++一次,就确定next的一位,即确定next[i]的数值,

每个next[]在内部实现,(注意next[0]一定为0,因为a的前后缀为0,所以我们for循环直接从i=1开始)现在我们来具体分析一下内部:

 首先在分析相等时的情况:

if(s[i]==s[j])
{
	j++;
	next[i]=j;
}

这里无需想太多,如果相等,则j++,说明此时在原有的基础上,又增加了一位相等的,就是说,如果之前j=1,有一位相等前后缀,现在又相等了,j++,即有两位相等前后缀了。

此时完成这个子串的next数组,储存他。

现在来分析一下不相等时:
while(j>0&&s[i]!=s[j])
{
    j=next[j-1];
}

 

我们来看这三类数列

为什么不相等时j要跳转到next[j-1]呢?

我们知道j前面部分一定会等于j后面的一部分,即aba==aba,aba(前)有前后缀,前缀等于后缀,那么aba(后)就也有这个相同的前后缀,前缀等于后缀,也就是说aba(前)的前缀会等于aba(后)的后缀。

而j=next[j-1]会找到aba的相等前后缀。再由while判断是否相等,相等则继承在这之上的前后缀长度,并++;不相等则又进入while循环(有点像递归),继续找更小的。

谢谢。

  • 37
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值