串的朴素模式匹配算法
模式匹配
主串:S=‘abcdefg’
子串:‘abc’ , ‘efg’ //子串一定是主串中存在的
模式串:‘cde’ ,‘ifg’ //模式串是想要在主串中找到的,未必存在
串的模式匹配: 在主串中找到与模式串相同的子串,并返回其所在的位置
int Index(SString S,SString T){
int k=1; //使用变量k来表示当前取出子串的起始位置
int i=k,j=1; //使用i和j来分别指向两个串的对应位置
while(i<=S.length&&j<=T.length){
if(S.ch[i]==T.ch[j]){
++i;
++j; //继续比较后继字符
} else{
k++; //检查下一个子串
i=k;
j=1;
}
}
if(j>T.length)
return k;
else
return 0;
}
算法性能分析:(若模式串长度为m,主串长度为n)
1.较好的情况:每个子串第一个字符就不匹配
2.匹配成功的最好情况:O(m)
3.匹配失败的最好情况:O(n-m+1)=O(n-m)=O(n)
4.最坏情况:n-m+1 匹配m次 ,O(nm)
朴素模式匹配算法(简单模式匹配算法):
1.将主串中和模式串长度相同的串先搞出来,挨个与模式串进行对比
2.当模式串与子串某个字符不匹配时,就立即放弃当前子串,转而检索下一个子串
KMP算法
朴素模式匹配算法的缺点: 用于在主串中记录当前匹配字符的指针i需要不断回溯,浪费时间。
KMP算法特点
1.用于记录主串位置的i指针不回溯
2.通过对模式串的判断,当j=k时匹配失败,说明1~k-1都匹配成功
eg:模式串为 ‘google’
- 若当前两个字符匹配i++,j++
- 若j=1时发生不匹配,则应让i++,而j依然是1
- 若j=2时发生不匹配,则应让j回到1
- 若j=3时发生不匹配,则应让j回到1
- 若j=4时发生不匹配,则应让j回到1
- 若j=5时发生不匹配,则应让j回到2
- 若j=6时发生不匹配,则应让j回到1
由此可以建立一个数组next ,当j=k且字符不匹配时,令j=next[k];
int Index_KMP(SString S,SString T,int next[]){
int i=1, j=1;
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;
}
next数组的快速求法
eg:‘abcadb’
- 若j=6时发生不匹配,则令j=next[6]=3;
eg:‘abababcdef’
- 若j=7时发生不匹配,则令j=next[7]=5;
- 对于上面的情况,不可以令j=next[7]=3,会错过很多字符
eg:‘aaaabcd’
- 若j=5时发生不匹配,则令j=next[5]=4;
eg:‘abcdefg’
- 若j=5时发生不匹配,则令j=next[5]=1;
eg:‘abcabd’
- 若j=1时发生不匹配,则令j=next[1]=0;之后再让i++,j++
总结
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
- 当第 j 个字符匹配失败时,由1~j-1个字符组成的串记为S,则:next[j]=S的最长相等前后缀长度+1
- 特别的next[1]=0
练习1:
模式串:‘ababaa’
序号j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
模式串 | a | b | a | b | a | a |
next[j} | 0 | 1 | 1 | 2 | 3 | 4 |
练习2:
模式串:‘aaaab’
序号j | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
模式串 | a | a | a | a | b |
next[j] | 0 | 1 | 2 | 3 | 4 |
KMP算法的平均时间复杂度:O(m+n)
//求模式串T的next数组 O(m)
void get_next(SString T,int next[]){
int i=1,j=0;
next[1]=0;
while(i<T.length){
if(j==0||T.ch[i]==T.ch[j]){
++i;
++j;
next[i]=j;
}
else
j=next[j]; //循环继续
}
}
int Index_KMP(SString S,SString T){
int i=1, j=1;
int next[T.length+1];
get_next(T,next); //求模式串的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算法的优化:nextval[j]数组
作用:避免了j指针回溯到前一个相同的字母,进行一些重复的不必要的比较
nextval[j]数组的求法
- 令nextval[0]=0
- 往后看下一个字符的next数组所指向的字符,与其是否相同,相同的话则令其nextval数组值等于相同字符的nextval数组值
- 如果不相同nextval数组值,则等于next数组值