KMP算法

很有启发的几篇文章:
文章传送门:
文章一:KMP算法的Next数组详解
文章二:从头到尾彻底理解KMP
文章三:字符串匹配的KMP算法
首先说说字符串模式匹配问题:
问题描述:子串的定位操作通常称作串的模式匹配,即查找子串在主串中的位置。我们把主串称为S,子串称作模式串T,用 i 和 j 指示主串S和模式串T中当前正待比较的字符位置。
算法基本思想:从主串S的第pos个字符起和模式的第一个字符比较之,若相等,则继续逐个比较下个字符(pos值不变);若不相等,从主串的pos++个字符起再重新和模式串的第一个字符比较。依次类推,直到模式串T中的每个字符依次和主串S中的一个连续的字符序列相等,则匹配成功,函数值为主串里的匹配串第一个字符在主串中的序号,若匹配不成功,函数值返回0.

上图:

image

匹配过程如下:
1:pos=1,i=1;j=1;S[i]==T[j],继续比较到i=6,j=6时S[6]!=T[6]
2:pos=2,i=2;j=1;S[i]!=T[j]
3:pos=3,i=3;j=1;S[i]!=T[j]
4:pos=4,i=4;j=1;S[i]==T[j],i=5;j=2;S[i]==T[j]一直到i=9;j=6时匹配成功,函数值为4.

数据结构描述:

//求子串位置的定位函数 Index(S,T,pos)
int Index(SString S,SString T,int pos){
    //返回子串T在主串中第pos个字符之后的位置。若不存在,则函数值为0.
    //其中,T非空,1<=pos<=StrLength(S)
    i=pos;j=1;
    while(i<=S[0]&&j<=T[0]){
        if(S[i]==T[j]){//继续比较后续字符
            i++;
            j++;
        }
        else{//指针后退重新开始匹配
            i=i-j+2;
            j=1;
        }
    }
    if(j>T[0])return i-T[0];
    else return 0;
}//Index

代码:

#include
using namespace std;
int main(){
    string S,T;//S是主串,T为模式串
    cin>>S>>T;
    int pos=0,flag=0,ans=-1;//flag==1,则匹配成功
    while(1){
        int i=pos;//i是主串的位置指针
        if(S.size()-i

KMP算法介绍:
这个算法是由高德纳和沃恩·普拉特在1974年构思,同年詹姆斯·H·莫里斯也独立地设计出该算法,最终由三人于1977年联合发表。这种算法被称为 Knuth-Morris-Pratt 操作,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置。

上图:

image

kmp过程
1:i=1;j=1.继续逐一比较直到i=6;j=6时S[i]!=L[j]
2:i=4;j=1.继续逐一比较直到i=9;j=6匹配成功

显然,KMP算法改进之处在于,每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右”滑动“尽可能远的一段距离后继续进行比较。那么问题来了,模式串向右“滑动”的可行距离多远,即当主串中第 i 个字符与模式中第 j 个字符比较不等时,主串中第 i 个字符应与模式串中哪个字符进行比较?(注意, i 不回溯。)

由此引出数组:
令;则表明当模式串中第个字符与主串第个字符“失配”时,在模式串中需重新和主串中该字符进行比较的字符的位置。即就是模式串向右“滑动”的距离。所以问题在于如何求数组?

引出前缀和后缀:假设主串'',模式串为'',并假设此时应与模式串中第个字符继续比较,则模式串中前个字符的子串必须满足关系式,且不可能存在满足下列关系式
''=''
已经得到的“部分匹配”的结果是
''=''
由式和式推得下列等式
''=''
式子用文字描述:
模式串中存在两个相同的子串,该子串以模式串的第一个字符为首字符,长度为,就是"前缀"和"后缀"的最长的共有元素的长度。"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
举例:

image

- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[C,BC],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[D,CD,BCD],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],
后缀为[A,DA,CDA,BCDA],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为 [B,AB,DAB,CDAB,BCDAB],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[D,BD,ABD,DABD,CDABD,BCDABD],共有元素的长度为0。

求数组的代码:

#include
using namespace std;
const int maxn=100;
int nxt[maxn];
void getNext(string p){
    // 初始条件
    int j=0;
    int k=-1;
    nxt[0]=-1; 
    // 根据已知的前j位推测第j+1位
    while(j>p;
    getNext(p);
    for(int i=0;i

运行:


nxt.png

改进:

void getNext(string p){
    // 初始条件
    int j=0;
    int k=-1;
    nxt[0]=-1; 
    // 根据已知的前j位推测第j+1位
    while(j

改进原因:当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ],必然导致后一步匹配失败(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配),所以不能允许p[j] = p[ next[j ]]。如果出现了p[j] = p[ next[j] ]咋办呢?答案是需要再次递归,即令next[j] = next[ next[j] ]。
如图:
!=注意 ==

image

若不优化,此时应该找到nxt[j],此时j==3,
因此此nxt[3]==1;而p[nxt[3]]==b;是无效操作。

完整版算法

#include
using namespace std;
const int maxn=100;
int nxt[maxn];
string s,p;
void getNext(string p){
    // 初始条件
    int j=0;
    int k=-1;
    nxt[0]=-1; 
    // 根据已知的前j位推测第j+1位
    while(j>s>>p;
    memset(nxt,-1,sizeof(nxt));
    getNext(p);
    int lens=s.size();
    int lenp=p.size();
    int ans=kmp(lens,lenp);
    cout<

你可能感兴趣的:(KMP算法)