前面讲到过BF算法,虽然简单,但是效率比较低,KMP算法对此做了很大改进,该算法是由Knuth,Morris,Pratt同时设计的,所以简称KMP算法
为什么说BF算法效率低呢?比如s="34343434345",t="345",在位置3时,s中字符为‘3’,t中为'5',不匹配,所以又重新从s中第2个位置,t中第1个位置开始匹配,匹配失败,又从s中第3....
可以看到,在s中后面子串"345"之前的匹配中很多都是不必要的,这无疑大大增加运行的次数,这些回溯都是不必要的
假设在某次si和tj匹配失败后,i不回溯,串t向右"移动"到某个位置,使得tk对准si继续向右进行。
首先,匹配失败因为si处的字符与tj处的字符不匹配,那么tj前面的j-1个字符都是匹配的,所以:
"t1t2t3...t(j-1)"="s(i-j+1)s(i-j+2)...s(i-1)" //1式
//t前面j-1个字符 //si前面j-1个字符
然后t向右"移动"到某个位置,使得tk对准si,那么tk前的k-1个字符是匹配的,所以
"t1t2t3...t(k-1)"="s(i-k+1)s(i-k+2)...s(i-1)" //2式
k肯定是小于j的,对吧,(之前是t串位置j处对准si,然后t向右"移动",使得tk对准si,所以k
"t(j-k+1)t(j-k+2)...t(j-1)"="s(i-k+1)s(i-k+2)...s(i-1)" //3式
由2式和3式可以得到:
"t1t2t3...t(k-1)"="t(j-k+1)t(j-k+2)...t(j-1)" //4式
这说明了什么呢?说明在某次si和tj匹配失败后,如果串t中满足前k-1个字符组成的串=串t中j-1位置前(包括j-1)k-1个字符组成的串,就可以将串t向右”移动”使得si对准tk,然后继续进行比较
next数组:
串中的每一个tj都对应一个k值,由上面的分析可知,k值仅仅取决于串t本身,与s无关,用next数组来存储tj对应的k值,next数组具有以下性质:
1: next[j]是一个整数,且0<=next[j]2: 为了是t右移但不丢失任何匹配成功的可能,当存在多个满足4式的k值时,
应选取最大的,使得t向右"移动"的距离最短,移动的字符为j-next[j]个
3:如果在tj前不存在满足4式的子串,定义next[j]=1,即用t1和si比较
因此,next数组定义如下:
当j=1时:
next[j]=0
当存在k满足4式时:
next[j]=k(若有多个k,选最大的)
当不存在上面的k时:
next[j]=1
比如有子串t=“abcaababc”,其next数组为:
next[]={0,1,1,122,3,2,3}
接下来就是KMP算法的执行过程了,初始时i=j=1;
如果si==tj,则i和j分别+1,如果si!=tj,则i不变,j回溯到next[j],然后再比较,如果si==tj,则i,j分别加1,继续匹配,以此类推,如果遇到next[j]==0,那么i,j也要分别增加1
以下是主串”aabcbabcaabcaababc”,子串”abcaababc”的匹配过程
怎么样,确实省去了很多不必要的比较吧,接下来就是实现了,那么如何求子串的next数组呢?
由定义知:
next[1]=0;
设next[j]=k,即有:
"t1t2...t(k-1)"="t(j-k+1)...t(j-1)"
那么next[j+1]等于什么呢?分两种情况:
1): 如果tk==tj,则:
"t1t2...t(k-1)tk"="t(j-k+1)...t(j-1)tj"
则next[j+1]=next[j]+1
2):如果tk!=tj,即:
"t1t2...t(k-1)tk"!="t(j-k+1)...t(j-1)tj"
此时可以将求next值的问题看成一个模式匹配问题,整个串即是主串又是
子串,在当前匹配过程中,已有next[j]=k,则应将串向右移动,使得第next[k]个字符和"主串"的第j个字符比较,如果存在K',那么
next[j+1]=next[k]+1
否则next[j+1]=1;
求解next数组需要好好体会
getNext函数:
void getNext(string t,int (&next)[20])
{
int i = 1,j=0;
next[1] = 0;
/*
假设next[j]=k,即"t1t2...t(k-1)"="t(j-k+1)t(j-k+2)...t(j-1)"
那么next[j+1]等于多少呢?
1):如果tk=tj,那么:
"t1t2...t(k-1)tk"="t(j-k+1)t(j-k+2)...t(j-1)tj"
所以next[j+1]=next[j]+1
2)如果tk!=tj,可以得到
next[j+1]=next[k]+1]或者为1
*/
while (i < t.length())
{
if (j == 0 || t[i-1] == t[j-1])
{
++i;
++j;
next[i] = j;
}
else
j = next[j];
}
}
KMP算法:
/*
模式匹配之KMP算法
*/
# include
# include
using namespace std;
void getNext(string t,int (&next)[20]);
int patternMatch_KMP(string s, string t,int (&next)[20]);
int main()
{
string s;
string t;
cout << "请输入主串:";
cin >> s;
cout << "请输入子串:";
cin >> t;
int next[20];
getNext(t,next);
int result = patternMatch_KMP(s, t,next);
if (result == -1)
cout << endl << "匹配失败" << endl;
else cout << endl << "子串在主串中的位置为:" << result << endl;
cout << endl << "子串的next数组为:" << endl;
for (int i = 1;i <= t.length();i++)
cout << next[i] << endl;
return 0;
}
int patternMatch_KMP(string s, string t, int (&next)[20])//返回子串t在串s第一次出现的位置(从1开始),若t不是s的子串
//返回-1
{
int i = 1, j = 1;
while (i <= s.length() && j <= t.length())//两个串都没扫描完
{
if ( j== 0||(s[i - 1] == t[j - 1]))//该位置上字符相等或者j==0,就比较下一个字符
{
i++;
j++;
}
else
{
j = next[j]; //i不变
}
}
if (j > t.length())
return (i - t.length());
return -1;
}
void getNext(string t,int (&next)[20])
{
int i = 1,j=0;
next[1] = 0;
/*
假设next[j]=k,即"t1t2...t(k-1)"="t(j-k+1)t(j-k+2)...t(j-1)"
那么next[j+1]等于多少呢?
1):如果tk=tj,那么:
"t1t2...t(k-1)tk"="t(j-k+1)t(j-k+2)...t(j-1)tj"
所以next[j+1]=next[j]+1
2)如果tk!=tj,可以得到
next[j+1]=next[k]+1]或者为1
*/
while (i < t.length())
{
if (j == 0 || t[i-1] == t[j-1])
{
++i;
++j;
next[i] = j;
}
else
j = next[j];
}
}