在主串中找到和模式串相同的子串,并返回其所在位置。下面考虑的主串和模式串的的初始位置都是从数组下标为1的位置。
朴素模式匹配
将主串中和模式串长度相同的子串和模式串一次比较,只要有一个字符不匹配,就舍弃当前子串比较下一个子串
主串长度为n,模式串长度为m,则最坏时间复杂度为 O ( n m ) O(nm) O(nm)(要匹配(n-m+1)m次)
朴素模式四配算法的缺点
: 当某些子串与模式串能部分四配时,主串的扫描指针 i 经 常回溯
,导致时间开销增加。最坏时间复杂度O(nm)
代码实现:
- 法1
int Index(SString S, SString T){
int k=1;
int i=k;
int j=1;
while(i<S.length && j<=T.length){
if(S.ch[i]==T.ch[j]){
i++;
j++; //继续比较后续字符
} else{
k++; //检查下一个子串
i=k;
j=1;
}
//匹配成功时j=T.length+1
if(j>T.length)
return k;
else
return 0;
}
- 法2
int Index(SString S, SString T){
int i=1;
int j=1;
while(i<S.length && j<=T.length){
if(S.ch[i]==T.ch[j]){
i++;
j++; //继续比较后续字符
} else{
i=i-j+2;
j=1;
}
//匹配成功时j=T.length+1
if(j>T.length)
return i-T.length;
else
return 0;
}
KMP算法
基本思想
KMP算法是对朴素匹配算法的改进。
- 每次回溯模式串的指针,主串的指针不再回溯。
- 通过增加一个回溯数组(next数组),代表模式串当前位置不匹配的时候回溯的位置
- 注意一个特殊的情况就是,当第一个字符不匹配的时候,主串的指针和模式串的指针都应后移再开始下一次匹配,因此此时对应的模式串的指针回溯到0,然后让主串和模式串的指针都移动到下一个位置再开始匹配
特点:当子串和模式串不匹配时,主串指针 i 不回溯,模式串指针 j=next[j] 算法平均时间复杂度: O(n+m)
next数组确定
串的前缀
: 包含第一个字符,且不包含最后一个字符的子串
串的后缀
: 包含最后一个字符,且不包含第一个字符的子串
对于模式串,当第j个字符匹配失败,由前j-1 个字符组成的串记为S,则next[j]= (S的最长相等前后缀长度+1)
。 特别地,next[1]=0
代码实现
- 求模式串的next数组
void get_next(SString T, int next[]){
int i=1;
int j=0;
while(i<T.length){
if(j==0 || T.ch[i]==T.ch[j]){
i++;
j++;
//若pi=pj,则next[j+1]=next[j]+1
next[i]=j;
}
else
j=next[j];
}
}
- KMP算法
int Index_KMP(SString S, SString T){
int i=1;
int j=1;
int next[T.length+1];
get_next(T, next);
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i]==T.ch[j]){
i++;
j++;
}
else
j=next[j];
}
if(j>T.length)
return i-T.length;
else
return 0;
}
KMP算法改进
KMP算法的缺点:存在无意义的对比。比如aaaab和aaacaaaab…匹配,匹配到第4个位置的时候不匹配,使用next数组(next数组的值为[0, 1, 2, 3, 4])则前3个a依次回退,但是通过模式串我们已知模式串前四个字符相等,则前三个字符和c必然也不匹配,就没必要进行比较,应该直接回溯到0。
改进方法:使用nextval数组(nextval是对next数组的优化)而不使用next数组。
//先计算出next数组
//令nextval[1]=0
for(int j=2;j<=T.length;j++){
if(T.ch[next[j]]==T.ch[j])
nextval[j]=nextval[next[j]];
else
nextval[j]=next[j];
}
内容来源:王道考研-数据结构