C++ : KMP 字符串匹配算法

在传统字符串匹配中我们求得字符串p出现在字符串s中的位置。我们把字符串s称为主串,字符串p称为模式串。

KMP算法的原理简单来说就是匹配的时候不回溯主串的指针i,而只回溯模式串指针j ,即匹配过程中,不移动主串,指移动模式串来达到"尽可能向右滑动最大的距离"。

num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
主串s                                              
模式串p             a b a a b c                      

                                                                     图1.

假如模式串p中的c与s[11] 失配了,那么再传统匹配算法中,是拿s[7]和p[0] (a)去匹配,而在KMP匹配中是那s[11]和p[2] (a)中去匹配。因为在这次失配中可知,主串s中6~10的值就是abaab,再拿p[0]和s[7] 匹配必定失配,这次比较就显得多余。那么我们怎么知道失配的时候,模式串要向右移多少个单位呢?

假设此时主串要正在匹配的位置是 i =11 ,而 与 p[j]=='c',即j==5产生了失配,那么j指针要回溯到 某个k,假如=2值时要进行下一步匹配 ,那么k值必须满足两个关系式

由图一可知   p[3]p[4]=s[9]s[10],

由图二可知   p[0]p[1]=s[9]s[10]

num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
主串s                                              
模式串p                   a b a                      

                                                                            图2.

换成通用表达式就是

 

用逻辑意义上来解释公式3:就是当在模式串匹配第j个要回溯到k时此时j和k满足 前缀k个值和后缀k个值要相同。满足这条件的最大k值即为将要回溯的位置,假如用next数组表示模式串要回溯的位置其中 next[j]=k。

所以实际上,模式串中的next[j]的值与主串s是无关的。

假设next[j]=k即求next[j+1]

如果此时P[j] = P[k]  那么next[j+1]=k+1 即 next[j+1]= next[j] +1

如果此时 P[j] != P[k] ,那么就要移动next[k]个字符进行比较,达到s[i] = p[k],前缀与后缀对齐的目的。相等则next[j+1]=next[k]+1,不相等则迭代 k=next[k]重新移动比较,例如结合图3和图4,next[5]=2,求next[6]?

因为next[5]=2,所以比较出P[5] !=P[2],前缀无法对齐后缀就继续向右移(回溯),

因为next[2]=0,所以比较出P[5] !=p[0],

因为next[0]=-1接下来比对P[5]和P[-1] 因为next[0] = -1;

当不存在可回溯的k时next[j+1] = -1 ;

在存在一个可回溯的位置0<k'<k<j 那么 
 

