1、朴素的模式匹配算法:用两个指针(数组角标)分别指向主串和子串,当字符相等时,指针(角标)都加一,否则,都退回到适当的位置,直到找到子串在主串中出现的位置为止。算法实现如下:
//返回子串t在主串s中第pos个位置的字符开始之后的位置,若不存在,则函数值返回-1
// 0<=pos<=strlen(s)-1
int index(char *s,char *t,int pos)
{
int len1 = strlen(s);
int len2 = strlen(t);
int i = pos;//i从pos位置开始,是主串s的下标
int j = 0;//j用于子串t中当前位置的下标
while(i<len1 && j<len2)
{
if (s[i] == t[j])
{
i++;
j++;
}
else
{
//i和j都回退
i = i-j+1;
j = 0;
}
}
if (j == len2)
{
return i-len2;
}
else
{
return -1;
}
}
2、KMP模式匹配算法:它是对朴素模式匹配算法的优化,用两个指针(数组角标)分别指向主串和子串,当字符相等时,指针(角标)都加一,否则,指向主串的指针或角标不变,而指向子串的指针或角标退回到适当地位置(这些位置存放在一个next数组当中),直到找到子串在主串中出现的位置为止。
这个next数组的长度和子串的长度相同,并且只与子串有关。它可由下面的公式表示:
j是next数组的下标,当j=0时,next[j]=-1;当j=1时,next[j]=0;在其他情况下,计算子串中当前字符之前的前缀串字符和后缀串字符相等的个数,并且前缀串字符不能包含当前字符的前一个字符,后缀串字符不能包含第一个字符。比如”abcabd”,若当前字符是c,则角标j是2,则前缀字符只有a,后缀字符只有b,又不相等,故next[2]=0,当j指向d时,前缀字符有”ab”,后缀串也有”ab”,故最大长度max=2,即next[j]=2.
计算next数组的代码如下:
**方法一:**
//通过计算返回子串t的next数组
void get_next(char *t,int *next)
{
int i,j;
i = 0;
j = -1;
next[0] = -1;
int len = strlen(t);
while (i < len-1)
{
if (j==-1 || t[i]==t[j])//t[i]表示后缀的单个字符,t[j]表示前缀的单个字符
{
++i;
++j;
next[i] = j;
}
else
{
j = next[j];//表示j回退的位置,若字符不相同,则j值回溯
}
}
}
**方法二**
int main()
{
char str[] = "aabaaaca";
int next[8];
next[0] = -1;
next[1] = 0;
for (int i=2;i<8;++i)
{
int count = next[i-1];
char *p = str + count;
char *s = str + i - 1;
if (*p == *s)
{
count++;
}
else
{
while (count>0)
{
p--;
if (*p != *s)
{
count--;
}
else
{
break;
}
}
}
next[i] = count;
}
return 0;
}
然后,KMP算法的实现如下:
//返回子串t在主串s中第pos个位置字符开始之后的位置,若不存在,则函数返回值为-1
//t非空,0<=pos<=strlen(s)-1
int index_kmp(char *s,char *t,int pos)
{
int len1 = strlen(s);
int len2 = strlen(t);
assert(pos>=0 && pos<=len1-1);
int i = pos;
int j = 0;
int next[255];//定义一个next数组
get_next(t,next);
// get_nextval(t,next);//对t分析,得到next数组
while (i<len1 && j<len2)
{
if (j==-1 || s[i] == t[j])
{
i++;
j++;
}
else
{
j = next[j];//j退回到合适的位置,i值不变
}
}
if (j == len2)
{
return i-len2;
}
else
{
return -1;
}
}
当然,上面的求next数组的方法还可以改进,比如,当主串是”aaaabcde”,子串是”aaaaax”,此时如果按上述算法求出next数组,在字符串匹配过程中会出现较多不必要进行的比较操作,因此要对next函数进行改进。代码如下:
//获取nextval数组,修改的部分加星号表示
void get_nextval(char *t,int *nextval)
{
int i,j;
i = 0;
j = -1;
nextval[0] = -1;
int len = strlen(t);
while (j<len-1)
{
if (j==-1 || t[i]==t[j])//t[i]表示后缀字符,t[j]表示前缀字符,i不回退
{
i++;
j++;
****if (t[i] != t[j])//当前字符与前缀字符不同
{
nextval[i] = j;
}
else//当前字符与前缀字符相同
{
nextval[i] = nextval[j];
}****
}
else//若前缀字符和后缀字符不相同,j会退到合适的位置
{
j = nextval[j];
}
}
}
总结改进过得KMP匹配算法,它是在计算出next值得同时,如果i位字符与它next值指向的j位字符相等,则该i位的nextval就指向j位的nextval值,如果不等,则该i位的nextval值就是它自己i的next的值。