全部博文(512)
分类: C/C++
2008-01-10 12:52:47
KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为O(m*n);KMP匹配算法。可以证明它的时间复杂度为O(m+n).。
int Index_BF ( char S [ ], char T [ ], int pos )
{
int i = pos, j = 0;
while ( S[i+j] != '\0'&& T[j] != '\0')
if ( S[i+j] == T[j] )
else
{
i ++; j = 0; // 重新开始新的一轮匹配
if ( T[j] == '\0')
else
如:T=”abCabCad” 则 next[6]=-1,因T[3]=T[6]
下标 | 0 | 1 | 2 | 3 | 4 |
T | a | b | c | a | c |
next | -1 | 0 | 0 | -1 | 1 |
下标 | 0 | 1 | 2 | 3 | 4 |
T | a | b | c | a | b |
next | -1 | 0 | 0 | -1 | 0 |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
T | a | b | a | b | c | a | a | b | c |
next | -1 | 0 | -1 | 0 | 2 | -1 | 1 | 0 | 2 |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
T | a | b | C | a | b | C | a | d |
next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 4 |
next[5]= 0 根据 (3) 虽T[0]T[1]=T[3]T[4],但T[2]==T[5]
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
T | a | d | C | a | d | C | a | d |
next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 0 |
void get_nextval(const char *T, int next[])
{
int j = 0, k = -1;
next[0] = -1;
while ( T[j/*+1*/] != '\0' )
{
if (k == -1 || T[j] == T[k])
{
++j; ++k;
if (T[j]!=T[k])
next[j] = k;
else
next[j] = next[k];
else
k = next[k];
void getNext(const char* pattern,int next[])
{
next[0]= -1;
int k=-1,j=0;
while(pattern[j] != '\0')
{
if(k!= -1 && pattern[k]!= pattern[j] )
k=next[k];
++j;++k;
if(pattern[k]== pattern[j])
next[j]=next[k];
else
next[j]=k;
}
}
#include
{
if( !Text||!Pattern|| Pattern[0]=='\0' || Text[0]=='\0' )//
int len=0;
const char * c=Pattern;
{
}
int index=0,i=0,j=0;
while(Text[i]!='\0' && Pattern[j]!='\0' )
{
if(Text[i]== Pattern[j])
{
++j;
}
else
{
index += j-next[j];
if(next[j]!=-1)
else
{
j=0;
++i;
}
}
if(Pattern[j]=='\0')
else
return -1;
int main()//abCabCad
{
char* text="bababCabCadcaabcaababcbaaaabaaacababcaabc";
char*pattern="adCadCad";
//getNext(pattern,n);
//get_nextval(pattern,n);
cout<
return 0;
五.其他表示模式值的方法
上面那种串的模式值表示方法是最优秀的表示方法,从串的模式值我们可以得到很多信息,以下称为第一种表示方法。第二种表示方法,虽然也定义next[0]= -1,但后面绝不会出现 -1,除了next[0],其他模式值next[j]=k(0≤k
下面给出几种方法的例子:
表一。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
T | a | b | a | b | c | a | a | b | c |
(1) next | -1 | 0 | -1 | 0 | 2 | -1 | 1 | 0 | 2 |
(2) next | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 1 | 2 |
(3) next | 0 | 1 | 0 | 1 | 3 | 0 | 2 | 1 | 3 |
第三种表示方法,在我看来,意义不是那么明了,不再讨论。
表二。
下标 | 0 | 1 | 2 | 3 | 4 |
T | a | b | c | a | c |
(1)next | -1 | 0 | 0 | -1 | 1 |
(2)next | -1 | 0 | 0 | 0 | 1 |
表三。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
T | a | d | C | a | d | C | a | d |
(1)next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 0 |
(2)next | -1 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
对比串的模式值第一种表示方法和第二种表示方法,看表一:
第一种表示方法next[2]= -1,表示T[2]=T[0],且T[2-1] !=T[0]
第二种表示方法next[2]= 0,表示T[2-1] !=T[0],但并不管T[0] 和T[2]相不相等。
第一种表示方法next[3]= 0,表示虽然T[2]=T[0],但T[1] ==T[3]
第二种表示方法next[3]= 1,表示T[2] =T[0],他并不管T[1] 和T[3]相不相等。
第一种表示方法next[5]= -1,表示T[5]=T[0],且T[4] !=T[0],T[3]T[4] !=T[0]T[1],T[2]T[3]T[4] !=T[0]T[1]T[2]
第二种表示方法next[5]= 0,表示T[4] !=T[0],T[3]T[4] !=T[0]T[1] ,T[2]T[3]T[4] !=T[0]T[1]T[2],但并不管T[0] 和T[5]相不相等。换句话说:就算T[5]==’x’,或 T[5]==’y’,T[5]==’9’,也有next[5]= 0 。
从这里我们可以看到:串的模式值第一种表示方法能表示更多的信息,第二种表示方法更单纯,不容易搞错。当然,用第一种表示方法写出的模式匹配函数效率更高。比如说,在串S=“adCadCBdadCadCad 9876543”中匹配串T=“adCadCad”, 用第一种表示方法写出的模式匹配函数,当比较到S[6] != T[6] 时,取next[6]= -1(表三),它可以表示这样许多信息: S[3]S[4]S[5]==T[3]T[4]T[5]==T[0]T[1]T[2],而S[6] != T[6],T[6]==T[3]==T[0],所以S[6] != T[0],接下来比较S[7]和T[0]吧。如果用第二种表示方法写出的模式匹配函数,当比较到S[6] != T[6] 时,取next[6]= 3(表三),它只能表示:S[3]S[4]S[5]== T[3]T[4]T[5]==T[0]T[1]T[2],但不能确定T[6]与T[3]相不相等,所以,接下来比较S[6]和T[3];又不相等,取next[3]= 0,它表示S[3]S[4]S[5]== T[0]T[1]T[2],但不会确定T[3]与T[0]相不相等,即S[6]和T[0] 相不相等,所以接下来比较S[6]和T[0],确定它们不相等,然后才会比较S[7]和T[0]。是不是比用第一种表示方法写出的模式匹配函数多绕了几个弯。
为什么,在讲明第一种表示方法后,还要讲没有第一种表示方法好的第二种表示方法?原因是:最开始,我看严蔚敏的一个讲座,她给出的模式值表示方法是我这里的第二种表示方法,如图:
她说:“next 函数值的含义是:当出现S[i] !=T[j]时,下一次的比较应该在S[i]和T[next[j]] 之间进行。”虽简洁,但不明了,反复几遍也没明白为什么。而她给出的算法求出的模式值是我这里说的第一种表示方法next值,就是前面的get_nextval()函数。匹配算法也是有瑕疵的。于是我在这里发帖说她错了:
现在看来,她没有错,不过有张冠李戴之嫌。我不知道,是否有人第一次学到这里,不参考其他资料和明白人讲解的情况下,就能搞懂这个算法(我的意思是不仅是算法的大致思想,而是为什么定义和例子中next[j]=k(0≤k
书归正传,下面给出我写的求第二种表示方法表示的模式值的函数,为了从S的任何位置开始匹配T,“当出现S[i] !=T[j]时,下一次的比较应该在S[i]和T[next[j]] 之间进行。” 定义next[0]=0 。
void myget_nextval(const char *T, int next[])
{
// 求模式串T的next函数值(第二种表示方法)并存入数组 next。
int j = 1, k = 0;
next[0] = 0;
while ( T[j] != '\0' )
{
if(T[j] == T[k])
{
next[j] = k;
++j; ++k;
}
else if(T[j] != T[0])
{
next[j] = k;
++j;
k=0;
}
else
{
next[j] = k;
++j;
k=1;
}
}//while
for(int i=0;i
{
cout<
}
cout<
}// myget_nextval
下面是模式值使用第二种表示方法的匹配函数(next[0]=0)
int my_KMP(char *S, char *T, int pos)
{
int i = pos, j = 0;//pos(S 的下标0≤pos
while ( S[i] != '\0' && T[j] != '\0' )
{
if (S[i] == T[j] )
{
++i;
++j; // 继续比较后继字符
}
else // a b a b c a a b c
// 0 0 0 1 2 0 1 1 2
{ //-1 0 -1 0 2 -1 1 0 2
i++;
j = next[j]; /*当出现S[i] !=T[j]时,
下一次的比较应该在S[i]和T[next[j]] 之间进行。要求next[0]=0。
在这两个简单示范函数间使用全局数组next[]传值。*/
}
}//while
if ( T[j] == '\0' )
return (i-j); // 匹配成功
else
return -1;
} // my_KMP
六.后话--KMP的历史
Cook于1970年证明的一个理论得到,任何一个可以使用被称为下推自动机的计算机抽象模型来解决的问题,也可以使用一个实际的计算机(更精确的说,使用一个随机存取机)在与问题规模对应的时间内解决。特别地,这个理论暗示存在着一个算法可以在大约m+n的时间内解决模式匹配问题,这里m和n分别是存储文本和模式串数组的最大索引。Knuth 和Pratt努力地重建了 Cook的证明,由此创建了这个模式匹配算法。大概是同一时间,Morris在考虑设计一个文本编辑器的实际问题的过程中创建了差不多是同样的算法。这里可以看到并不是所有的算法都是“灵光一现”中被发现的,而理论化的计算机科学确实在一些时候会应用到实际的应用中。