//朴素的模式匹配算法
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回0.
//T非空,1 <= pos <= StrLength(S)。
int Index(String S,String T,int pos)
{
int i = pos;//i用于主串S中当前位置下标,若pos不为1,则从pos位置开始匹配
int j = 1;//j用于子串T中当前位置下标值
while(i <= S[0] && j <= T[0])//若i小于S长度且j小于T的长度时循环
{
if(S[i] == T[j])//两字母相等时则继续
{
++i;
++j;
}
else//指针后退重新开始匹配
{
i = i - j + 2;//i退回到上次匹配首位的下一位,至于为什么是这个,可以举个例子有助于理解
j = 1;
}
}
if(j > T[0])
return i - T[0];
else
return 0;
}
分析一下朴素模式匹配算法的时间复杂度:
(n为主串S的长度,m为要匹配的子串T的长度)
1)最好情况下,就是一开始就匹配成功,时间复杂度为O(1),稍差一些就是开始每次都是首字母不匹配,比如S = "abcdefgoogle",T = "google",此时的时间复杂度为O(n + m);
2)根据等概率原则,平均是(n + m) / 2次查找,时间复杂度为O(n + m);
3)最坏情况下的时间复杂度,观察图5-6-6和图5-6-7,可知为O((n - m + 1) * m);
朴素模式匹配算法比较低效,后来发明了KMP模式匹配算法(克努特--莫里斯--普拉特算法)。
在KMP模式匹配算法中,不用回溯i的值,所以将算法的时间复杂度从O((n - m + 1) * m)砍到了O(n + m)。
在引入KMP算法时,先引入一个例子:S = "abcdefgab",T = "abcdex";图5-7-1是用朴素模式匹配算法的过程,因为T中a与后面的字母都不一样,在第一次比较中,bcde与主串S中的bcde进行匹配过了,类似传递性,所以a与主串S的那几个位置的匹配可以不做,从而减少时间,图中的2,3,4,5都可以省去。
KMP模式匹配算法避免了没有必要的回溯的发生。
i值不进行回溯了,那就要考虑j值的变化,T串各个位置的j值的变化定义为一个数组next,那么next的长度就是T串的长度。于是我们得到下面函数的定义:
(CSDN的编译器好像打不出来上面的样子,只好截图)
//通过计算返回子串T的next数组。
void get_next(String T,int *next)
{
int i,j;
i = 1;
j = 0;
next[1] = 0;
while(i < T[0])//此处T[0]表示串T的长度
{
if(j == 0||T[i] == T[j])//T[i]表示后缀的单个字符;T[j]表示前缀的单个字符
{
++i;
++j;
next[i] = j;
}
else
j = next[j];//若字符不同则j值回溯
}
}
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0.
//T非空,1 <= pos <= StrLenth(S).
int Index_KMP(String S,String T,int pos)
{
int i = pos;
int j = 1;
int next[255];
get_next(T,next);
while(i <= S[0] && j <= T[0])
{
if(j == 0||S[i] == T[j])
{
++i;
++j;
}
else
{
j = next[j];//j退回合适的位置,i值不变
}
}
if(j > T[0])
return i - T[0];
else
return 0;
}
KMP还是有缺陷的,比如当S = "aaaabcde",T = "aaaaax"
KMP模式匹配算法的改进
具体过程参考《大话数据结构》P168
//求模式串T的next函数修正值并存入数组nextval
void get_nextval(String T,int *nextval)
{
int i,j;
i = 1;
j = 0
nextval[1] = 0;
while(i < T[0])
{
if(j == 0 || T[i] == T[j])
{
++i;
++j;
if(T[i] != T[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];//若字符不同,则j值回溯
}
}