几种常见的字符串匹配算法
常见的几种字符串匹配算法包括KMP算法,BM算法,Horspool算法,Sunday算法,fastsearch算法,KR算法等等。这里主要介绍一下KMP算法的匹配法则。
KMP算法思想:
KMP算法是由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。作为经典的前缀匹配算法,我们来探索一下它的神秘之处。
首先,简单粗暴的字符串匹配算法是这样的:
从左往右,text链(母链)的 i=4 和pat链(子链)的 j=4 匹配失败,遇到障碍了怎么办呢?
pat链整体右移了一步。
在 i=1 和 j=0 处匹配失败,又遇到麻烦了。。。
pat链它又往后移动一步。
这时在 i=4 和 j=2 处匹配失败。。。
你猜它会怎么做?聪明!它又往后移动一步。并把这种匹配模式贯彻到底,直到 i[6..10]时text和pat完全匹配,走了6步。
下面再来看一下KMP算法遇到麻烦是怎么解决的:
KMP遇上麻烦了。。。
KMP一步跳到这了。。。
快看,KMP又跳了!!
它向后走了一步。
快看,AC的色彩!!它匹配成功了!!同样的问题,它只用了4步。
你是怎么做到的呀?简单粗暴以无比崇拜的眼光看着KMP
KMP清了清嗓子,骄傲却谦卑:KMP算法的优点就是字符串匹配失败后的回溯时,利用它**next数组的记忆功能**,避免了再次匹配已经匹配过的字符,从而实现跳跃前进。
next数组是这样来的:
对于模式串 pat = ABABA ,执行这段程序
void get_next()
{
int i = 0, j = -1;
next[0] = -1;
while(i < lenp)
{
if(j == -1 || pat[i] == pat[j])
{
i++; j++;
next[i] = j;
}
else j = next[j];
}
}
你会得到这样一个序列:
这就是next[]数组了。
怎么样?发现了什么没?看 i = 4时, pat[2..4] == pat[0.. 2 ]。
再换一组:
看 i = 13,pat[7..13] == pat[0..6]。
发现了吧,其实**next数组的取值完全取决于模式串**,与主串无关。并且**next[ j ]的值要取到恰到好处**,以上例来讲,
next[ 13 ] == 6,这样就使得pat[ 0..6 ](从0到next[ 13 ])这7个字符与pat[ 7..13 ](13往前数7个)这7个字符完全一样。这就是恰到好处。
知道了这些,再结合上面的程序演示一些例子,我们会发现,我们肉眼凡胎就可以写出next数组的取值来。
随便给你一个模式串abcabcabcd,能直接写出它的next数组来吗?
说完了next数组,再看看KMP函数体吧!
void KMP()
{
int i = 0, j = 0;
while(i < lent && j < lenp)
{
if(j == -1 || text[i] == pat[j])
{
j++; i++;
}
else j = next[j]; /*这里用到了next数组*/
}
}
KMP()
{
int i = 0, j = 0;
while(i < lent && j < lenp)
{
if(j == -1 || text[i] == pat[j])
{
j++; i++;
}
else j = next[j]; /*这里用到了next数组*/
}
}
通过对主串text 和模式串pat 逐字符的比较,最终得以匹配。
各算法见仁见智的地方就是遇到“麻烦”时的应对策略。
拿刚开始的例子从开始走到结束亲自尝试一下
(建议回到刚开始亲自试一下)
最后,学习KMP算法要知道三句话:next数组起到记忆的作用;next数组的取值仅仅取决于模式串;next[ j ]的值要取到恰到好处。多了解一些别的算法思想对KMP算法的学习也有很大的帮助。
/* POJ 3461 代码附上 */
#include <stdio.h>
#include <string.h>
char text[1000002], pat[10002];
int next[10002];
int ans, lent, lenp;
void get_next()
{
int i, j;
i = 0; j = -1;
next[0] = -1;
while (i < lenp)
{
if (j == -1 || pat[i] == pat[j])
++i, ++j, next[i] = j;
else
j = next[j];
}
}
void KMP()
{
int i = 0, j = 0;
get_next();
while (i < lent)
{
if (j == -1 || text[i] == pat[j])
++i, ++j;
else
j = next[j];
if (j >= lenp)
{
ans++;
j=next[j]; //如果不能子链重合 这边要改成j=0;
}
}
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
ans = 0;
scanf("%s", pat);//子链
scanf("%s", text);//母链
lent = strlen(text);
lenp = strlen(pat);
KMP();
printf("%d\n", ans);
}
return 0;
}
#include <stdio.h>
#include <string.h>
char text[1000002], pat[10002];
int next[10002];
int ans, lent, lenp;
void get_next()
{
int i, j;
i = 0; j = -1;
next[0] = -1;
while (i < lenp)
{
if (j == -1 || pat[i] == pat[j])
++i, ++j, next[i] = j;
else
j = next[j];
}
}
void KMP()
{
int i = 0, j = 0;
get_next();
while (i < lent)
{
if (j == -1 || text[i] == pat[j])
++i, ++j;
else
j = next[j];
if (j >= lenp)
{
ans++;
j=next[j]; //如果不能子链重合 这边要改成j=0;
}
}
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
ans = 0;
scanf("%s", pat);//子链
scanf("%s", text);//母链
lent = strlen(text);
lenp = strlen(pat);
KMP();
printf("%d\n", ans);
}
return 0;
}