[String,算法]字符串匹配的KMP算法:
字符串匹配的KMP算法是由Knuth、Morris 和 Pratt三位教授共同发明,其无回溯算法的精巧程度令人叹为观止.
网上相关的参考:
http://mhss.nease.net/string/KMP.html
下面给出我的实现,算法的精华是代码背后的分析,这里就不再详述了,尤其是复杂度分析相当有难度.具体请参考相关的资料.
1算法思路:
在matchStr中找是否含有patternStr子串: (这里字符串的实现用的是字符串的顺序表示)
1.a)外层的无回溯层:
将matchStr与patternStr进行比较:
1) 如果patternStr的当前位置与matchStr的当前位置不匹配:
1.1)若patternStr的当前位置为patternStr的首位置,则matchStr的位置直接加1.
1.2)若patternStr的当前位置不为patternStr的首位置,则matchStr的位置停在当前位置,patternStr的下标回溯到前面的一个恰当位置,利用next数组进行回溯.
2) 如果patternStr的当前位置与matchStr当前位置匹配,则patternStr和matchStr的位置均前进1.
3)当patternStr或matchStr的长度到达其长度时,跳出,否则继续做1,2.
4)如果matchStr达到其长度,则输出找到的位置,为matchPos-patternPos+1.
如果patternStr达到其长度,则输出没有找到的信息.
1.b)Next数组的实现思路:
Next数组主要是标志出一个字符数组的当前位置的最大真前,真后子缀的下标位置,这里采用了一个改进算法,为了配合1.2里的工作,让next数组里的下标尽可能的往前移动.
这一部分对应的程序段为:
if(patternStr->c[i]==patternStr->c[k])//make the match quicker.
nextArr[i]=nextArr[k];
else
nextArr[i]=k;
在真前与真后子缀的查找的算法同样有技巧:
对于patternStr里的第i个位置,如果它的前缀数组的位置值为k,则下一个位置的前缀数组的最大位置只有可能为k+1,成立条件为next[i]==next[k],如果没有找到,则下一个位置的在前缀数组中的值为-1.
这一部对应的程序段为:
while(k>=0&&patternStr->c[i]!=patternStr->c[k])
k=nextArr[k];
//exit1:k=-1; exit2:p->c[i]=p->c[k],has true pre and rear zi zhui.
当这些算法的细节都了解清楚后,你对KMP的实现已基本没有问题的,就像我一样,但对其算法的正确性,效率的分析还是得下一翻工夫才能真正领悟.
数据结构定义:
顺序型字符串:
#define STRING_LEN 15000
/*---Start of SeqStirng---*/
struct SeqString;
typedef struct SeqString* PSeqString;
struct SeqString
{
char c[STRING_LEN];
int len;
};
/*Core Algorithm:KMP Alogorithm*/
int kmpMatch1(PSeqString sourceStr,PSeqString patternStr)
{
int* preArr;
int lenPre;
int pos=-1;
int pScr,pMatch;
int i;
assertF(sourceStr!=NULL,"in kmpMatch,pass in sourceStr is null\n");
assertF(patternStr!=NULL,"in kmpMatch,pass in patternStr is null\n");
lenPre=patternStr->len;
preArr=(int*)malloc(sizeof(int)*lenPre);
assertF(preArr!=NULL,"in kmpMatch,preArr mem apply faillure\n");
makeNext(patternStr,preArr);
/*Core Algorithm of KMP*/
pScr=0;
pMatch=0;
while(pScr<=sourceStr->len-1&&pMatch<=patternStr->len-1)
{
if(pMatch==-1||sourceStr->c[pScr]==patternStr->c[pMatch])
{ //current pos has no match||current pos has temp match
pScr++;
pMatch++;
}
else
{
//not match,roll back the next arr.
pMatch=preArr[pMatch];
}
}
if(pMatch!=patternStr->len)
pos=-1;
else
pos=pScr-pMatch;
return pos;
}
void makeNext(PSeqString patternStr,int* nextArr)
{
int i,k;
k=-1;
i=0;
nextArr[0]=-1;
while(i<patternStr->len-1)
{
while(k>=0&&patternStr->c[i]!=patternStr->c[k])
k=nextArr[k];
//exit1:k=-1; exit2:p->c[i]=p->c[k],has true pre and rear zi zhui.
i++;//move to current valued pos.
k++;//next arr num adjust,
if(patternStr->c[i]==patternStr->c[k])//make the match quicker.
nextArr[i]=nextArr[k];
else
nextArr[i]=k;
}
}
为了便于对比,给出一个简单的回溯型的匹配算法的实现:
int simpleMatch(PSeqString sourceStr,PSeqString patternStr)
{
int i,j;
int pos;
int flag;
assertF(sourceStr!=NULL,"in simple match,sourceStr is null\n");
assertF(patternStr!=NULL,"in simple match,patternStr is null\n");
flag=0;
j=0;
i=0;
while(i<sourceStr->len)
{
while(patternStr->c[j]==sourceStr->c[i]&&j<patternStr->len)
{
i++;j++;
}
if(j==patternStr->len)
{
flag=1;
break;
}
else
{
i=i-j+1;
j=0;
}
}
if(flag)
pos=i-j;
else
pos=-1;
return pos;
}
源码下载: (测试程序是在10000个字符里找helloWorld子串)
好了,就介绍这些,附上源码:
http://emilmatthew.51.net/downloads/kmpTest.rar
//如果上面这个链接无法响应下载(有可能是被网站给屏蔽掉了),则可使用下载工具(如迅雷等)下载。欢迎提出批评与指正意见