目录
【题记】每次遇到KMP,要么不了了之,要么简单套用模板,前几天刚背完模板,过几天啥都不记得了,没有一点点进步。
消除恐惧的最好方法就是面对恐惧。坚持,才是胜利。加油,奥里给!
本文参考了网上诸多关于KMP算法的解释与理解,力求通俗易懂。
KMP算法
0.问题
字符串匹配是计算机的基本任务之一。
字符串匹配是字符串搜索算法(String searching algorithms)又称字符串比对算法(string matching algorithms),是一种搜索算法,是字符串算法中的一类,用以试图在一长字符串或文章中,找出其是否包含某一个或多个字符串,以及其位置。
1.next数组的计算与含义
朴素算法就不说了,相信大家都会,不会也没事,因为会了也派不上用场。
KMP算法最重要的是“部分匹配表”(即next数组,感觉自我匹配表这个名字更合适),这个是KMP算法的核心,也是最重要且最难理解的部分。
以下表为例进行解释:
首先,我们需要知道真.前缀和真.后缀:
以“PYTHON”为例:
真.前缀:“P”,“PY”,“PYT”,“PYTH”,“PYTHO”
真.后缀:“YTHON”,“THON”,“HON”,“ON”,“N”
而所谓的部分匹配值就是:某字符串的真前缀和真后缀的最长匹配大小。
回到上表中,我们每次只看从第一个字符到现在观察的这个字符。
对于第一个元素a,真前缀和真后缀都为空,因此a的部分匹配值为0;
对于第二个元素b,我们只观察前两个元素"ab",此时,真前缀有:"a",真后缀有:"b",前缀和后缀不匹配,所以b的部分匹配值也为0;
对于第三个元素a,我们只观察前三个元素"aba",此时,真前缀有:"a","ab",真后缀有:"ba","a",因为"a"和"a"匹配,所以第三个元素a的部分匹配值为1;
依次类推,我们观察第7个元素"c",对于前7个元素"abababc",我们不需要一个一个看真前后缀了,通过分析,其每个真后缀一定包含元素"c",而其真前缀没有包含"c"的,因此,"c"的模式匹配值为0;
对于最后一个元素"a"来说,"abababca"的开头和结尾都是"a",因此其模式匹配值至少为1。除了"a"这个真后缀外,其余所有真后缀都是包含"c"的,而其所有真前缀中只有"abababc"包含"c",且这个真前缀不等于相同长度的真后缀"bababca",因此最后一个元素的部分匹配值为1。
由此,我们计算得到了部分匹配表的所有值。
2.使用Next数组
字符串匹配的朴素解法是一个字符一个字符的匹配,如果遇到不同的,就将短串整个后移一位,再从头开始比较。
例:
当a和c不匹配时,将"babca"向右移一位再开始比较
而KMP的想法是当a和c不匹配时,前面的"babc"都是匹配的,这应该可以用上,可以一次向后多移动几位,提高效率。而向后移动几位可以通过Next数组求出。
规则如下:如果最后一个匹配字符的部分匹配值大于1,则 向后移动位数 = 当前字符串已经匹配的长度 - 该部分匹配值,否则向后移动1位。
具体样例如下:
bacbababaabcbab
|
abababca
由前面计算出来的结果,第一个元素的部分匹配值为0,因此就向后移动1位。
bacbababaabcbab
|||||
abababca
当前最后一位匹配元素a的部分匹配值为3,且当前已匹配 5 位,因此下一步将向前移动 5 - 3 = 2 位,如下(x表示跳过):
bacbababaabcbab
xx|||
abababca
由此便介绍完了KMP算法的使用。还有对next数组计算的优化,以及代码详解有时间再更。
给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串P在模式串S中多次作为子串出现。
求出模板串P在模式串S中所有出现的位置的起始下标。
输入格式
第一行输入整数N,表示字符串P的长度。
第二行输入字符串P。
第三行输入整数M,表示字符串S的长度。
第四行输入字符串S。
输出格式
共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100010;
char a[N],b[N];
int ne[N],f[N];
void kmp(int n,char a[],int m,char b[])
{
for(int i=2,j=0;i<=n;i++)
{
while(j>0 && a[i]!=a[j+1]) j = ne[j];
if(a[i] == a[j+1]) j++;
ne[i] = j;
}
for(int i=1,j=0;i<=m;i++)
{
while(j>0 && (j==n || b[i]!=a[j+1])) j = ne[j];
if(b[i] == a[j+1]) j++;
f[i] = j;
if(f[i] == n) cout<<i - j<<' ';
}
}
int main()
{
int n, m;
cin>>n>>a+1>>m>>b+1;
kmp(n,a,m,b);
return 0;
}
下标从0开始的模板 :
const int N = 100010;
int ne[N],f[N];
void kmp(string a,string b)
{
ne[0] = -1;
for(int i=1,j=-1;i<a.size();i++)
{
while(j>=0 && a[i]!=a[j+1]) j = ne[j];
if(a[i]==a[j+1]) j++;
ne[i] = j;
}
for(int i=0,j=-1;i<b.size();i++)
{
while(j!=-1 &&(j== a.size()-1 || b[i]!=a[j+1])) j = ne[j];
if(b[i] == a[j+1]) j++;
if(j == a.size()-1)
//找到
}
}