zoukankan      html  css  js  c++  java
  • 『看*』kmp字符串匹配算法

    学习KMP前提须知

    (kmp)是一个字符串快速匹配算法,它能够很快的在文本串中查找与模式串相同的字符串,在竞赛上多用于一些字符串的查找或着是某些算法的前置技能使用,其中一些算法思路可谓精华。

    算法内容

    竞赛需要用到的点

    1、Noip不太常考kmp,但普及组考的几率很大 (目前不太清楚Csp考不考,但是学了总没错

    2、kmp常用作于一些其他字符串算法的前置技能

    3、kmp的精华在于思路,而不在简单的代码

    KMP求字符串匹配略讲

    和网上博客涉及到真前缀和真后缀讲的不同(根据自己的理解

    需要了解两个知识

    文本串:你需要去遍历的文本

    模式串:你需要查找的字符串内容

    在文本串中寻找模式串,在刚学 OI 初期,很多人都是通过暴力枚举,看字符串的每一位是否匹配,实际上这样的方式是很低效的,最差的情况时间复杂度可以达到(O(n imes m)),那怎么才能更快的匹配呢?通过暴力我们不难看出每次匹配我们都得将文本串和模式串分开遍历,每一次遍历一个文本串内的字符都要遍历一遍模式串,那么有什么办法能让他们同时遍历呢?很简单,当模式串中出现重复的字串时我们就能够解决这个问题了,即使没有也能够成立。

    假设,一个模式串中没有出现重复的子串,那么可能会出现以下情况

    文本串 abcdefgiabcdefgh

    模式串 abcdefgh

    我们设两个指针(i, j),这两个指针分别代表遍历到文本串的某一个位置和模式串的某一个位置,这两个串的第一个位置我们都用编号1 来表示,那么很显然我们(i = j = 8)时,匹配就失败了,此时 (i) 继续变大,而 (j) 就要从 1 重新开始,那么直到(i = 16)(j = 8) 时,我们发现了这个匹配的字符了,此时我们遍历仅跟文本串的大小有关系,于是就是(O(n))

    很好,这下时间复杂度都是线性的了,这明显符合了我们的要求,那么我们继续考虑 若出现了重复的子串又是什么情况呢,如以下情况

    文本串 ababcababa

    模式串 ababa

    和上面的方法一样,我们设两个指针(i, j),这两个指针分别代表遍历到文本串的某一个位置和模式串的某一个位置,这两个串的第一个位置我们都用编号 1 来表示,那么很显然我们(i = j = 5)时,匹配就失败了,此时 (i) 暂时不变,而 (j) 从 2 重新遍历,然后发现(i = 5, j = 3)时,匹配仍然失败,那么此时 (i) 继续变大,而 (j) 从 1 重新开始,直到匹配结束

    很显然,我们跳跃的时候并不是直接跳到开始了,那这有是怎么一回事呢?

    对于一个模式串来说,若里面有相同的子串,那么我们就能够从后往前跳,因为匹配过的我们才能跳,因为是满足匹配需同位相同的条件的(可以脑补一下) 这就是kmp中最重要的思想和next数组

    注意,这里一直针对的都是模式串!

    那么根据上面的想法我们就能够得出代码

    // char a, char b //a 文本 b 模式
    int lenb = strlen(b + 1);
    nxt[1] = 0;
    for (int i = 2, j = 0; i <= lenb; i++) {
        if(j != 0 || b[i] != b[j + 1]) {
            j = nxt[j];
        } if(b[i] == b[j + 1]) j++;
        nxt[i] = j;
    }
    

    一道模板题LuoGuP3375

    //#define fre yes
    
    #include <cstdio>
    #include <cstring>
    
    const int N = 1000005;
    char c[N], s[N];
    int num[N], nxt[N];
    
    int main() {
        scanf("%s", c + 1);
        scanf("%s", s + 1);
        int lenc = strlen(c + 1);
        int lens = strlen(s + 1);
        nxt[1] = 0;
        for (int i = 2, j = 0; i <= lens; i++) {
            while(j != 0 && s[i] != s[j + 1]) {
                j = nxt[j];
            } if(s[i] == s[j + 1]) j++;
            nxt[i] = j;
        }
    
        int ans = 0;
        for (int i = 1, j = 0; i <= lenc; i++) {
            while(j != 0 && c[i] != s[j + 1]) {
                j = nxt[j];
            } if(c[i] == s[j + 1]) j++;
            if(j == lens) {
                ans++;
                num[ans] = i - lens + 1;
            }
        }
    
        for (int i = 1; i <= ans; i++) {
            printf("%d
    ", num[i]);
        }
    
        for (int i = 1; i <= lens; i++) {
            printf("%d ", nxt[i]);
        } return 0;
    }
    
  • 相关阅读:
    会计期间勿关闭后台打开
    动态创建VIEW
    约束变量
    JS 图片懒加载
    如何在Javascript中对数组的遍历使用异步函数
    JavaScript之闭包
    面试:数组去重你会几种呀?
    JavaScript重构技巧-降低函数复杂度
    JavaScript的内置对象
    js/jQuery获取data-*属性值
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11497447.html
Copyright © 2011-2022 走看看