子串的定位操作通常称作串的模式匹配
朴素的模式匹配算法
- 以主串的每一个字符作为子串的开头,与要匹配的字符串进行匹配。
- 对主串做大循环,每个字符开头作为子串的长度的小循环,直到匹配成功或全部遍历完成为止。
/*返回子串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退回到上次匹配首位的下一位*/
//串是从 1 开始计数的
//i-j+1 恰好回到这次匹配的第一个字符
//所以 (i-j+1)+1 指到下一个字符
//从而开始一轮新的匹配
j = 1;
}
}
if(j > T[0]){
return i-T[0];
}
else
{
return 0;
}
}
时间复杂度:O(n*m)
时间消耗过大,一般不建议采用。
KMP模式匹配算法
有三位前辈,D.E.Knuth、J.H.Morris 和V.R.Pratt(其中Knuth 和Pratt 共同研究,Morris独立研究)发表一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称之为克努特一莫里斯一普拉特算法,简称KMP算法。
KMP算法当中只需要子串的位置发生前后变化,主串向后移动即可。
- 待匹配串某几位互不相等,而它们又在主串中有对应的子串,当后一位匹配失败时,待匹配串中的这几位不必再与主串中明显与自己不相等的字符去比较;
- 待匹配串中存在回文相等,且它们在主串中有对应的子串,当后一位匹配失败时,待匹配串中回文串的前几位不必再与主串中明显与自己相等的字符去比较。
- 大大降低的时间的消耗,其时间复杂度O(n+m);
寻找子串的回溯位置:
/*通过计算返回子串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<=StrLength(S)
int Index_KMP(String S, String T, int pos){
// i 用于主串S当前位置下标值,若pos不为1,则从pos位置开始匹配
int i = pos;
int j = 1;//j 用于子串T中当前位置下标值
int next[255];// 定义next数组
get_next(T,next);// 对串T作分析,得到next数组
// 若i小于S的长度且j小于T的长度时,循环继续
while(i <= S[0] && j <= T[0]){
// 两字母相等则继续,相对于朴素算法增加了j=0 判断
if(j == 0 || S[i] == T[j]){
++i;
++j;
}
// 指针后退重新开始匹配
else{
// j退回合适的位置,i值不变
j = next[j];
}
}
if(j>T[0]){
return i-T[0];
}
else
{
return 0;
}
}
附:
关于找 next 数组的改进:
// 求模式串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;// 则当前的j为nextval在i位置的值
}
else
{
nextval[i] = nextval[j];
//如果与前缀字符相同,则将前缀字符的nextval值赋值给nextval在i位置的值
}
}
else
{
//若字符不相同,则j值回溯
j=nextval[j];
}
}
}
如果实在理解不了,那就背会!