• KMP算法匹配原理以及C++实现


    原创作品,转载请注明出处:点我
    假设A表示目标字符串,A="abababaababacb",B表示匹配模式,B="ababacb"
    用两个指针i和j分别表示,A[i-j+1 .... i]与B[1...j]完全相等。也就是说,i是不断增加的,随着i的增加j相应的变化,且满足以A[i]结尾的长度为j的字符串正好匹配B串的前j个字符(j当然越大越好),现在需要jianyanA[i+1]和B[j+1]的关系。当A[i+1]=B[j+1]时,i和j各自增加一,什么时候j=m了,我们就说B是A的子串(B串已经完整了),并且跟根据这使得i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1...i]与B[1...j]保持匹配且新的B[j+1]恰好与A[i+1]匹配。
    i  = 1  2  3  4  5  6   7  8  9 10 11 12 13  14
    A = a  b  a  b  a  b  a  a  b  a   b   a   c   b
    B = a  b  a  b  a  c  b
    j  = 1  2  3  4  5  6  7
    当i,j等于5时,A[i+1]跟B[j+1]不相等,这是要缩小j为j'(也就是要把B字符串往右移)。我们发现,j'必须要使得B[1...j]中的头j'个字母和末j'个字母完全相等,这样j变成j'后才能继续保持i和j的性质。当然,j'越大越好。当心的j为3时,恰好符合要求。
    我们可以知道,新的j可以取多少跟i无关,只与B串有关。我们可以预处理出这样的一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配的时候,心的j的最大值是多少。
    以B="ababacb"为例,解释P[j]数组的求结果
    a b a b a c b
    0 0 1 2 3 0 0
    1、首字符a,一律设为0,即P[1]=0
    2、“ab” 第一个字符为a,最后一个字符为b,不相等,所以长度为0,即P[2]=0
    3、“aba”,头两个字符串为“ab”,后两个为"ba",不相同,头一个字符串为a,后一个也为a,相同,所以长度为1,即P[3]=1
    4、"abab",头两个为ab,后两个为ab,相同,头三个位aba,末尾三个为bab,不同,所以最大长度为2,即P[4]=2
    5、"ababa",头三个位aba,末尾三个也为aba,头四个为abab,末尾四个为baba,不同,所以最大长度为3,即P[5]=3
    以此类推,可以得出数组P[j]  
    求出了P[j]之后,就可以根据P[j]进行匹配了,还是以上面的A、B为例,匹配过程中用到的几个变量
    pattern表示B,Target表示A
    headIndex指向A中跟B进行匹配的子串的首字符
    targetIndex指向A中正在跟B匹配的字符的索引,patternIndex指向B中正在匹配的字符在B中的索引
    targetIndex等于向右移动的位数加上patternIndex,即targetIndex=headIndex-1+patternIndex
    第一步、
    此时,patternIndex= 1,targetIndex= 1,headIndex=1
    此时pattern[patternIndex] == target[targetIndex],然后patternIndex跟targetIndex增加一,再接着比较是否相同
    直到targetIndex跟patternIndex为6的时候,pattern[patternIndex] != target[targetIndex]
    此时,就需要把B向右移动,进行下一次的匹配,那移动多少比较好呢?这就需要根据P[j]来计算
    由于此时,patternIndex前面的ababa已经匹配了,P[5]=3,前面匹配的字符串ababa的长度为5,所以字符串pattern向右移动的位数为5-3=2,即pattern向右移动到3,即新的headIndex=headIndex+2=3;而新的patternIndex=P[5]+1=4,即新的patternIndex指向B串中的第四位,targetIndex=headIndex+patternIndex-1=3+4-1=6,所以移动之后的情况如下图
    此时,此时pattern[patternIndex] == target[targetIndex],然后patternIndex跟targetIndex增加一,再接着比较是否相同
    当patternIndex等于6,targetIndex等于8时,pattern[patternIndex] != target[targetIndex],又要把B串往右移,此时,
    P[5]=3,前面的ababa已经匹配,长度为5,所以向右移动的位数为5-3=2,此时,headIndex=headIndex+2=3+2=5,patternIndex=P[5]+1=4,指向B串中的第四位,targetIndex=headIndex+patternIndex-1=5+4-1=8,所以targetIndex指向A串中的第八位
    此时pattern[patternIndex] != target[targetIndex],又要把B串往右移,此时前面的aba已经匹配成功,长度为3,P[3]=1,所以往右移动的长度为3-1=2,移动两位,此时,headIndex=headIndex+2=5+2=7,patternIndex=P[3]+1=1+1=2指向B串中的第二位,targetIndex=headIndex+patternIndex-1=7+2-1=8,指向A串的第八位
    此时pattern[patternIndex] != target[targetIndex],又要把B串往右移,此时前面已经匹配的串为a,长度为1,P[1]=0,往右移动的位数为1-P[1]=1-0=1;
    此时,headIndex=headIndex+1=7+1=8,patternIndex=P[1]+1=1,指向B串的第一位,targetIndex=headIndex+patternIndex-1=8+1-1=8,指向A串的第八位,如图所示
    此时再一次匹配,就会匹配成功。
    下面是KMP算法的C++实现,有点小问题
     1 #ifndef __KMP__H__
     2 #define __KMP__H__
     3 #include <string>
     4 #include <vector>
     5 using namespace std;
     6 
     7 class KMP{
     8 public:
     9              //void static getNext(const string &str,vector<int> &vec);
    10              int kmp();
    11             KMP(){}
    12             KMP( const string &target,const string &pattern):mTarget(target),mPattern(pattern){}
    13             void setTarget(const string &target);
    14             void setPattern(const string &pattern);
    15 private:
    16             vector< int> mVec;
    17             string mTarget;
    18             string mPattern;
    19             void getNext();
    20 };
    21 #endif

    下面是源代码实现

     1 #include "KMP.h"
     2 #include <iostream>
     3 #include <vector>
     4 using namespace std;
     5 
     6 
     7 //获取字符串str的所有子串中相同子集的长度
     8 //比如字符串ababacb,分别获取字符串a,ab,aba,abab,ababa,ababac,ababacb中D
     9 //最前面和最后面相同的子串的最大长度,比如
    10 //a:因为aa为a单个字符,所以最前面和最后面相同的子串的最大长度为a0
    11 //aba,最前面一个a和最后面一个元a素a相同,所以值为a1,abab最前面2个ab和最后面两个ab相同,值为a2
    12 //ababa最前面3个为aaba,最后面3个为aaba,所以值为a3
    13 void KMP::getNext()
    14 {
    15       mVec.clear(); //清空?ec
    16       //vec.push_back(0);//为a了使用方便,vec的第一个数据不用
    17       mVec.push_back(0); //第一个字符的下一个位置一定是0,比如"ababacb",首字符a的值为0
    18       string::const_iterator start = mPattern.begin();
    19       string::const_iterator pos = start + 1;
    20       while(pos != mPattern.end())
    21       {
    22             string subStr(start,pos+1); //获取子字符串
    23             int strLen = subStr.size() - 1;//获取子串中D前后相同的子子串的最大长度
    24             do
    25             {
    26                    string prefix(subStr,0,strLen); //获取subStr中D的前面strLen子集
    27                    string postfix(subStr,subStr.size()-strLen,strLen); //获取subStr中D的前面?trLen子集
    28                    if(prefix == postfix)
    29                    {
    30                            mVec.push_back(strLen);
    31                            break;
    32                     }
    33                     --strLen;
    34                     /如果前后相同的子集的长度小于一
    35                     /说明没有相同的,则把0压栈
    36                     if(strLen < 1)
    37                        mVec.push_back(0);
    38              } while(strLen > 0);
    39 
    40              ++pos;
    41        }
    42 }
    43 
    44 void KMP::setPattern(const string &pattern)
    45 {
    46       mPattern = pattern;
    47 }
    48 
    49 void KMP::setTarget(const string &target)
    50 {
    51       mTarget = target;
    52 }
    53 
    54 
    55 
    56 
    57 int KMP::kmp()
    58 {
    59      getNext(); //首先获取next数据
    60      int targetIndex = 0;
    61      int patternIndex = 0;
    62      int headIndex = 0;//指向跟pattern匹配的Target的第一个元素的索引
    63      while(patternIndex != mPattern.size() && targetIndex != mTarget.size())
    64      {
    65            for(int i = 0; i < mPattern.size()-1;++i)
    66            {
    67                   if(mPattern[patternIndex] == mTarget[targetIndex])
    68                   {
    69                          ++patternIndex;
    70                          ++targetIndex;
    71                          if(mPattern.size()== patternIndex)//如果已经匹配成功,则退出循环
    72                                  break;
    73                    }
    74                    else
    75                    {
    76                          if(0 == patternIndex)//如果第一个字符就不匹配,则把mTarget左移一位
    77                                   ++headIndex;
    78                          else
    79                          {
    80                                   headIndex += patternIndex - mVec[patternIndex-1];//由于vector索引从零开始,所以要减去一
    81                                   patternIndex = mVec[patternIndex-1];//更新patternIndex索引
    82                          }
    83                          targetIndex = headIndex + patternIndex;//跟新targetIndex索引
    84                          break;
    85                     }
    86 
    87             }
    88       }
    89 
    90       return headIndex;
    91 }
  • 相关阅读:
    字符串导出xml文件并弹出下载对话框
    Resource interpreted as Document but transferred with MIME type application/json
    org.w3c.dom document 和xml 字符串 互转
    Java xml出现错误 javax.xml.transform.TransformerException: java.lang.NullPointerException
    plsql developer中如何设置sql window显示行号
    Oracle表数据转换为XML格式数据
    Java用于取得当前日期相对应的月初,月末,季初,季末,年初,年末时间
    JS 中Json常用操作
    详解html中的marquee属性
    .net core 3.0中的Json API
  • 原文地址:https://www.cnblogs.com/rio2607/p/4575883.html
Copyright © 2020-2023  润新知