zoukankan      html  css  js  c++  java
  • kmp学习笔记(模板)

    介绍

    Knuth-Morris-Pratt字符串查找算法(简称为KMP算法
    用于字符串的模式匹配。相比朴素算法,它的时间复杂度为O(n + m),其中n是文本串长度,m是模式串长度。

    原理

    kmp通过计算模式串的next数组作为辅助来减少朴素算法中重复的比对。

    next数组代表模式串的最长相同真前后缀。例如:在模式串s =“ABABCD”中,它的next数组为next=[-1 0 0 1 2 0 0]。其中next[0]为-1为的是保证完全失配时不陷入死循环。

    假设有文本串t="ABADDABABD"。开始以位置0为起点,在匹配到t[0:2]="ABA"都相等;但是匹配到t[3] = 'D' != s[3] = 'C',这时不需要从位置1重新开始。因为由next[3]=1知道,s串位置3之前最长相同真前后缀为1,即s[0:0] == s[2:2]。因此可以直接从文本串的位置3开始和模式串的位置1匹配,即next[3]位置。

    如何构造next数组?假设包括位置 i 前的next数组已经构建完成,设j = next[i]。如果s[i + 1] == s[j + 1],那么显然next[i + 1] = j + 1;否则,由于s[0 : next[j]]存在真前缀和s[0 : i - 1]真后缀相等,故令j = next[j],如果s[i + 1] == s[j + 1],那么next[i + 1] = j + 1,否则重复上述过程,直到j = next[0] = -1代表不存在相同的真前后缀,那么next[i + 1] = 0。

    时间复杂度O(n)

    实现

    板子都很好写

    //求next
    int nt[N]; //next
    void getnext(char t[]) {
        int i = 0, j = -1;
        nt[i] = j;
        while(t[i]) {
            if(j == -1 || t[i]  == t[j]) {
                i++;
                j++;
                nt[i] = j;
            } else {
                j = nt[j];
            }
        }
    }
    
    void search(char s[], char t[]) { //s文本串,t模式串
        int i = 0;
        int j = 0;
        while(s[i]) {
            if(j == -1 || s[i] == t[j]) {
                i++;
                j++;
                if(!t[j]) { //找到了
                    //处理
                    j = nt[j]; //继续找。如果只找一次可以直接break
                }
            } else {
                j = nt[j];
            }
        }
    }
    

    应用

    除了匹配模式串,还可以找循环节,找最长相同前后缀等等。具体看题。

    扩展kmp

    设文本串为s(长度为n),模式串为t(长度为m)。扩展kmp作用是求extend数组。其中extent[i]代表t和s[i : n-1]的最长相同前缀。

    由于当存在extend[i] == m时,就起到kmp算法的功能,故又称扩展kmp。

    求extend数组需要求next数组作为辅助。这里next[i]代表t和t[i : m - 1]的最长相同前缀。假设next数组已经求出,下面求extend数组。

    设p为匹配过程中能达到的最右边界。设a为p对应位置,即extend[a] = p - a。假设包括i - 1前的extend数组都已经求出。那么首先,i的位置必定位于[a,p]区间之内。那么i位置对应在next上的位置为i - a。有两种情况:

    1. 如果i + next[i - a]小于p,那么可得extend[i] = next[i - a];
    2. 如果i + next[i - a] 大于等于p ,那么在直接在t[p - i]的基础上扩展就好了。注意是p - i而不是p - a,因为是在前缀的基础上扩展。然后更新p和a。

    求next数组其实就是t自己对自己求extend数组的过程。

    板子很好写

    int extend[N]; 
    int nt[N]; //next
    void getnext(char t[]) {
        int a = 0, p = 0;
        int len = strlen(t);
        nt[a] = len;
        for(int i = 1; i < len; i++) {
            if(i >= p || i + nt[i - a] >= p) { 
                if(i >= p) p = i;
                while(p < len && t[p] == t[p - i]) p++;
                nt[i] = p - i;
                a = i;
            } else {
                nt[i] = nt[i - a];
            }
        }
    }
    
    void search(char *s, char *t) {
        int a = 0, p = 0;
        int n = strlen(s);
        int m = strlen(t);
        for(int i = 0; i < n; i++) {
            if(i >= p || i + nt[i - a] > p) { // i >= p 的作用:举个典型例子,S 和 T 无一字符相同
                if(i >= p) p = i;
                while(p < n && p - i < m && s[p] == t[p - i]) p++;
                extend[i] = p - i;
                a = i;
            } else {
                extend[i] = nt[i - a];
            }
        }
    }
    
    
    

    相关题目

    kuangbin kmp专题

    扩展kmp:
    Clairewd’s message
    Period II
    Count the string

    参考

    https://oi-wiki.org/string/kmp/

  • 相关阅读:
    poj 3616 Milking Time
    poj 3176 Cow Bowling
    poj 2229 Sumsets
    poj 2385 Apple Catching
    poj 3280 Cheapest Palindrome
    hdu 1530 Maximum Clique
    hdu 1102 Constructing Roads
    codeforces 592B The Monster and the Squirrel
    CDOJ 1221 Ancient Go
    hdu 1151 Air Raid(二分图最小路径覆盖)
  • 原文地址:https://www.cnblogs.com/limil/p/12695071.html
Copyright © 2011-2022 走看看