在长度为n的数组T[n]中查找一个长度为m的数组P[m],如果用朴素字符串匹配方法要用O(mn)的时间,用自动机匹配要O(n)的时间,但一般的自动机要O(ml)的时间(l为字符集的宽度),而KMP只要O(m)的预处理时间。
其实最早接触字符串匹配自动机应该是在数字电路中的序列检测器那时候,序列检测器是用硬件区实现一个个状态的转换,这里和那儿是一个原理。主程序维持一个状态量mode是在读入某个字符后的匹配长度,它随着继续读入字符而不断变化,而变化的函数可以由模式P[m]和字符集去确定,确定这个函数的时间可以为O(ml)(根据《算法导论》,但是我的程序都是O(m^3 * l)的,不知道他是怎么实现的),以杭电1686题为例:
#include<stdio.h>
#include<string.h>
#include<memory.h>
#define MAXM 1000005
#define MAXN 10005
#define MAXW 26
char str[MAXM];
char key[MAXN];
int mode[MAXN][MAXW];
int FINITE_AUTOMATION_MATCHER();
void COMPUTE_TRANSITION_FUNCTION();
int main()
{
//freopen("Sample Input.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s%s",key,str);
printf("%d\n",FINITE_AUTOMATION_MATCHER());
}
return 0;
}
int FINITE_AUTOMATION_MATCHER()
{
int len1 = strlen(str);
int len2 = strlen(key);
int cnt = 0;
int cur_mode = 0;
memset(mode,0,sizeof(mode));
COMPUTE_TRANSITION_FUNCTION();
for(int i = 0;i < len1;i++)
{
cur_mode = mode[cur_mode][str[i] - 'A'];
cnt = cur_mode == len2 ? cnt + 1 : cnt;
}
return cnt;
}
void COMPUTE_TRANSITION_FUNCTION() //四层循环求转换函数,O(m^3 * l)的复杂度
{
int len = strlen(key);
for(int i = 0;i <= len;i++)
{
for(int j = 0;j < 26;j++) //写的有点仓促,很乱。。。。
{
int k = i + 1 > len ? len : i + 1;
int flag = 0;
for(;k > 0;k--)
{
flag = 1;
if(key[k - 1] - 'A' != j)
{
continue;
}
for(int s = 0;s < k - 1 && flag;s++)
{
flag = key[s] == key[i - k + s + 1] ? 1 : 0;
}
if(flag)
{
break;
}
}
mode[i][j] = k;
}
}
}
普通的自动机由于转移函数耗时太长TL了。KMP算法类似于自动机它也维持一个暂时的匹配长度mode,在读入下一个字符时如果匹配不成功,那么就要减小mode,求减小的量(前缀函数)就是KMP的核心。前缀函数f[i]是满足是模式P[i]的真后缀和前缀的最大长度,算法中求前缀函数的过程就是模式自己和自己匹配的过程,f[i]就是在读入第i个字符时和模式P的最大匹配长度,因此求前缀函数的过程和匹配过程高度相似,下面还是以hdu1686题为例:
#include<stdio.h>
#include<string.h>
#include<memory.h>
#define MAXM 1000005
#define MAXN 10005
char str[MAXM];
char key[MAXN];
int mode[MAXN];
int KMP_MATCHER();
void COMPUTE_PREFIX_FUNCTION();
int main()
{
//freopen("Sample Input.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s%s",key,str);
printf("%d\n",KMP_MATCHER());
}
return 0;
}
int KMP_MATCHER()
{
int cnt = 0;
int cur_mode = 0; //相当于自动机中的状态
int len1 = strlen(str);
int len2 = strlen(key);
memset(mode,0,sizeof(mode));
COMPUTE_PREFIX_FUNCTION();
for(int i = 0;i < len1;i++)
{
while(cur_mode > 0 && key[cur_mode] != str[i])
{
cur_mode = mode[cur_mode];
}
if(key[cur_mode] == str[i])
{
cur_mode++;
}
if(cur_mode == len2)
{
cnt++;
}
}
return cnt;
}
void COMPUTE_PREFIX_FUNCTION() //自己和自己匹配的过程
{
mode[1] = 0;
int k = 0; //已匹配长度
for(int i = 2;i <= strlen(key);i++)
{
while(k > 0 && key[k] != key[i - 1])
{
k = mode[k];
}
if(key[k] == key[i - 1])
{
k++;
}
mode[i] = k;
}
}
//7738004 2013-03-11 19:14:48 Accepted 1686 671MS 1268K 1152 B G++ 超级旅行者
算法还是不够快,
功力尚浅啊,还需修炼,看看有没有更快的算法。
参考文献:《算法导论》