while(k'>0 && k'< k ){
 if(k' = -1){
  next[j+1] = 0;
}
 if(p[k'] != p[k]){
   k= next[k]
 }else
 {
    next[j+1] = next[k] +1
}
}
num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
主串s                                              
模式串p             a b a a b c a                    

                                                                     图3

num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
主串s                                              
模式串p             a b a a b c a c                  

                                                                     图4.

.

num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
主串s                                              
模式串p             a b a a b c a c                  

                                                                     图5.

.所以字符串abaabcac的next值为 next [] = {-1,0,0,1,1,2,0,1};

改进的next ,

例如字符串 aaaab的next []={-1,0,1,2,3};

num 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
主串s                                              
模式串p             a a a a b                        

      KMP算法中当s[9] 和p[3]  (b)比较失配发生回溯时,接下来s[9] 会和p[2]比较,此时这个比较是多余的必定会再次失配 。把多余的比较剔除掉就是改进的next。

如果回溯的值与原值相等即,  P[j] = P[k] 时直接 另 P[j] = P[next[k]];

hpp:

//
// Created by hu on 2020/8/5.
//

#ifndef SMARTDONGLIB_SSTRING_H
#define SMARTDONGLIB_SSTRING_H
#include  <string>
#include <vector>

namespace SmartDongLib {

    class SString {
    public:
        SString(){ str_="";}
        explicit SString(std::string str): str_(std::move(str)){}
        SString(const  SString& str){ str_=str.str_;}
        int length(){return str_.length();}
        SString copy(){return *this;};
        bool isEmpty(){return str_.empty();}
        void clear(){str_=""; }
        SString concat(const SString& str2){ SString  ret(str_ + str2.str_); return ret;}
        SString subString(int pos, int len){SString ret (str_.substr(pos, len)); return ret;}
        SString subString(int pos=0){SString ret (str_.substr(pos)); return ret;}
        int index( const SString& , int pos =0);
        int index_KMP(SString& str2, int pos=0);
        SString replace(SString src, const SString& target);
        void strinsert(int pos,const SString& T){str_.insert(pos, T.str_);}
        void strdelete(int pos,int len){str_.erase(pos,len);}
        SString operator+(std::string str1){ return SString(str_ + std::move(str1));}
        SString operator+=(std::string str1){ return SString(str_ + std::move(str1));}
        bool operator==(const std::string& str1){return str_==str1;}
        std::string get(){return str_;}
        void getnext(int next[]);
    private:
        std::string  str_;
    };
}

#endif //SMARTDONGLIB_SSTRING_H

cpp  :

//
// Created by hu on 2020/8/5.
//

#include "sdstructure/linearlist/SString.h"
namespace SmartDongLib {

   inline int SString::index(const SString& str2,  int pos ) {
        int iptr = pos , jptr = 0;
        while (iptr < str_.length() && jptr<str2.str_.length()){
            if (str_[iptr] == str2.str_[jptr]){
                //相等则比较下一位
                iptr++ ; jptr++;
            } else{
                //不相等则回溯,模式串指针从0 开始 i 回溯到原先的起始值+1 , 现值i'与原先的起始值i 满足 i'-i=j'-j其中j=0
                iptr = iptr - jptr+1;
                jptr = 0;
            }
        }
        if (jptr >=str2.str_.length()){
            return iptr - jptr;
        }
        return -1;
    }
    /**
     * <p> 1 . 求next数组,有了next数组后一个一个匹配,如果失配让 j = next[j];
     * @param substr
     * @param pos
     * @return
     */
    inline int SString::index_KMP(SString& substr, int pos) {
        int i=pos, j=0;
        int next[substr.str_.length()];
        substr.getnext(next);
        int thisLen=length(),sublen=substr.length();
        while ( i < thisLen && j < sublen){
            if (j==-1 || str_[i] == substr.str_[j]){
                i++;
                j++;
            } else{
                j=next[j];
            }
        }
        if (j >= sublen){
            int ret =i-sublen;
            return ret;
        }
        return -1;
    }

    inline SString SString::replace(SString src, const SString& target) {
        if (src.str_.empty()){
            return *this;
        }
        int index=0;
        while ( index != -1) {
            index = index_KMP(src);
            if(index != -1) {
                str_.erase(index,  src.str_.length());
                str_.insert(index, target.str_);
            }
        }
        return *this;
    }

    /**
     * <p>原理: 当求next的第j个元素时,看  j-1 个元素开始和第0个元素比对,k不断增加取最大值满足  0<k<j
     * 从后往前数k个即第 j-k+1...j-1元素与 0...k-1
     * 如  abaabcac    当(a)j=0 next[0]=0 ; (b)j =1 ,next[1]=1,;(a) j=2时,k=1,第1个元素和第0个元素比对即a和b比不对就是1
     *  当(a)j=3,k=1,第2个元素和第0个元素 比a和a匹配上了 那就是next[3]=2;
     * @param substr
     * @return
     */
    inline void SString::getnext(int next[]) {

        const int len =str_.length();
//        next.resize(len,-1);
        int i = 0,j=-1;next[0]=-1;
        while (i<len){
            if (j==-1 ||str_[i] == str_[j]){
                ++i;++j;
                if (str_[i] != str_[j]){
                    next[i]=j;
                } else{
                    next[i]=next[j];
                }
            } else{
                j=next[j];
            }
        }
        //return next;
    }
}

example:

//
// Created by hu on 2020/8/6.
//

#include <iostream>
#include "sdstructure/linearlist/SString.cpp"
using namespace std;
using namespace SmartDongLib;
int main(){
    SString mainstr("acabaabaabcacaabc");
    SString substr("abaabcac");//-1   0   -1   1   0   2   -1   1
    SString target("ijn");
    int next[substr.length()];
    substr.getnext(next);
    for (int i :next){
        cout<<i<<"   ";
    }


    int index=mainstr.index_KMP(substr);
    int index2=mainstr.index(substr);
    cout<<endl<<index;
    cout<<endl<<index2;
    mainstr.replace(substr,target);
    cout<<endl<<mainstr.get();
}

猜你喜欢

转载自blog.csdn.net/superSmart_Dong/article/details/107924487