说明:设主串为S,模式串为T,i表示主串元素的下标,j表示模式元素的下标,k是next( )函数找出来模式的下标
为了实现这些算法,都需要对串进行“定长顺序存储表示”
一、子串位置的定位函数(回溯法)
**
1. 算法思想:
主串的第pos个位置,和模式的第1个位置开始比较,如果相等,则继续比较后续字符;若不等,则 i 指针跑到pos的下一个位置上,j 指针继续从第1个位置开始比较……直到找到对应的子串。
代码如下:
int Index(SString S, SString T, int pos) {
int i = pos; int j = 1;
while(i <= S[0] || j <= T[0]) {
if(S[i] == T[j]) { //如果相等,则继续比较后续字符
i++; j++
}else {
i = i-j+2; //不相等,则 i 指针往右移动一格,j 指针继续从第一项开始
j = 1;
}
}
if(j > T(0)) return i-T(0); //返回子串在主串的第一个匹配字符的位置
else return 0; //不匹配,返回0
}
- 算法分析
若设n和m分别是主串和模式的长度,则一般情况下,该算法的时间复杂度为O(n + m)
但是在最坏情况下,它的复杂度为O(n * m)【主串和模式的前面字符都匹配,只有最后一个字符不一样】
于是我们想找一种更好、更稳定的算法……(前方高能!!)
二、KMP算法(非回溯法)
- 对第一种方法的改进
改进:在这种算法模式下,我们不需要对 i 指针进行回溯,我们只用向右移动模式串就行了。
我们需要解决一个问题:模式串要移动多远,即主串中第 i 个字符应与模式的第几个字符比较?
**
2. 算法思想:
假设移动模式串后,模式的第 k ( k < j ) 个字符与主串的第 i 个字符比较,则必定满足:
主串 i 指针之前的 k-1 个字符,和模式从头开始的 k-1 个字符匹配!
如果我们只看模式串的话,则会发现:如果模式的第 j 个字符发生失配,那么必定满足:
模式 j 指针之前的 k-1 个字符,和模式从头开始的 k-1 个字符匹配!
由此,我们产生了方法——当匹配过程中主串第 i 个字符和模式第 j 个字符不匹配时,只需把模式移动至它的第 k 项和主串第 i 个字符对齐就行了!
然后从这儿开始比较 k 和 i 配不配:如果匹配,那么往下继续比较;如果不匹配,那么再一次移动模式串(方法和加粗部分完全相同)
既然模式的每一项都有不匹配的可能,那么我们用一个数组next[ ],来写出每一项不匹配的时候所对应的 k 值(next[ j ] = k【很重要!】)。
那么如何求出每一项的k值呢?有如下规则:
1. 规定:next[1] = 0;
2. 比较第 k 和第 j 个字符是否匹配——
2.1. 如果匹配(T[ k ] == T[ j ]),则next[ j + 1 ] = next[ j ] + 1;
2.2. 如果不匹配(T[ k ] != T[ j ])再看k的next[ k ]
2.2.1. 若next[ k ] == T[ j ],则next[ j + 1 ] = next[ k ] + 1;
2.2.2. 若next[ k ] != T[ j ],那么继续找next[ k ]这一项的 k 值,再比较和第 j 个字符是否匹配,如果不匹配就一直找下去……
最后,如果你幸运找到了匹配项,则next[ j + 1 ]就是你找到的那一项的项数 + 1
如果不幸一直没找到,那么next[ j + 1 ]就等于1。
来看看代码:
void get_next(SString T, int next[]) {
int i = 1; //用于遍历模式串
next[1] = 0; //规定
int j = 0; //next数组里面的数
while(i < T(0)) {
if(j == 0 || T[i] = T[j]) { //一旦第k项和第j项匹配,那么next数组的j+1项的值就是你找到的那一项的项数 + 1
i++; j++;
next[i] = j;
}else {
j = next[j]; //如果不匹配,就找第j项对应的k值
}
}
现在,我们就有了模式串每一项所对应的k值了!
最后,我们来看看现在定位函数的实现:
int Index(SString S, SString T, int pos) {
int i = pos; int j = 1;
while(i <= S[0] || j <= T[0]) {
if(j == 0 || S[i] = T[j]) { //匹配,则比较下一个字符
i++; j++
}else {
j = next[j]; //不匹配,移动模式串到下一个k处,再比较是否匹配
}
}
if(j > T(0)) return i-T(0); //返回子串在主串的第一个匹配字符的位置
else return 0; //不匹配,返回0
}
思路来源于严奶奶的《数据结构》
最后感谢https://blog.csdn.net/qq_37969433/article/details/82947411
里面的动图来自这篇博客