在传统字符串匹配中我们求得字符串p出现在字符串s中的位置。我们把字符串s称为主串,字符串p称为模式串。
KMP算法的原理简单来说就是匹配的时候不回溯主串的指针i,而只回溯模式串指针j ,即匹配过程中,不移动主串,指移动模式串来达到"尽可能向右滑动最大的距离"。
s[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 | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
模式串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] 匹配必定失配,这次比较就显得多余。那么我们怎么知道失配的时候,模式串要向右移多少个单位呢?
假设图1此时主串要正在匹配的位置是 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]
s[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 | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
模式串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
}
}
s[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 | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
模式串p | a | b | a | a | b | c | a |
图3
s[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 | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
模式串p | a | b | a | a | b | c | a | c |
图4.
.
s[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 | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
模式串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};
s[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;next[i]=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();
}