kmp学习笔记(模板)

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] = j,否则重复上述过程,直到j = next[0] = -1代表不存在相同的真前后缀,那么next[i] = 0。

实现

板子都很好写

//求next
int next[N];
void getnext(char *t) {
    int i = 0, j = -1;
    next[i] = j;
    while(s[i]) {
        if(j == -1 || s[i]  == s[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}
void search(char *s, char *t) {
    int i = 0;
    int j = 0;
    while(s[i]) {
        if(j == -1 || s[i] == t[j]) {
            i++;
            j++;
            if(!s[j]) { //找到了
                //处理
                j = next[j]; //继续找。如果只找一次可以直接break
            }
        } else {
            j = next[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], next[N];
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] = next[i - a];
        }
    }
}

相关题目

kuangbin kmp专题

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

参考

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

猜你喜欢

转载自www.cnblogs.com/limil/p/12695071.html
今日推荐