KMP算法

1. KMP字符串匹配

原理 @_皎月半洒花

  • 朴素的单模式串匹配大概就是枚举每一个文本串元素,然后从这一位开始不断向后比较,每次比较失败之后都要从头开始重新比对,大概期望时间复杂度在 \(\Theta(n+m)\) 左右,对于一般的弱数据还是阔以跑的了滴。但是其实是可以被卡成 \(\Theta(nm)\) 的。
  • \(KMP\) 的精髓在于,对于每次失配之后,我都不会从头重新开始枚举,而是根据我已经得知的数据,从某个特定的位置开始匹配;而对于模式串的每一位,都有唯一的“特定变化位置”,这个在失配之后的特定变化位置可以帮助我们利用已有的数据不用从头匹配,从而节约时间。
  • 比如我们考虑一组样例:
模式串:abcab
文本串:abcacababcab
  • 首先,前四位按位匹配成功,遇到第五位不同,而这时,我们选择将 \(abcab\) 向右移三位,或者可以直接理解为移动到模式串中与失配字符相同的那一位。可以简单地理解为,我们将两个已经遍历过的模式串字符重合,导致我们可以不用一位一位地移动,而是根据相同的字符来实现快速移动。
模式串:   abcab
文本串:abcacababcab
  • 但有时不光只会有单个字符重复:
模式串:abcabc
文本串:abcabdababcabc
  • 当我们发现在第六位失配时,我们可以将模式串的第一二位移动到第四五位,因为它们相同
模式串:   abcabc
文本串:abcabdababcabc
  • 那么现在已经很明了了, \(KMP\) 的重头戏就在于用失配数组来确定当某一位失配时,我们可以将前一位跳跃到之前匹配过的某一位。而此处有几个先决条件需要理解:
  1. 我们的失配数组应当建立在模式串意义下,而不是文本串意义下。因为显然模式串要更加灵活,在失配后换位时,更灵活简便地处理。
  1. 如何确定位置呢?
  • 首先我们要明白,基于先决条件 \(1\) 而言,我们在预处理时应当考虑当模式串的第 \(i\) 位失配时,应当跳转到哪里.因为在文本串中,之前匹配过的所有字符已经没有用了——都是匹配完成或者已经失配的,所以我们的 \(kmp\) 数组(即是用于确定失配后变化位置的数组,下同)应当记录的是:
  • 在模式串 \(str1\) 中,对于每一位 \(str1_i\) ,它的 \(kmp\) 数组应当是记录一个位置 \(j\) , \(j \leq i\) 并且满足 \(str1_i=str1_j\) 并且在 \(j!=1\) 时理应满足 \(str1_1\)\(str1_{j-1}\) 分别与 \(str_{i-j+1}\) ~ \(str1_{i-1}\) 按位相等
  • 上述即为移位法则
  • 3、从前缀后缀来解释 \(KMP\) :
给定串:ABCABA
前缀:A,AB,ABC,ABCA,ABCAB,ABCABA
后缀:A,BA,ABA,CABA,BCABA,ABCABA
  • 其实刚才的移位法则就是对于模式串的每个前缀而言,用 \(kmp\) 数组记录到它为止的模式串前缀的真前缀和真后缀最大相同的位置。然而这个地方我们要考虑“模式串前缀的前缀和后缀最大相同的位置”原因在于,我们需要用到 \(kmp\) 数组换位时,当且仅当未完全匹配。所以我们的操作只是针对模式串的前缀 —— 毕竟是失配函数,失配之后只有可能是某个部分前缀需要“快速移动”。所以这就可以解释 \(KMP\) 中前后缀应用的一个特点:
  • \(KMP\) 中前后缀不包括模式串本身,即只考虑真前缀和真后缀,因为模式串本身需要整体考虑,当且仅当匹配完整个串之后;而匹配完整个串不就完成匹配了吗

实现

  • 如何求kmp数组(即next数组)
  • 初始化:下标从1开始;next_2=next_1=0;
  • 自己和自己匹配
    -A- b ……
……  -A- b 
  • 我们要找到两段相同的部分(通过自身的next数组),并且下一个字符也相同,那么我们可以将匹配的部分增加一个
for(int i=2,j=0;i<=N;i++){
	while(j&&str[j+1]!=str[i]){
		j=nxt[j];
	}
	if(str[j+1]==str[i]){
		j++;
	}
	nxt[i]=j;
	num[i]=num[j]+1;
}
  • 如何用next数组
  • 每次匹配文本串的一位,让模式串不断往回跳直到匹配
  • 匹配后将模式串向后移动一位
  • 如果模式串匹配完了,就记录匹配位置
for(int i=1,j=0;i<=lt;i++){
    while(j&&P[j+1]!=T[i]){
        j=fail[j];
    }
    if(P[j+1]==T[i]){
        j++;
    }
    if(j==lp){
        printf("%d\n",i-lp+1);
        j=fail[j];
    }
}


例题

1.【模板】KMP字符串匹配

2.P4391 [BOI2009]Radio Transmission 无线传输




2.KMP+dp

例题

1.[NOI2014]动物园

以此题为例,用朴素的kmp一遍一遍地跳,统计相同前后缀总个数,会被卡到\(\Theta(nm)\),此时我们需要引入dp,跳到上一个匹配的串时,因为前后的串是匹配的,所以可以直接继承前面的串的所有子问题

-A- …… -A-


posted @ 2019-07-02 15:24  guoshaoyang  阅读(159)  评论(0编辑  收藏  举报