朴素的模式匹配算法
1.主串S第一位开始,S与T前三个字母都匹配成功,但S第四个字母d与T串第四的字母g不相同。
2.主串S第二位开始,主串S的首字符是o,要匹配的首字符是g,匹配失败:
…
3.主串S第五位开始,S与T,6个字母全部匹配,此时成功找到了子串在母串中的位置:
所以它就是对主串的每一个字符作为子串的开头,与要匹配的字符串进行匹配,对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历完成为止。
下面就是实现这个过程的代码:
int Index(String S,STring 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;
j=1;
}
if(j>T[0])
{
return i-T[0];
}
else return 0;
}
}
4.最坏情况:就是每次不成功的匹配都发生在T串的最后一个,例如:
S=00000000000000000000000000000000000000000000000001
T=0000000001
在匹配的时候,每次都得将T串中字符循环到最后一位才发现它们是不相等的,这样T串需要在S串的前40个位置都判断10次,并得出不匹配的结论,这里比较了 [(50-10)+1]x10=410次,并且不论T串最后一位是1还是其他。
通常情况下设S串长度为n,T串长度为m,则最坏情况的时间复杂度是O
((n-m+1)xm)。
5.朴素模式匹配算法的问题:
- 首先假如S串是abcdefgab… T串是abcdex那么它们按照这种方式匹配的结果应该如下:
按照正常的思维这个过程是没有任何问题的,但是先驱者们发现了一个问题:T串自身的结构是第一个a并不与后面的每一个字符相等,并且第一次的比较之后发现S串与T串前面五个字符又是对应相等的,所以T串的首字符a不可能与S串中的第2位到第5位字符相等,所以上面的②③④⑤步全是多余的。
但是最后一步不多余,因为第一次得到的结论是x!=f,然后串中的规律是x!=a,简单的思考一下就可以知道并不能得到f!=a的结论,所以第⑥步比较是必要的。
- 再来看一下第二个例子,S=“abcababca”,T=“abcabx”,还是按照朴素的匹配方式走一遍:
然后再分析T串自身的特点,即:第一个a不与后面的bc相等,但与第四个字符相等,第二个字符b也是与后面的第五个字符相等。那么在第一次的比较之后,使用类似的方法还是可以首先很快排除②③步,然后再从逻辑上考虑一下,既然前面已知1,2位字符与4,5位字符相等,然后又知道S串与T串前五个是对应相等的,所以T串中的前1,2个字符一定也是与S串中的4,5个字符是相等的。
所以④⑤步也是多余的。并且第⑥步还是可以省略前两次比较。
然后省略掉这两个例子中多余的步骤,对比对比:
(1)
(2)
可以发现一个关键是 i 是不需要回溯的,但是我们需要通过某一种方法来了解T串自身的结构,好让 j 可以在需要回溯时,能够回到恰当的位置上。
KMP模式匹配算法
核心:定义一个next数组,长度与T串长度相等,每一个元素与T串中的元素一一对应,代表的是如果S串在于T串相比较的时候,字符不相等时,T串的 j 应该回溯的位置。
下面是该数组的定义:
下面通过几个例子来体验一下这个过程:
- 例1:子串T=“abcdex”,按照上面的函数定义,推导出next数组的过程如下:
(1)当j=1时,next[1]=0;
(2)当j=2时,j由1到j-1 就只有字符“a”,属于第三种情况,'p1…pj-1’中没有重复,所以next[2]=1;
(3)当j=3时,j由1到j-1 串是“ab",a与b不是相同的字符,还是属于第三种没有重复的情况,所以next[3]=1;
以后同理,最终此T串的next数组为:011111
- 例2:T=“abcabx”
(1)当j=1时,next[1]=0;
(2)当j=2时,j由1到 j-1 就只有字符“a”,属于第三种情况,‘p1…pj-1’中没有重复,所以next[2]=1;
(3)当j=3时,j由1到 j-1 串是“ab",a与b不是相同的字符,还是属于第三种没有重复的情况,所以next[3]=1;
(4)当j=4时,j由1到j-1=3,串是“abc”,还是属于第三种没有重复的情况,所以next[4]=1;
(5)当j=5时,j 由1到 j-1=4,串是“abca”,此时前缀字符a与后缀字符a相等,即’p1’=‘p4’,所以k-1=1;j-k+1=5-k+1=4都可以得出此时k值为2;
(6)当j=6时,j由1到 j-1=5,串是“abcab”,此时有“p1p2”=“p4p5”,所以k-1=2;或j-k+1=6-k+1=4都可以算出k的值为3
因此此时的next数组就变为011123
同时可以总结出经验:如果前后缀一个字符相等,k值是2;如果是两个字符相等,k值是3;前后缀是n个字符相等,则k值是n+1。
- 例3:T=“ababaaaba”
(1)当j=1时,next[1]=0;
(2)当j=2时,j由1到 j-1 就只有字符“a”,属于第三种情况,'p1…pj-1’中没有重复,所以next[2]=1;
(3)当j=3时,j由1到 j-1 串是“ab",a与b不是相同的字符,还是属于第三种没有重复的情况,所以next[3]=1;
(4)当j=4时,j由1到j-1串是“aba”,“p1”=“p3”,根据之前的规律可以知道这里的k值应为2;
(5)当j=5时,j由1到j-1串是“abab”,“p1p2”=“p3p4”,所以k值为3;
(6)当j=6时,j由1到j-1串是“ababa”,所以这里"p1p2p3"=“p3p4p5”,所以k值为4;需要注意的是这里的前缀和后缀中有一个相同的字符,第3个字符a;但是在之前的定义中并没有说前缀和后缀是不能重复的,所以不要仅从概念上单纯的认为前缀和后缀一定是一前一后毫无交集的。
(7)当j=7时,j由1到j-1串是“ababaa”,可以判断出来这里只有一头一尾"a"=“a”,所以k=2;
(8)当j=8时,j由1到j-1串是“ababaaa”,还是只有"a"=“a”,所以k=2;
(9)当j=9时,j由1到j-1串是“ababaaab”,此时前缀“ab”与后缀“ab”相等,所以next[9]=3;
因此得到的next数组就是:011234223
- 例4:T=“aaaaaaaab”
(1)当 j=1时,next[1]=0;
(2)当 j=2时,j由1到j-1 就只有字符“a”,属于第三种情况,'p1…pj-1’中没有重复,所以next[2]=1;
(3)当 j=3时,j由1到j-1串是“aa”,前缀字符“a“与后缀字符”a“相等,next[3]=2;
(4)当 j=4时,j由1到j-1的串是“aaa”,前缀字符是"aa"后缀字符是“aa”,两者相等,next[4]=3;
(5)…同理可得 j=5到 j=9时,每一个j值对应的next[j]对应的值,分别为4~8;
代码实现next数组的推导:
void get_next(String T,int *next)
{
int i,j;
i=1;
j=0;
next[1]=0;
while(i<T[0])
{
if(j==0||T[i]==T[j])
{
i=i+1;
next[i]=(j=j+1);
}
else
{
j=next[j];
}
}
}
代码的详解主要听讲述~~
注:这里是后面补充的这个地方的详解:https://blog.csdn.net/qq_43771635/article/details/97893426
运用next数组进行模式匹配
int Index_KMP(String S,String T,int pos)
{
int i=pos;
int j=1;
int next[255];
get_next(T,next);
while(i<=S[0] && j<=T[0])
{
if(j==0||S[i]==T[j])
{
i++;
j++;
}
else
{
j=next[j];
}
}
if(j>T[0])
{
return i-T[0];
}
else
{
return 0;
}
}
详解听讲述~~
KMP模式匹配算法的改进
为什么需要改进,以及怎样改进听讲述~~
这里直接上改进之后的代码:
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;
}
else
{
nextval[i]=nextval[j];
}
}
else
{
j=nextval[j];
}
}
}
详解听讲述。