字符串模式匹配
【定义】是求第一个字符串(模式串 )在第二个字符串(主串)中的位置。
一.简单模式匹配算法
【算法描述】 从主串S指定字符开始(一般为第一个)和模式串T的第1个字符比较,若相等,则继续逐个比较后续字符,直到T中的每个字符依次和S中的一个连续字符序列相等,则称匹配成功;如果比较过程中有某对字符不相等,则从主串S的下一个字符再重新和T的第一个字符比较。如果S中的字符都比完了仍然没有匹配成功,则称匹配不成功。
【伪代码】
Intindex(Sstring S,Sstring T)
{
Int i=1,j=1;
While(i<s[0]&&j<T[0])
{
If(S[i]==T[i])
{
++i;
++j;
}
Else
{
I=i-j+2;
J=1;
}
}
If(j>T[0]) //表示T中字符被逐个比较完毕
Return i-T[0];
}
一. KMP算法
【改进】每当一趟匹配过程中出现字符不等时,不需回溯i指针,而是利用已得到的“部分匹配”的结果将模式串向右”滑动“ 尽可能远的一段距离后继续进行比较。
假设原始串为S,长度为n,模式串为T,长度为m.目前匹配到如下位置。
假设长度为j-1的S串对应结果已经求出,即next[ j-1 ]的函数值k已经求出,现在要求next[j]的函数值,由于 1~k-1的子串与j-k~j-1的子串是匹配的。下面需分两种情况讨论:
1. 模式串下标k处的字符与j-1处字符匹配,即1~k位置的子串与j-k~j-1位置的子串匹配,则next[j]=k+1,即next[j]=next[j-1]+1
2. 模式串下标k处字符与j-1处不匹配,此时我们要做的是等效于向前移动S2串,消除j-1处的不匹配,移动到哪个位置合适呢?显然是next[k]。因为这其实又是一次模式串匹配的问题,假设next[ k ]=k’ (next[k]表示当模式串匹配到T[k]遇到失败时,在模式串中需要重新和主串匹配的位置),相当于找到了S串中这么一对子串 1~k’-1和 j-1-(k’+1)~j-1是匹配的, 回到1,否则回到2
3)将S[j-1]与S[k]进行比较:
a.如果相等,则该next[j]=k+1
b.如果不等,令k=next[k],若k不等于0,跳到3;若k等于0,next[j]=1(1表示模式串第一个字符)
【普通版】
void get_next(char T[],int next[])
{
inti=1;
next[1]=0; //next[1]初始化,next数组定义的
intj=0;
while(i<=T[0])
{
if(j==0||T[i]==T[j]) //j=0情况是模式串需重新开始比对
{
++i;
++j;
next[i]=j;
}
else
j=next[j];
}
}
【增强版】
void get_nextval(char T[],int next[])
{
inti=1;
next[1]=0;
intj=0;
while(i<=T[0]-'0')
{
if(j==0||T[i]==T[j])
{
++i;
++j;
if(T[i]!=T[j]) //避免子串中重复出现一系列字符
next[i]=j;
else
next[i]=next[j];
}
else
j=next[j];
}
}
int KMP(char S[],char T[],int next[],intpos)
{
//利用模式串T的next函数求T在主串S第pos个字符之后的位置
//其中T[]非空,1<=pos<strlen(S)
inti=pos;
intj=1;
while(i<=S[0]&&j<=T[0])
{
if(j==0||S[i]==T[j]) //j=0情况是模式串重新回到了第一个字符的位置
{
++i;
++j;
}
else
j=next[j];
}
if(j>T[0])
returni-T[0];
else
return0;
}
【小结】尽管简单模式匹配算法时间复杂度为O(n*m),kMP时间复杂度为O(m+n),但在一般情况下,简单模式实际时间执行近似O(n+m),因此至今仍被采用。KMP算法,仅仅是在主串和子串有很多部分匹配时,才显得快很多,主要优点是主串不回溯。