KMP 是一种用来处理字符串匹配问题的算法。
对于长度分别为 的字符串 ,显然可以暴力地找到 在 中的所有出现位置:
for(int i=1;i<=n-m+1;i++)
{
bool f=true;
for(int j=1;j<=m;j++)
if(S[i+j-1]!=T[j])
{
f=false;
break;
}
}
if(f) printf("%d\n",i);
}
但是这样的算法时间复杂度是 的。
考虑改进这个算法,注意到下面这两个字符串匹配时:
S:ACACBEAC
T:ACACE
模式串匹配到 E
时发现失配了,但此时可以不仅仅把 移动一位,而是直接移动两位:
S:ACACBEEAC
T:^^ACACE
因为 已经匹配了,而在 中,前两位构成的前缀 和后两位构成的后缀 相同,所以可以移动两位。
对于一个字符串 ,定义 为 的 border 当且仅当 且 。那么当 的前 位已匹配,第 位失配时,就可以将 设置成 的最长的 border 的长度。
设 表示 的最长 border 的长度,那么匹配的过程就可以变成这样:
int j=0;
for(int i=1;i<=n;i++)
{
while(j!=0&&T[j+1]!=S[i]) j=kmp[j]; // 如果失配,那么不停移动 T
if(T[j+1]==S[i]) j++; // 能匹配就匹配
if(j==m) printf("%d\n",i-m+1); // 匹配成功
}
而 数组的求解就可以看作是 和自己匹配:
kmp[1]=0; // kmp[1]=0
for(int i=2,j=0;i<=m;i++) // T 和自己匹配
{
while(j!=0&&T[j+1]!=T[i]) j=kmp[j]; // 失配则不断移动 T
if(T[j+1]==T[i]) j++; // 匹配
kmp[i]=j; // 当前匹配的长度就是 kmp[i]
}
这样做的时间复杂度是 的。
时间复杂度证明:
首先预处理 数组是 的,因为 总共最多只会增加 次,每次跳 至少会让 减少 ,所以最多回跳 次。
匹配的时间复杂度是 的,具体原理同上。
实际上 数组相当于单串 AC 自动机的 数组。
一些拓展
-
想要求 的小于等于 或者 的最长的 border 也可以用类似求 数组的方法来求:
num[1]=0; for(int i=2,j=0;i<=n;i++) { while(j!=0&&a[j+1]!=a[i]) j=kmp[j]; if(a[j+1]==a[i]) j++; while(j*2>i) j=kmp[j]; num[i]=j; }
这段代码求的就是 表示 长度小于等于 的最长的 border 的长度。
这样做的时间复杂度是 的,证明类似求 数组的时间复杂度的证明。