KMP算法详解

本文详细介绍了KMP算法,包括问题描述、朴素思路、KMP算法的核心——next数组的求解以及模式匹配的过程。通过递推方式解析next数组,并提供了具体的代码实现,帮助读者深入理解KMP算法。
摘要由CSDN通过智能技术生成

目录

问题简单描述:

朴素思路解法

KMP算法

next数组的求解

 模式匹配的过程


问题简单描述:

给定字符串s和模式串p,求模式串p在字符串s中出现的位置(所有位置都要给出),假设给定字符串s="ababcdabcabcd",模式串p="abcd"

朴素思路解法

暴力搜索:模式串逐个向右移动,复杂度O(M*N)

//字符串s
a b c a b c a b k  

//模式串 a b c a b k 

a b c a b c a b k  

a b c a b k 
  a b c a b c a b k  
    a b c a b c a b k  
      a b c a b c a b k  
        a b c a b c a b k  
          a b c a b c a b k  
............................

在这里,我们可以这样考虑根据已经扫描过的子串信息,直接将模式串右拉,例如当前s[i]与p[j]不想等,我们希望p[j]的前一个字符串p[j-1]形成的后缀和p[0]开始构成的前缀信息有重合,这样我们就可以直接将模式串拉到重合的位置,省略搜索时间。例如,在上诉例子中:当s[5]=c与p[5]=k不相等时,根据p[4]的最长公共前后缀(ab)直接将模式串abcabk拉到继续重合的位置,然后再进行后续判断

//字符串s
a b c a b c a b k  

//模式串 a b c a b k 

a b c a b c a b k  

a b c a b k  
      a b c a b k//匹配成功 
        
............................

KMP算法

next数组的求解

根据朴素思路提到的子串的最长公共前后缀(不能是整个字符串)长度,也就是KMP算法中的next数组,下面给出一个next数组的例子。例如下面的aba,其前缀为a ab(aba不算在里面),其后缀为ba a(aba不算在里面),所以公共前后缀为a,长度为1。其他的依次类推。

回顾next数组的完整定义:

  • 定义 “k-前缀” 为一个字符串的前 k 个字符; “k-后缀” 为一个字符串的后 k 个字符。k 必须小于字符串长度。
  • next[x] 定义为: P[0]~P[x] 这一段字符串,使得k-前缀恰等于k-后缀的最大的k.

  这个定义中,不知不觉地就包含了一个匹配——前缀和后缀相等。接下来,我们考虑采用递推的方式求出next数组。如果next[0], next[1], ... next[x-1]均已知,那么如何求出 next[x] 呢?

分情况讨论。首先,已经知道了 next[x-1](以下记为now),如果 P[x] 与 P[now] 一样,那最长相等前后缀的长度就可以扩展一位,很明显 next[x] = now + 1. 图示如下。

  刚刚解决了 P[x] = P[now] 的情况。那如果 P[x] 与 P[now] 不一样,又该怎么办?

 那我们现在需要寻找的是字符串[0.....x]的最长公共前后缀,我们可以知道:公共前缀肯定在子串A中,公共后缀肯定在子串B中,由于子串A和子串B是完全相等的,问题又转化成为求解子串A的最长公共前后缀即now=next[now-1],即如下图所示。然后再去判断p[now]与p[x]相等否重复上诉过程。

 具体的代码实现可以参考如下:

void get_next(vector<int> &next, string p){
    //根据给定的模式串求解next数组
    //next[i]数组表示模式串[0---i]的最长公共前后缀长度
    next.resize(p.length(), 0);
    int j = 0;
    next[0] = j;
    for(int i = 1; i < p.length(); i++){
        //j最开始进入循环时是next[i-1]的值,即[0----i-1]的最长公共前后缀长度
        while(j > 0 && p[i] != p[j]){
            /*s[i]与s[j]不相等,则必须进行回退
            本来是要求A [0----j-1]与 B[*---i-1]的最长A前缀与B后缀的长度,由于A B相同
            实质上是转化成为求A的最长前后缀长度
            */
            j = next[j-1];
        }
        if(p[i] == p[j])
            //在公共前后缀长度j上直接+1就行 
            j++;
        next[i] = j;
    }
}

 模式匹配的过程

和next数组的构建过程也即为相似,代码如下所示:

void match(string s, string p, vector<int> next){
    //字符串s和p的匹配代码
    for(int i = 0, j = 0; i < s.length(); i++){
        //i表示字符串s当前扫描到的位置,j表示模式串当前扫描到的位置
        while(j > 0 && s[i] != p[j]){
            //s[i]与p[i]不相等,直接将模式串p向后移动
            /*
                字符串s: * * a b c g k h f a b c d * * * * * * * * * 
                模式串P:    a b c g k h f a b c g
                                           a b c g k h f a b c g
                                                 a b c g k h f a b c g
                                                   a b c g k h f a b c g
                对串s已经扫描的部分,我们应该
            */
            j = next[j-1];
        }
        if(s[i] == p[j]){
            j++;
        }
        if(j == p.length()){
            cout << "match_end :" << i << endl;
            j = 0;
        }
    }
}

完整代码如下所示:

#include "bits/stdc++.h"

using namespace std;


//KMP算法实现

void get_next(vector<int> &next, string p){
    //根据给定的模式串求解next数组
    //next[i]数组表示模式串[0---i]的最长公共前后缀长度
    next.resize(p.length(), 0);
    int j = 0;
    next[0] = j;
    for(int i = 1; i < p.length(); i++){
        //j最开始进入循环时是next[i-1]的值,即[0----i-1]的最长公共前后缀长度
        while(j > 0 && p[i] != p[j]){
            /*s[i]与s[j]不相等,则必须进行回退
            本来是要求A [0----j-1]与 B[*---i-1]的最长A前缀与B后缀的长度,由于A B相同
            实质上是转化成为求A的最长前后缀长度
            */
            j = next[j-1];
        }
        if(p[i] == p[j])
            //在公共前后缀长度j上直接+1就行 
            j++;
        next[i] = j;
    }
}

void match(string s, string p, vector<int> next){
    //字符串s和p的匹配代码
    for(int i = 0, j = 0; i < s.length(); i++){
        //i表示字符串s当前扫描到的位置,j表示模式串当前扫描到的位置
        while(j > 0 && s[i] != p[j]){
            //s[i]与p[i]不相等,直接将模式串p向后移动
            /*
                字符串s: * * a b c g k h f a b c d * * * * * * * * * 
                模式串P:    a b c g k h f a b c g
                                           a b c g k h f a b c g
                                                 a b c g k h f a b c g
                                                   a b c g k h f a b c g
                对串s已经扫描的部分,我们应该
            */
            j = next[j-1];
        }
        if(s[i] == p[j]){
            j++;
        }
        if(j == p.length()){
            cout << "match_end :" << i << endl;
            j = 0;
        }
    }
}

int main(){
    string s, p;
    s = "abcdabcdebcd";
    p = "bcd";
    vector<int> next;
    get_next(next, p);
    match(s, p, next);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值