KMP算法

先注明,本文章转载于自己的CSDN博客

KMP算法用于字符匹配工作

朴素匹配方法的复杂度是O(N*M)
KMP算法复杂度达到了O(N+M)
从这表达式来说,复杂度明显地降低了
但这个算法最大的问题,就是很难理解。 在网上看了很多人,包括有个哥大家都说他讲得已经是最好的那个了,但是我还是没有看明白他在说什么,只是大概了解了他的思路。
回去之后,翻了下数据结构和算法的书,认认真真地看了遍,收获很大。 特在这做一下笔记,方便大家一起学习。


KMP算法的意思就是(这里采用我在很多书上看到的理解之后的一个汇总版) 在那之前,我们要明白两个东西
1.目标串:就是我们要比对的串(就是那个往往都会比模式串更长的那个)
2.模式串:就是那个检查在目标串中是否匹配的那个串
在KMP算法中,模式给我们提供了一个工具,通过这个工具,使得我们减少了时间,这个工具就是next函数(这个就是KMP算法中最难理解的一部分)。
一般来说,我们在比较两个串的时候,都是采用先比较各个位置,要是不对,就将指向模式串的指针复原,即下一次检查就从模式串的开头来看。 然后指向目标串的指针向后移动一个位置。
但是KMP算法告诉我们,其实在移动时候,我们不必要傻傻地移动一位,而移动的位数,我们可以通过我们要比对的对象知道。 而把这个移动的位数,或者说是下一个要比对的对象下标记下来,那么之后遇到在这个点不匹配的时候,我就直接移动到对应的位置,而不需要只移动一位,这样就节省了时间。

而这个储存,我们就是采用next实现的,不管你之前是看了多少其他的说法,请把之前对于next函数的看法放到一边,再认真看我下面的文字


这里采用的是,next[j] = k 表示,第j个位置的模式串和对应的目标串中的字符不匹配的时候,就用第k位的,模式串中字符和目标串的失配的位置进行比较
next函数不仅仅表示了位置,同时也描述了长度,这点在后面的文字中慢慢理解,这里先讲述出来


看到这里,你可以理解了KMP算法的大致操作了。如果没有,请再看一遍上面的文字,一个被称之为难的算法,只看一遍就能懂,那真是很高的传授技术加上双方的好运了。
next函数的生成,可以说是最难理解的一部分,但是,我相信,这也只是临门一脚的问题。 因为,next函数的生成就是按照KMP算法本身来产生的。 试想一下,next函数不正是在同一个串内的字符串的比较么? 为了理解这个,我废了很大功夫
我做了这样的一个假设: 两个空字符一定相等。这一点非常重要

那么用递推公式给出next 如果前面的已经匹配好了。(先不管匹配了有多长,是从哪开始了) 但是我们保证了前面的那个一定是匹配好的。
我们可以做上面的假设的原因就是我们之前讲到的那个空字符一定是相等的,那么我就可以说这对空字符是匹配好的
因为最小,我们都可以说有一对空字符相等,所以,就可以做上面的假设。
那么就开始匹配了。我们在这里恰好使用以模式串开头部分作为了我们所说的模式串。(这就是前缀)
将next[0]设置为-1。 意思是,如果这个不匹配,就用next[-1]来和这个开头的不匹配的字符来进行匹配。 但是next[-1]是不存在是,在我的这里,就假设为了那个空字符串。
所以,就是将这个最开头那个空的(不存在的)那个字符,匹配当前的串,不就是将整个串向后移动一位(至少对于第一个串中不匹配的对象是这样的)。
到这里,关于为什么next[0]要取-1要清楚了
之后呢,往下走。如果这个位置都匹配的,那么下面向下移动的之后的那个位置(假设匹配不对的位置),每一个串是前面那个字符的匹配情况加一。本质上是双方本来就往后移动了,那么就直接赋值就好了。
如果不匹配,那么由于我们采用的是前面是模式串,后面是目标串的抽象定义。就是说,前面那些字符的next值已经算好了的,那么就直接用前面算好的next值进行回溯。 用之前KMP的算法的思路,就实现了所有模式串的next值。 next值生成代码如下:

void getNext(int next[]) {
    int j = 0, k = -1, length = str.size();
    // 其中k表示的是前面的那个串,而j表示的是后面那个串,用同一个串的不同起始点的串进行比较 
    // 前面的那个串去匹配后面的那个串,通过这个方式来进行匹配(也是一个KMP) 
    next[0] = -1;  // 表示如果开头不能匹配,就用这个前面那个-1来匹配,也就是后移动一位 
    while(j < length) {
        if (k == -1 || str[j] == str[k]){
            j ++; k ++;
            next[j] = k;
        } else {
            k = next[k];
        }
    }
}

看完上面的文字,就要把KMP算法看懂了,之后再看hiho1015

和一般的KMP不同,这一题,要求对于匹配出来的串进行计数。
就是看究竟匹配了几次?

对于原来的KMP的改的地方就是

if (k == str.size()) {
    ans += 1;
    k = next[k];
}

加了上这个函数地方。
这里的意思是,如果匹配完成了,就将计数数字加一,并且,将原来的进行回溯,如果这个回溯理解不了,那么是因为前面的KMP还没有看懂,要再看一次,就可以明白,这里保证不管对不对,在这个串的位置,一定是不匹配的(你的长度都超过了还能对???),那就一定要回溯。
文末有原题链接

代码如下:

#include <iostream>
using namespace std;

string str, ptr;
void getNext(int next[]) {
    int j = 0, k = -1, length = str.size();
    // 其中k表示的是前面的那个串,而j表示的是后面那个串,用同一个串的不同起始点的串进行比较 
    // 前面的那个串去匹配后面的那个串,通过这个方式来进行匹配(也是一个KMP) 
    next[0] = -1;  // 表示如果开头不能匹配,就用这个前面那个-1来匹配,也就是后移动一位 
    while(j < length) {
        if (k == -1 || str[j] == str[k]){
            j ++; k ++;
            next[j] = k;
        } else {
            k = next[k];
        }
    }
}

int KMP_Ans(int next[]) {
    int j = 0, k = 0, length = ptr.size(), ans = 0;
    while (j < length) {
        if (k == -1 || str[k] == ptr[j]) {
            j ++; k ++;
            if (k == str.size()) {
                ans += 1;
                k = next[k];
            }
        } else {
            k = next[k];
        }
    }
    return ans;
} 


int main(){
    int time;
    cin >> time;
    while (time--){
        cin >> str>> ptr;
        int *a = new int [str.size() + 1];// 将数组开稍微大点 
        getNext(a);
        cout << KMP_Ans(a)<< endl;
        delete[] a; 
    }
}

hihocoder原题链接
但要登录好了之后,才能直接跳转到题目,否则就是只有登录链接,不过可以登录完了之后,再来点击这个,也就可以直接访问了。

CSDN原文链接
有彩色字,可能会更好看一点。

欢迎关注我的公众号:肥宅Sean笔记


肥宅Sean笔记
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容