题目1:KMP算法
题目原文:http://hihocoder.com/contest/hiho3/problem/1
【题目解读】
就是KMP算法的基本使用之一。添加了计数功能。
KMP算法的核心思想,就是通过减少匹配失败时模式串的回溯位数,来减少匹配次数。此时回溯的时候,使用 next 数组。
求解 next 数组见:http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html,需要注意的是,这里用到了数学归纳法。即:
假设:根据定义next[0]=-1。然后,假设next[j]=k, 即P[0...k-1]==P[j-k,j-1]
则有:1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;
2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]
next 数组的求解,可以视为相错一位的模式串,其中一个作为原始串,另外一个作为模式串,进行匹配,失败则回溯
【编写细节】
(1)想要理解KMP算法的主体比较部分,建议先实现 BF算法,就是最普通的暴力匹配。如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char ori[1000005];
char par[10005];
int next[10005];
int main()
{
int t;
scanf("%d", &t);
long olen;
int plen;
int ans;
while(t--)
{
ans = 0;
memset(ori, 0, sizeof(ori));
memset(par, 0, sizeof(par));
memset(next, 0, sizeof(next));
scanf("%s", par);
scanf("%s", ori);
olen = strlen(ori);
plen = strlen(par);
for(long i=0; i<olen; i++)
{
long j = i;
int k = 0;
while(ori[j] == par[k])
{
j++;
k++;
if(k >= plen) break;
}
if(k>=plen) ans++;
}
printf("%d\n", ans);
}
return 0;
}
对照这个再去写KMP的主题匹配,就能理解 for 里面的第一个while为了找到原始串与模式串的第一个匹配点(不一定是模式串的第一个字符);
(2)KMP里面如果不匹配就回溯(绝不用--),一律使用k = next[k],不会使用到 k--;只要回溯了第一次,之后不匹配即可不断回溯直至找到匹配或者直接到头;
(3)一定要注意!KMP算法需要单独考虑模式串与原始串长度均为1时的情况!次数 next 数组只有一个值-1;
(4)此题是KMP匹配的计数功能,所以需要原始串的一位一位字符扫描(for循环),以防遗漏ABABA中有两个ABA的情况;
(5)其余细节写在代码注释。。。很难搞,当模板记下来吧。
【AC代码】
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char ori[1000005];
char par[10005];
int next[10005];
int t, plen;
long olen, ans;
void get_next(int len)
{
next[0] = -1;
int j = 0;
int k = -1;
while(j < len)
{
// 一直找到可以匹配的开始,或者无法匹配的开头
if(k==-1 || par[j] == par[k])
{
// 此时保证j不能已经加过
next[j+1] = k + 1;
j++;
k++;
}
else
{
k = next[k];
}
}
}
int main()
{
scanf("%d", &t);
while(t--)
{
ans = 0;
scanf("%s", par);
scanf("%s", ori);
plen = strlen(par);
olen = strlen(ori);
if(olen==1 && plen==1)
{
if(ori[0] == par[0])
ans = 1;
else
ans = 0;
printf("%d\n", ans);
continue;
}
get_next(plen);
int j = 0;
for(long i=0; i<olen; i++)
{
// while的顺序:一定要先找到一个可以匹配的起点
// j>=1杜绝出现next[j]=-1,因此单独考虑 olen=plen=1
while(ori[i] != par[j] && j>=1)
{
j = next[j];
}
if(ori[i] == par[j])
{
j++;
}
// 在找到匹配的第一个字符前,i不能继续++
// else
// {
// j = next[j];
// }
if(j >= plen)
{
ans++;
// 回溯过程中不会遗漏,最差情况从头匹配
j = next[j];
}
}
printf("%d\n", ans);
}
return 0;
}