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<bits/stdc++.h>
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<T.size()){
            break;
        }
        for(int j=0;j<T.size();j++){//j是模式串的位置指针
            if(S[i]==T[j]){
                i++;
                if(j==T.size()-1){
                    flag=1;
                    ans=pos+1;//S下标是从零开始,故ans在pos基础上加1
                    break;
                }
                continue;
            }
            else{
                pos++;
                break;
            }
        }
        if(flag==1){
            break;
        }      
    }
    cout<<ans<<endl;
    return 0;
}

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 不回溯。)

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

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

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。

next数组的代码:

#include<bits/stdc++.h>
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.size()-1){//p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k])nxt[++j]=++k;
        else k=nxt[k];       
    }
}
int main(){
    string p;
    cin>>p;
    getNext(p);
    for(int i=0;i<p.size();i++)
        cout<<nxt[i]<<' ';
    cout<<endl;
    return 0;
}

运行:


nxt.png

改进:

void getNext(string p){
    // 初始条件
    int j=0;
    int k=-1;
    nxt[0]=-1; 
    // 根据已知的前j位推测第j+1位
    while(j<p.size()-1){//p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k]){
            ++j;
            ++k;
            if(p[j]!=p[k])nxt[j]=k;//优化
            else nxt[j]=k;
        }
        else k=nxt[k];       
    }
}

改进原因:当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] ]。
如图:
s_3!=p_3注意 p_1==p_3

image

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

完整版kmp算法

#include<bits/stdc++.h>
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<p.size()-1){//p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k]){
            ++j;
            ++k;
            if(p[j]!=p[k])nxt[j]=k;//优化
            else nxt[j]=k;
        }
        else k=nxt[k];       
    }
}
int kmp(int lens,int lenp){
    int i=0,j=0,ans=-1;//如果最后ans==1,则匹配失败
    while(i<lens&&j<lenp){
        if(j==-1||s[i]==p[j]){
            i++;
            j++;
        }
        else j=nxt[j];
        if(j==lenp)return ans=i-lenp+1;//习惯上第一个字符下标为1
    }
    return ans;
}
int main(){
    //string s,p;
    cin>>s>>p;
    memset(nxt,-1,sizeof(nxt));
    getNext(p);
    int lens=s.size();
    int lenp=p.size();
    int ans=kmp(lens,lenp);
    cout<<ans<<endl;
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 字符串匹配KMP算法详解 1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有...
    张晨辉Allen阅读 2,382评论 0 3
  • 数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章、这篇文章、还有这篇他们写得非常...
    sunhaiyu阅读 1,722评论 1 21
  • 数据结构 第8讲 KMP算法 讲这个算法之前,我们首先了解几个概念: 串:又称字符串,是由零个或多个字符组成的有限...
    rainchxy阅读 1,266评论 0 3
  • 文章大纲:1.KMP算法概念2.KMP算法中最核心的next[] 数组是如何生成的3.使用KMP算法 匹配字符串 ...
    柠檬乌冬面阅读 806评论 0 3
  • KMP的由来 在KMP算法之前,对文本进行匹配时使用的是朴素模式匹配算法,也就是最简单匹配算法.当然运行效率也是让...
    圣光忏悔阅读 1,740评论 2 13