1:简单的模式匹配算法(朴素的模式匹配算法)
该算法由于要回退,所以算法的时间复杂度为最坏的情况下O(n * m),n,m分别为主串和模式串的长度
code:
//返回t子串在ch[i....n]主串中的位置,位置从零开始,如果不存在,返回-1
int indexOf (char *ch,char *t,int i) {
int lenCh = strlen (ch);
int lenT = strlen (t);
int j = 0;
while (i < lenCh && j < lenT) {
if (ch[i] == t[j]) {
i++;
j++;
}else {
//回退
i = i - j + 1;
j = 0;
}
}
if (j >= lenT) {
return i - lenT;
}else {
return -1;
}
}
总结:
虽然时间复杂度为O(n*m),但是在一般的情况下,其中实际的执行时间近似于O(n+m),所以至今仍然很多地方在使用。记得以前用过java 中Strring类的indexOf这个函数,于是看了一下String类的indexOf(String target,int fromIndex)的实现,使用的就是该简单算法。
2:
由于有了回退,就导致了简单模式匹配的算法时间最坏的情况下时间复杂度为O(n*m),,那么有没有一种可能,当出现主串中的S[i]字符和模式串中T[j]不匹配时,下标i不需要回退,
而是将模式尽可能的向右滑动,让S[i]和T[k]再进行比较,即是:当主串中下标为i的字符与模式串中下标为j的字符失配时,主串中的下标为i的字符应该和模式串中的哪个字符进行比较。
主串为s[0]s[1]s[2]s[3].....s[n],模式串为t[0]t[1]t[2]t[3].....t[m]
假设在某次失配的时候主串中下标为i的字符和模式串中下标为k的字符比较,则有:
“t[0]t[1]t[2]t[3]....t[k-1]” = = “s[i-k]s[i-k+1]s[i-k+2]...s[i-1]”, (1)
从已经得到部分匹配,则有:
“t[j-k]t[j-k+1]t[j-k+2].....t[j-1]”== “s[i-k]s[i-k+1]s[i-k+2]...s[i-1]” , (2)
综合(1)和(2)得出:
“t[0]t[1]t[2].....t[k-1]” == “t[j-k]t[j-k+1]t[j-k+2].....t[j-1]”(3)
前者可以看成是部分匹配的前缀,后者可以看成是部分匹配的后缀。
即前缀等于后缀,这里还不可以得到是最大前缀应该等于最大后缀。
看一个具体的例子:
s[0],s[1],s[2]......s[i-j],s[i-j+1]....s[i-1],s[i],s[i+1]...s[n]
t[0],t[1]...........t[j-1],t[j],t[j+1].....t[m]
假设主串和模式串在s[i]和t[j]处失配。
(1):t[0]向后移动一位
s[0],s[1],s[2]......s[i-j],s[i-j+1],s[i-j+2]....s[i-1],s[i],s[i+1]...s[n]
t[0], t[1].................. t[j-1],t[j],t[j+1].....t[m]
如果s[i-j+1] = t[0],s[i-j+2] = t[1]...s[i] = t[j-1](很显然是最大前缀和后缀),则主串中的s[i]在和t[j]失配的情况下,s[i]应该和t[j-1]再进行比较。否则,即s[i-j+1] = t[0],s[i-j+2] = t[1]...s[i] = t[j-1]其中至少有一个不等,此时,主串和模式串就没有比较必要,继续执行(1),知道找到一个最大前缀等于后缀或者不存在这样的前缀和后缀停止。
用一个next[j]来表示模式串在下标j处和主串在下标i处失配,主串s[i] 应该和模式串中的哪个位置的字符比较。很显然next[]数组的求值与主串是无关的。
下面求解next数组:
已知:next[j] = k;其中0<=k < j-1。
(1):当:t[k] == t[j],则next[j+1] = next[j] + 1
(2)当:t[k] != t[j],此时模式串即是模式串也是主串,在k位置失配,k’ = next[k],
如果:t[j] == t[k’],则next[j+1] = k’ + 1即next[j+1] = next[k] + 1;
如果t[j] != t[k’],重复进行(2)过程,直到找到一个 _k值,使得t[_k] == t[j]或者t[j] = 0;
根据上面的想法具体的代码如下:
#define MAX_LENGTH 20
int next[MAX_LENGTH];
/**求next函数的算法*/
void getNext (char * ch) {
int i = 0,j = -1;
int length = strlen (ch);
next[0] = -1;//下标从零开始
while (i < length-1) {
if (j == -1 || ch[i] == ch[j]) {
++i;
++j;
next[i] = j;
}else {
j = next[j];
}
}
}
int indexOf (char * s,char * t,int fromIndex) {
int i,j;
int sLen = strlen (s);
int tLen = strlen (t);
getNext (t);
i = fromIndex;
j = 0;
while (i < sLen && j < tLen) {
if (j == -1 || s[i] == t[j]) {
++i;
++j;
}else {
j = next[j];
}
}
if (j >= tLen) {
return i - tLen;
}else {
return -1;
}
}
int main () {
char *ch = "abxye";
char *ch2 = "xy";
int position = indexOf (ch,ch2,1);
printf ("%d\n",position);
return 0;
}
Example:
i
S:a b a b a b c
T:a b a b c
j
1:首先有next[0] = -1;
2:i = 0,j = -1; 因为j == -1,所以有:i = i + 1 = 1,j = j + 1 = 0 ,next[1] = 0;
3:i = 1,j = 0;因为t[i] = t[1] = b,t[j] = t[0] = a, a != b所以:j = next[j] = next[0] = -1。
4:i = 1,j=-1;因为j == -1,所以:i = i + 1 = 2, j = -1 + 1 = 0 ,next[2] = 0;
5:i = 2,j = 0;因为t[2] = t[0] = a,所以:i = 2 + 1 = 3,j = 0 + 1 = 1,next[3] = j = 1;
6:i = 3,j = 1;因为t[3] =t[1] = b,所以:i = 3 + 1 = 4,j = 1 + 1 = 2;,next[4] = 2;
改进后的next函数如下:
void get_nextval(char const* ptrn, int plen, int* nextval)
{
int i = 0;
nextval[i] = -1;
int j = -1;
while( i < plen-1 )
{
if( j == -1 || ptrn[i] == ptrn[j] ) //循环的if部分
{
++i;
++j;
//修正的地方就发生下面这4行
if( ptrn[i] != ptrn[j] ) //++i,++j之后,再次判断ptrn[i]与ptrn[j]的关系
nextval[i] = j; //之前的错误解法就在于整个判断只有这一句。
else
nextval[i] = nextval[j];
}
else //循环的else部分
j = nextval[j];
}