BF算法分析
假设主串为“abcdefgab”,模式串为“abcdex”(模式串的每一位都不一样),那么按照BF算法,比较到主串的“f”时,匹配失败,模式串回退到开始位置“a”,主串回退到字符“b”,匹配失败,主串下标到“c”,以此类推...
其实这里面,因为模式串的每一位都不一样,所以第一次匹配失败时,已经匹配成功的部分“a”到“e”,首字符"a"已经匹配成功,所以主串的"b"到“e”跟模式串的首字符“a”肯定是无法匹配的,因此我们可以省略掉这一部分的循环。
如下图:
KMP模式匹配算法原理探索
后面我们用i来表示对主串的遍历(i从1开始),j来表示对模式串的遍历(j从1开始)。
KMP算法的精髓就是:利用已知信息(模式串),根据其重复值来修改回朔值(主串并不会进行回朔),得到一个最恰当的位置来跟主串进行下一轮的比较:
这种情况的前提是模式串每一位都并不一样,我们再来看一个案例(模式串有重复):
假设主串为“abcababca”模式串为“abcabx”,那么应该怎样来比较呢?
按照上面的经验,第一轮比较 i=6&&j=6时,匹配失败,因为前三位abc互不相同,所以 i=2&&j=1 i=3&&j=1是无意义的比较,如下图:
那么,下面我们来思考一下,第四步是不是有必要的?
根据模式串信息,前两位“ab”与第四五位"ab"是相同的,因为第一轮比较已经到了第六位,所以前两位肯定跟主串里的第四五位也是相等的,所以这一步也可以优化(直接从 i=6 j=3比较):
KMP模式匹配算法——next数组推导
我们把模式串各个位置j值变化定义为一个next数组,那么next的长度就是模式串的长度,next下标也是从1开始,默认第一位是0,即next[1] = 0。KMP中i是不往回走的,i要么是不动,要么是跟j一起往后移。
模式串为“abcedx”时next的推导:
- j=1时匹配冲突,next[1] = 0
- j=2时匹配冲突,j由1到j-1范围内只有字符“a”,next[2]=1
- j=3时匹配冲突,j由1到j-1范围内只有字符“ab”,不存在相等情况,next[3]=1
- j=4时匹配冲突,j由1到j-1范围内只有字符“abc”,不存在相等情况,next[4]=1
- j=5时匹配冲突,j由1到j-1范围内只有字符“abcd”,不存在相等情况,next[5]=1
- j=6时匹配冲突,j由1到j-1范围内只有字符“abcde”,不存在相等情况,next[6]=1
模式串为“abcabx”时next的推导:
- j=1时匹配冲突,next[1] = 0
- j=2时匹配冲突,j由1到j-1范围内只有字符“a”,next[2]=1
- j=3时匹配冲突,j由1到j-1范围内只有字符“ab”,不存在相等情况,next[3]=1
- j=4时匹配冲突,j由1到j-1范围内只有字符“abc”,不存在相等情况,next[4]=1
- j=5时匹配冲突,j由1到j-1范围内有字符“abca”,出现了重复,我们来找到重复的前置字符和后置字符,得到前置字符“a”与后置字符“a”,此时j回朔到a就没有必要了,因为a肯定是有的,直接回朔到b,即next[5]=2
- j=6时匹配冲突,j由1到j-1范围内有字符“abcab”,出现了重复,得到前置字符“ab”与后置字符“ab”,此时j回朔到a和b都没有必要了,直接回朔到c得到next[6]=3
tips :如果前后置一个字符相等,那么next对应位置的值就是2,如果两个字符相等,那么next对应位置的值就是3,如果n个字符相等,那么next对应位置的值就是n+1
按照上面得到的方法,来计算模式串“ababaaaba”的next数组:
- j<4时,跟上面一样,next[1]=0,next[2]=1,next[3]=1;
- j=4,前后置相同的字符分别为“a”和“a”,next[4]=2;
- j=5,前后置相同的字符分别为“ab”和“ab”,next[5]=3;
- j=6,注意,此时的前后置相同的字符分别为“aba”和“aba”(前后不完全相等时最长的相等字符),中间的a即是前置也是后置,next[6]=4;
- j=7,前后置相同的字符分别为“a”和“a”,next[7]=2;
- j=8,前后置相同的字符分别为“a”和“a”,next[8]=2;
- j=9,前后置相同的字符分别为“ab”和“ab”,next[9]=3;
再来试试“aaaaaaaab”:
- j=1,next[1] = 0
- j=2,只有一个“a”,next[2]=1;
- j=3,前后置相同的字符分别为“a”和“a”,next[3]=2;
- j=4,前后置相同的字符分别为“aa”和“aa”,next[4]=3;
- j=5,前后置相同的字符分别为“aaa”和“aaa”,next[5]=4;
- j=6,前后置相同的字符分别为“aaaa”和“aaaa”,next[6]=5;
- j=7,前后置相同的字符分别为“aaaaa”和“aaaaa”,next[7]=6;
- j=8,前后置相同的字符分别为“aaaaaa”和“aaaaaa”,next[8]=7;
- j=9,前后置相同的字符分别为“aaaaaaa”和“aaaaaaa”,next[9]=8;
用代码来求得next数组
假设主串为“abcababca”,模式串为“abcdex”,此时next数组为{0,1,1,1,1,1}。当遍历到匹配失败时,将j回朔到next[j]的位置
我们来模拟一下执行过程:
-
第一轮比较,i=4,j=4时匹配失败,将j回朔到next[4]的位置,即j=next[4]=1;
-
继续遍历,i=6,j=3时匹配失败,回朔到j=next[3]=1的位置继续比较
-
依次类推......
那用代码如何计算next数组呢?
假设,主串S='abcababca',模式串T='abcdex'
- 默认next[1] = 0;
2, i=0,j=1开始遍历,当j<T.length时,从1-length遍历字符串
3,如果i=0表示[0,j]这个范围内没有找到相同的字符串,所以i要回朔到1的位置,表示next[j]=1
4,如果T[i]=T[j],表示找到了与其相同字符的位置,所以next[j]=i;
5,当以上两个条件都不满足,则将i回朔到前面记录的next[i]的位置
模拟下计算步骤 (开始i=0,j=1):
1,i=0,j=1,T[i]!=T[j],表示0-1范围内没有重复字符,则i++,j++,next[2]=1;此时i=1,j=2
2, T[i]!=T[j] && i!=0,此时需要将i回朔到next[i]的位置,即i=0,此时i=0,j=2
3, T[i]!=T[j] && i=0,表示i-j范围内没有重复字符,i++,j++,next[3]=1,此时i=1,j=3
4, T[i]!=T[j] && i!=0,此时需要将i回朔到next[i]的位置,即i=0,此时i=0,j=3
5, T[i]!=T[j] && i=0,表示i-j范围内没有重复字符,i++,j++,next[4]=1,此时i=1,j=4
6, T[i]!=T[j] && i!=0,此时需要将i回朔到next[i]的位置,即i=0,此时i=0,j=4
7, T[i]!=T[j] && i=0,表示i-j范围内没有重复字符,i++,j++,next[5]=1,此时i=1,j=5
8, T[i]!=T[j] && i!=0,此时需要将i回朔到next[i]的位置,即i=0,此时i=0,j=5
9, T[i]!=T[j] && i=0,表示i-j范围内没有重复字符,i++,j++,next[6]=1,此时i=1,j=6
10,跳出循环
最终求得next数组为 [0,1,1,1,1,1]
求解next数组时有4种情况:
1,默认next[1]=0;
2,当 i=0 时,表示从头开始比较,则i++,j++,next[j]=1;
3,当T[i]==T[j],表示找到了相同的字符,准备开始下一次循环 i++,j++,同时next[j]=i;(因为next数组是从1开始的,而遍历是从0开始的,所以先做++,再赋值)
4,当T[i]!=T[j],表示字符不相同,此时要扩大范围,将i退回到合理的位置,i=next[i]
下面用代码来计算:
//通过计算返回子串T的next数组;
//注意字符串T[0]中是存储的字符串长度; 真正的字符内容从T[1]开始;
void get_next(String T,int *next){
int i,j;
j = 1;
i = 0;
next[1] = 0;
//abcdex
//遍历T模式串, 此时T[0]为模式串T的长度;
//printf("length = %d\n",T[0]);
while (j < T[0]) {
//printf("i = %d j = %d\n",i,j);
if(i ==0 || T[i] == T[j]){
//T[i] 表示后缀的单个字符;
//T[j] 表示前缀的单个字符;
++i;
++j;
next[j] = i;
//printf("next[%d]=%d\n",j,next[j]);
}else
{
//如果字符不相同,则i值回朔;
i = next[i];
}
}
}
next数组的使用
KMP思路:
1,遍历主串S,i是用来标记主串的索引,遍历模式串T,j是用来标记模式串的索引;
2,当 i>S.length || j>T.length; i>S.length,j<T.length,表示主串遍历完也没找到匹配;只有一种可能,就是当j>T.length,表示找到了匹配字符串
3,当j = 0,表示需要将模式串从 1这个位置与主串i+1这个位置比较
4,当S[i] == T[j],表示模式串j和主串i的位置字符相等,i++,j++;
5,当j>0且S[i]!=T[j],表示此事需要移动模式串的j,那么我们让j=next[j],以达到减少比较次数的目的
//KMP 匹配算法
//返回子串T在主串S中第pos个字符之后的位置, 如不存在则返回0;
int Index_KMP(String S,String T,int pos){
//i 是主串当前位置的下标准,j是模式串当前位置的下标准
int i = pos;
int j = 1;
//定义一个空的next数组;
int next[MAXSIZE];
//对T串进行分析,得到next数组;
get_next(T, next);
count = 0;
//注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
//若i小于S长度并且j小于T的长度是循环继续;
while (i <= S[0] && j <= T[0]) {
//如果两字母相等则继续,并且j++,i++
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 -1;
}
}