KMP算法学习记录
Foreword:
初学KMP匹配算法,不得其门,总感觉自己想,想不出来,看书上文字解释晦涩难懂。不能准确的捕捉算法设计时候的灵光和思路 。于是自己试着完成了一遍,现将过程记录下来,以供复习。
Content:
(一) 预设:模式串和母串的声明
1. 子串的定位操作通常称为穿的模式匹配,求的是子串(常称之为‘模式串’)在主串中的位置;本次,我们将创建两个字符串T(母串)和P(模式串),然后进行匹配。
2. 串是一种连续存储的数组,串的地址就是第一个字符的地址,串的数据类型都要是字符。
3. 语言限定:这里使用的语言为C语言。
4. 笔者习惯:行文中使用‘?=’是想表示“是否等于”的有意思。
5. 字符串的前缀、后缀和部分匹配值:
a. 前缀:指代除了最后一个字符以外,字符串的头部子串集合;
b. 后缀:指代除了第一个字符以外,字符串的尾部子串集合;
c. 部分匹配值:等长的前缀和等长的后缀中最长的前后缀长度;
illustrate:‘ababa’
'a'前后缀都是空集,部分匹配值为0;
'ab'前缀为‘a’,后缀为'b',部分匹配值为0;
'aba'前缀为{'a','ab'}, 后缀为{'a','ba'},'a'='a'部分匹配值为1;
'abab'前缀为{'a','ab','aba'}, 后缀为{'a','ab',bab},'a'='a','ab'='ab'且'ab'长度为2,'a'长度为1,2>1, 所以部分匹配值为2;
'ababa'前缀为{'a','ab','aba'}, 后缀为{'a','ba','aba'},'a'='a', 'aba'='aba',且3>1, 所以部分匹配值为3;
(二) 匹配前准备:创建字符串
? 宏:
1 #include
2 #include
3 #include <string.h>
4 #define MaxSize 100
? 创建一个结构体命名为String,包含一个静态存储声明的数组和数组实际长度:
1 typedef struct String{
2 char ch[MaxSize];
3 int length;
4 }SString,*String;
? 主函数,声明母串T和模式串P,使用
1 int main(){ 2 3 SString T; 4 5 SString P; 6 7 int situated; 8 9 printf("请输入字符序列T:\n"); 10 11 scanf("%s",&T.ch); 12 13 printf("字符串T: %s \n",T.ch); 14 15 T.length=strlen(T.ch); 16 17 printf("T.length: %d \n",T.length); 18 19 printf("请输入字符序列P:\n"); 20 21 scanf("%s",&P.ch); 22 23 printf("字符串T: %s \n",P.ch); 24 25 P.length=strlen(P.ch); 26 27 printf("P.length: %d \n",P.length); 28 29 situated = PMIndex(T,P); 30 31 printf("匹配完成,相对位置为:%d \n",situated); 32 33 return 0;}
(三)暴力匹配方法(violent match method)
一般来说,我们会创建两个字符串,暨母串和模式串,他们分别存储在数组T.ch[]和P.ch[],长度分别是T.length和P.length。
我们声明int型变量i是母串的字符序号,暨母串的第几个字符,它的区间是[1,T.length]。
我们声明int型变量j是母串的字符序号,暨子串的第几个字符,它的区间是[1,P.length]。
母串存储的数组的角标、数组元素和i、j序号值对应为:
母串 |
a |
b |
c |
…… |
k |
角标 |
0 |
1 |
2 |
…… |
T.Length-1 |
i |
1 |
2 |
3 |
…… |
T.length |
模式串 |
a |
b |
c |
…… |
m |
角标 |
0 |
1 |
2 |
…… |
P.Length-1 |
i |
1 |
2 |
3 |
…… |
P.length |
我们完成匹配的思路很直接,很粗鲁,直接用子串第一个和母串第一个值匹配,相同就顺次匹配下一对直到模式串匹配完成结束。但如果没有匹配完就失配了,重新用模式串的所有字符和母串第二个元素开始的字符开始一一对应的匹配,直到模式串匹配完成或者再发生失配,失配就继续重新从i=3开始循环匹配,直到母串匹配完返回‘没匹配到’或者匹配到模式串返回匹配的地址。
1 int Index(SString T,SString P){ 2 3 //暴力匹配方法 4 5 int i=1; 6 7 int j=1; 8 9 while(i<=T.length && j<=P.length){ 10 11 if(T.ch[i-1]==P.ch[j-1]){ 12 13 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 14 15 ++i; 16 17 ++j; 18 19 //继续比较后面的字符 20 21 } 22 23 else{ 24 25 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 26 27 i = i-j+2; 28 29 j=1; 30 31 //指针后退,模式串T右移,重新开始匹配 32 33 } 34 35 if(j==P.length+1){return i-P.length;} 36 37 } 38 39 return 0; 40 41 }
母串 |
a |
a |
c |
d |
r |
e |
模式串一次循环匹配 |
a |
c |
s |
|
|
|
模式串二次循环匹配 |
|
a |
c |
s |
|
|
模式串二次循环匹配 |
|
|
a |
c |
s |
|
(四)模拟人在正常情况的匹配方法:
母串 |
a |
b |
a |
a |
b |
a |
g |
h |
i |
模式串第一次匹配 |
a |
b |
a |
g |
|
|
|
|
|
模式串第二次匹配 |
|
|
a |
b |
|
|
|
|
|
模式串第三次匹配 |
|
|
|
a |
b |
a |
g |
|
|
如上表所示,我们在对上表情况进行暴力匹配的时候,第一次失配后,i总要退回到 i+2-j 的位置重新从j=1开始匹配。这里已经匹配成功的‘aba’字符在用暴力算法从新匹配的时候就相当于,模式串自己和自己匹配,一旦模式串比较长这种做法无异于十分低效且费力。这里用到了预设里提到的前后缀,如果在一次匹配进行过程中发生失配时,已匹配的字符串拥有部分匹配值num>0, 那么我们会直接把模式串右移(j-num-1)位,暨i不变,j=num+1;
更确切地说,右移位数 == 已匹配字符数 - 已匹配字符串对应的部分匹配值
上面的内容书上和网上教程讲的很清楚,不详细说明了。
这里对如何求前后缀部分匹配值的计算进行展示:
1 int SectionMarch(SString P,int temp1){//模式串和已匹配数,返回最长的前缀等于后缀的缀的长度 2 for(int Q=1;Q//从第大到小进行部分匹配,每次匹配长度为temp1-Q 3 for(int i=0;i<=temp1-Q-1;i++){ 4 if(P.ch[temp1-Q-i-1]==P.ch[temp1-i-1]){} 5 else{ break;} 6 7 if(i==temp1-Q-1){return temp1-Q;} 8 } 9 } 10 return 0; 11 }
上图对不同个数的字符串的部分匹配值PM进行了说明,可见面对temp1个字符的字符串 'ary[0],ary[1],ary[2]……ary[Q-1]':需要分别匹配
PM = temp1-1:ary[0] == ary[1]&ary[1]==ary[2]&…ary[temp1-1-i-1]==ary[temp1-1-i]…&ary[temp1-1-1-1]==ary[temp1-1-1]&ary[temp1-1-0-1]==ary[temp1-1-0];
PM = temp1-2: ary[0] == ary[2]&ary[1]==ary[3]&…ary[temp1-1-i-2]==ary[tempp1-1-i]…&ary[temp1-1-1-2]==ary[temp1-1-1]&ary[temp1-1-0-2]==ary[temp1-1-0];
……
……
……
PM = temp1-Q: ary[temp1-Q-1-(temp1-Q-1)] == ary[temp1-1-(temp1-Q-1)]&ary[temp1-Q-1-(temp1-Q)]==ary[temp1-1-(temp1-Q)]&…ary[temp1-1-i-Q]==ary[tempp1-1-i]…&ary[temp1-1-1-Q]==ary[temp1-1-1]&ary[temp1-1-0-Q]==ary[temp1-1-0];
……
……
……
PM = 1:ary[0] == ary[temp1-1];
这样自上而下从右到左顺次匹配,直到首个把一行匹配完都正确的出现就能够直接确定部分匹配值;
这样我们就可以进行模拟手工匹配了!!!
1 int PMIndex(SString T,SString P){//参数是已匹配的部分字符串 2 //模拟人工匹配方法 3 int i=1; 4 int j=1; 5 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 6 int temp1=0; 7 int Max_march; 8 9 while(i<=T.length && j<=P.length){ 10 if(T.ch[i-1]==P.ch[j-1]){ 11 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 12 ++i; 13 ++j; 14 temp1++;//记录匹配次数 15 //继续比较后面的字符 16 } 17 else{ 18 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 19 Max_march=SectionMarch(P,temp1);//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 20 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 21 i++; 22 } 23 else if(Max_march==0){ 24 j=1; 25 } 26 else{ 27 j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 28 } 29 temp1 = 0; 30 //指针后退,模式串T右移,重新开始匹配 31 } 32 if(j==P.length+1){return i-P.length;} 33 } 34 return 0; 35 }
运行结果:
当然,我们实际上每一次在匹配成功后失配的时候都进了一次针对匹配字符串的前后缀部分匹配值的计算。而且,这种计算是二次嵌套,每次计算的时间复杂度都高达O(n^2)。所以在匹配的串较长的时候,最好提前把子串每种运算的PM值计算出来做成一一对应的表单,存储在数组中这样每次使用的时候就不用都调用匹配函数了。
基本对函数和主题没什么改变,详细如下:
int PMListIndex(SString T,SString P){ //模拟PM表匹配方法 int i=1; int j=1; //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 int temp1=0; int Max_march; int section_march[P.length];//对应模式串的PM匹配表 section_march[0]=0;//将P[1]前面的空置字符和T[i]对齐 section_march[1]=0; for(int temp2=2;temp2<=P.length-1;temp2++){ section_march[temp2]=SectionMarch(P,temp2); } printf("制作的表单为:\n"); for(int temp2=0;temp2<=P.length-1;temp2++){ printf("当第j=%d 个字符失配时,其已匹配字符串的最大部分匹配值为:%d \n",temp2+1,section_march[temp2]); } while(i<=T.length && j<=P.length){ if(T.ch[i-1]==P.ch[j-1]){ printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); ++i; ++j; temp1++;//记录匹配次数 //继续比较后面的字符 } else{ printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); Max_march=section_march[temp1];//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 i++; } else if(Max_march==0){ j=1; } else{ j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 } temp1 = 0; //指针后退,模式串T右移,重新开始匹配 } if(j==P.length+1){return i-P.length;} } return 0; }
展示结果:
(四)KMP的匹配方法:
从上面我们已经看到了直接寻找模式串的所有部分前后缀匹配值是比较低效的,特别是在模式串较长的时候反而会弄巧成拙,更加的低效。由于我们单是在模式串自身的结构研究得到的部分匹配值,那么是否也可以从这里入手,挖掘我们上面做的表单项与项之间的关系,进一步避免调用SectionMarch()函数计算呢?
还真可以,先回顾一下这个图。
- 这里j代表模式串的序号取值为[1,P.length];
- ’ary‘ 是任意母串名称,'num'为母串角标取值为[0,P.length-1];
- PM[j] 代表当第j个元素失配时,前面已经匹配的字符串的部分匹配值,在理论上可能得到的取值;
- 备注------看图
这里需要强调两个可能的推理:
推理1 |
已知:PM[j]==number1 如果:ary[number]==ary[j-1] 得出:PM[J+1]=PM[J]+1 |
Elaborate:
观察上图(下面是我截取的部分):
我们能直观的发现这个规律,
PM[4]==1时,确定了a=c;
PM5]==2时,确定了a=c&&b=d;
PM6]==3时,确定了a=c=e&b=d;
PM7]==4时,确定了a=c=e&b=d=f5;
…………
看看具体的情况,仔细想想:当j==5,暨第五个字符失配时,PM[5]等于其对应元素'f'前面4个已经匹配的字符组成的字符串[a,b,c,d]实际上的最大部分匹配,而当PM[4]==1时,a=c, 这已经是在ary[3]失配时,我们面对其前面已经匹配的字符串[a,b,c]能得到的最大前后缀匹配值了。那么如果b==d,就能直接得到[ab]==[cd],进而得到一个足够大的PM[5]的值。
但是,这会是PM[5]最大的匹配值么?
推理3 |
已知:PM[j]==number1 得出: (1)PM[j-1]对应的匹配字符中有一定包含PM[j-1]==number1-1对应的匹配值,且PM(j-1)>= number-1 (2)由上面的定理得,PM[j+1]<=number+1 |
上面的定理我们也可以用数学归纳法得到证明,这里还是带来直观的感受:
如图所示,在同一行的数据里我们发现左面的数据限制是右面的数据的前设,且只差一个元素的比较。实际上,这对应着定理1。所以我们可以确定两个个规律:
(1)当同一行数据,右边的数据成立的时候,其正左面的数据也一定成立;
(2)由于我们的部分匹配串长度是按照从小到大排列的,PM值只取所有部分匹配值中最大的一个,所以在同一列数据里某个串的长度被认定为PM值时其下面的式子一定不成立!进而推论得到,其下面式子对应的同行右面的式子也一定不成立!
这样就能解释推理一得到的式子一定是其下一个PM值的最终值了。
因此,相应的,如果我们知道PM[4]==2时(暨第ary[3]个字符失配且最大部分匹配长度为2的时候),只需要确定ary[2]?=arry[3];即可确定PM[5]是否==PM[4]+1
求PM的过程的本质:
我们在求PM[j]的时候,实质上研究的是第j个元素[e]之前的字符串[a,b,c,d]的前后缀匹配关系,并把最长的匹配值赋给PM[j]作为记录
PM[4]=j-2=2时,确定的是[d]前2个以内的后缀[b,c]以及前2个以内的前缀[a,b]的匹配关系
举个栗子:
j |
5 |
ary[num] |
e |
角标num |
4 |
PM[j] |
[0,1,2,3] |
备注 |
[] [0,3]--[a,d]-->a=d3 [01,23]--[ab,cd]-->a=c&&b=d3 [012,123]--[abc,bcd]-->a=b=c=d3 |
我们在求PM[5]的时候,实质上研究的是第5个元素[e]之前的字符串[a,b,c,d]的前后缀匹配关系,并把最长的匹配值赋给PM[5]作为记录
PM=j-2=3时,确定的是[e]前3个以内的后缀[b,c,d]以及3个以内的前缀[a,b,c]的匹配关系
PM=j-3=2时,确定的是[e]前2个以内的后缀[c,d]以及2个以内的前缀[a,b]的匹配关系
……
j-5:[a,e]-->[0,4]
j-4:[b,e]-->[1,4]
j-3:[c,e]-->[2,4]
j-2:[d,e]-->[3,4]
通过PM[j]推导PM[j+1]的思路历程:
这里设j=6
实际上,我们在计算得到PM[6]=j-3=3时研究的是[f]之前的字符串[a,b,c,d,e]自身的前后缀匹配关系,并且把得到的最大的匹配值赋给了PM[6]作为记录。而在得到PM[6]=3时,我们就确定了[f]前PM[6](注:PM[6]=3)个以内的后缀[c,d,e]以及PM[6](注;PM[6]=3)个以内的前缀[a,b,c]的匹配关系。
而在通过PM[j]研究PM[j+1]时,先判断PM[j+1]的值是否可以达到其在推理3下的理论最大值PM[j]+1。在这里就是判定PM[7]是否可以达到PM[6]+1=4。
这时,根据推理1,先进行'ary[3]==ary[5]'的比较。
概由于如果匹配成功即可得到PM[j+1]=PM[j]+1=4;所以这里讨论不匹配的情况,暨:PM=j-3时, 匹配[d,f]-->[3,5],结果为d!=f的情况。
此时:PC[j+1]=j-3+1=4匹配失败;
值得注意的是:[a,b,c][d]
[c,d,e][f]
其中红色部分纵向一一对应,且由于推理3知:匹配串的长度PM[j+1]的值已经不能再增长了,暨在[d,f]失配时,PM[j+1]<=j-3-->p[7]<=3。
因此,此时我们要探讨和关注的就是——[f]前的PM[6]-1个(PM[6]-1=2暨2个)以内的尾缀[d,e]同[d]前2个以内的前缀[a,b]之间的匹配关系!
这时我们可以发现,在我们计算得到PM[4]=j-2=2时
,研究的是[d]前PM[4]=2个以内的后缀[b,c]以及前PM[4]=2个以内的前缀[a,b]的匹配关系!
值得关注的是:由于[a,b,c]=[c,d,e],所以两个字符串的前后缀匹配值是相同的!因而当我们要求[c,d,e]的后缀==[a,b,c]的前缀的最小匹配值时,就是求[a,b,c]自身的前后缀的最小匹配值!
而这个过程,正是我们在求PM[4]时完成的!
直观感受一下:
PM[4]=0时:[a]
[d]
[a,b,c][d]
[c,d,e][f]
PM[4]=1时:[a][b]
[c][d]
[a,b,c][d]
[c,d,e][f]
PM[4]=2时(实际上在确定PM[6]=3时,PM[4]是不可能等于2的。因为假设相等则会造成a=b=c=d=e,这时候计算PM[6]=4,和前设PM[6]等于3相矛盾!这里用它举例子只是为了展示逻辑运算关系,这里的问题不会影响运算逻辑,建议忽略笔者这点疏忽,没啥影响。这就跟我告诉你,假设有一个上万吨的陨石在你家楼上800米的位置由静止开始自由落体,然后经过推理告诉妮,你家可能会塌一样!我假设的情况可能不会发生,但是这不影响你理解辣么大的石头砸下去,你家会塌的推理!继续):
[a,b][c]
[b,c][d]
[a,b,c][d]
[c,d,e][f]
上面红色部分纵向一一对应的,并表示在各自情况下的最大匹配关系。在PM[4]=2时,[a,b]=[b,c];[b,c]=[d,e]
而实际上,我们需要得到的就是尾缀[d,e]和前缀[a,b]之间的最长的匹配关系。在这种
[a,b][c]
[d,e][f]
[a,b]=[d,e]的情况下,我们只要判定c是否等于f,就可以确定PM[7]是否能达到PM[6]+1-1=3了。
当然,PM[4]的值不一定就是正好等于2,但是由于我们计算PM[4]取值的时候就是在计算[d]前字符串[a,b,c]的自身前后缀匹配关系了,并且取了最大的匹配值赋值给了PM[4]。所以很容易确定,PM[4]=number时,我们可以直接确定长度为number的最大匹配前后缀字符串并通过对ary[number]和ary[j-1]比较来确定是否PM[j+1]=number+1。
这时候如果ary[number]和ary[j-1]依旧不相等怎么办?
这里采用一个合理的例子:PM[6]=3&PM[4]=1&ary[1]!=ary[5]时,
-(…O__O…|||)-
例子不够用了!!!!
改一下,这里采用一个勉强合理的例子:
已知:PM[7]=4&PM[4]=2&ary[4]!=ary[6]&ary[2]!=ary[6],
在我们希望通过PM[7]求PM[8]的时候,同样执行通过PM[6]求PM[7]的过程。
先按照推理1,根据推理1,进行'ary[4]==ary[6]'暨[e]和[g]的比较,进而确定PM[8]?=PM[7]+1。显然根据设定是不等的。
又根据推理3知,PM[8]<=PM[7]+1。而又因为PM[8]=5的匹配失败,所以PM[8]<=PM[7]。所以,这时我们要寻找PM[7]-1长度的前缀字符串[a,b,c,d]和后缀[c.d.e.f]合适的匹配值。
继而,为了判断后缀[c.d.e.f]和前缀[a,b,c,d]进一步的匹配关系。
首先,由PM[7]可以确定后缀[c.d.e.f]==前缀[a,b,c,d],所以求他们的前后缀匹配关系就是求前缀[a,b,c,d]自身的前后缀匹配关系!而这个过程正是我们在计算PM[4]的时候完成的。
根据题设条件,PM[4]=2,暨这个时候的最大匹配值为2。所以有:[a,b]==[c,d],继而得到[a,b]==[e,f],进而判断ary[2]?=ary[6]以确定PM[8]?=PM[4]+1。显然根据题设ary[2]!=ary[6]。这时可以判断PM[8]<=PM[4]
我们需要对PM[4]长度的前缀字符串[a,b]自身的匹配关系进行判断,同样的,这个过程正是在我们求PM[2]的时候完成的。
从理论上说,PM[2]可能有{0,1}两种可能的取值。数值为1的时候:
显然,这时候应该进行'ary[1]==ary[6]'暨[b]和[g]的比较,进而确定PM[8]?=PM[2]+1。
PM[2]=0的时候,则直接将'ary[0]==ary[6]'暨[a]和[g]进行比较,进而确定PM[8]?=PM[2]+1。
上述操作直观上看是这样的:
(3)通过PM[2]=0判断PM[8]?=0+1=1:[][a]
(3)通过PM[2]=1判断PM[8]?=1+1=2:[a][b]
[b][c]
(2)通过PM[4]=2判断PM[8]?=2+1=3:[a,b][c]
[c,d][e]
(1)通过PM[7]=4判断PM[8]?=5:[a,b,c,d][e]
[c,d,e,f][g]
而实际上,和之前同样的理由,由于设定的原因,实际上,这里的PM[2]不可能等于1。当然像PM[2]等于几,PM[4]等于几都是前设应该给出的,是我们需要用暴力算法计算的,或者用其前面的值推导已知的。这里为了尽量考虑全面的情况,所有数据都是假设+逆推+假设给出的,实际上不会遇到这种问题,作题这些都会是合理已得数据,自己计算这些也会是事先计算好的才对。
1 int KMPIndex(SString T,SString P){ 2 //模拟PM表匹配方法 3 int i=1; 4 int j=1; 5 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 6 int temp1=0; 7 int Max_march; 8 int section_march[P.length];//对应模式串的PM匹配表 9 section_march[0]=-1;//将P[1]前面的空置字符和T[i]对齐 10 section_march[1]=0; 11 12 13 for(int temp2=2;temp2<=P.length-1;temp2++){ 14 printf("顺利进入第 %d 次外循环;\n",temp2-1); 15 int section_march_temp1; 16 section_march_temp1=temp2; 17 int h=1; 18 for(int section_march_temp=section_march[temp2-1];section_march_temp1!=-1;section_march_temp=section_march[section_march_temp]){ 19 printf("顺利进入第 %d 次内循环;\n",h); 20 printf("此刻,section_march_temp: %d P.ch[section_march_temp]: %c temp2-1: %d P.ch[temp2-1]: %c \n",section_march_temp,P.ch[section_march_temp],temp2-1,P.ch[temp2-1]); 21 if(P.ch[section_march_temp]==P.ch[temp2-1]){ 22 printf("P.ch[section_march_temp= %d]= %c 和 P.ch[temp2-1= %d] = %c 比较判断成功 \n",section_march_temp,P.ch[section_march_temp],P.ch[temp2-1]); 23 section_march[temp2]=section_march[section_march_temp1-1]+1; 24 printf("判断成功后,section_march[temp2 = %d ]= %d \n",temp2, section_march[temp2]); 25 break;} 26 printf("匹配失败,进行深一层内循环比对。\n"); 27 section_march_temp1= section_march_temp; 28 if(section_march[section_march_temp]==-1){ 29 printf("没有合适的匹配值,即将退出内循环,section_march[%d]=0 \n",temp2); 30 section_march[temp2]=0; 31 } 32 printf("此刻section_march_temp1= %d \n",section_march_temp1); 33 34 h++; 35 } 36 } 37 38 39 printf("制作的表单为:\n"); 40 for(int temp2=0;temp2<=P.length-1;temp2++){ 41 printf("当第j=%d 个字符失配时,其已匹配字符串的最大部分匹配值为:%d \n",temp2+1,section_march[temp2]); 42 } 43 44 while(i<=T.length && j<=P.length){ 45 if(T.ch[i-1]==P.ch[j-1]){ 46 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 47 ++i; 48 ++j; 49 temp1++;//记录匹配次数 50 //继续比较后面的字符 51 } 52 else{ 53 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 54 Max_march=section_march[temp1];//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 55 printf("此刻最大的前后缀匹配值= %d \n",Max_march); 56 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 57 i++; 58 } 59 else if(Max_march==0){ 60 j=1; 61 } 62 else{ 63 j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 64 temp1 = Max_march; 65 continue; 66 } 67 temp1 = 0; 68 //指针后退,模式串T右移,重新开始匹配 69 } 70 if(j==P.length+1){return i-P.length;} 71 } 72 return 0; 73 }
运行结果展示:
这实际是我们的PM表匹配的方法·,由于每次PM[j]都是求其前面的字符串的部分匹配值,书上通过将PM这一行数据右移让她更直观的展示出来,由于是右移,所以最左面的用-1填充,函数里可以用来提示没有匹配值。最右边的匹配值因为实际上用不到(某个元素的匹配值都是下一个元素使用的)就被无情舍弃了。再把得到的值统一加一,就成了next[]函数。
书上给的代码:
1 void get_next(SString T,int next[]){ 2 int i=1; 3 int j=0; 4 next[1]=0; 5 // next[2]=1; 6 while(i<T.length){ 7 if(j==0||T.ch[i]==T.ch[j]){ 8 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i],T.ch[j]); 9 ++i,++j; 10 next[i]=j; 11 printf("next[i= %d] j= %d \n",i,j); 12 } 13 else{ 14 printf("失配!不能执行+1操作,对j递归,j=next[j= %d ]:%d \n",j,next[j]); 15 j=next[j]; 16 } 17 } 18 printf("制作的表单为:\n"); 19 for(int temp2=0;temp2<=T.length-1;temp2++){ 20 printf("(%d): %d \n",temp2+1,next[temp2]); 21 } 22 } 23 24 int Index_KPM(SString S,SString T,int next[]){//书上给的,运行有点问题。。。。 25 int m=1,n=1; 26 while(m<=S.length&&n<=T.length){ 27 if(n==0||S.ch[m]==T.length){ 28 printf("匹配T[m=%d] P[n=%d]:%c %c \n",m,n,S.ch[m],T.ch[n]); 29 ++m;++n; 30 } 31 else{ 32 n=next[n]; 33 } 34 if(n>T.length){ 35 return m-T.length; 36 } 37 else{return 0;} 38 } 39 }
是挺简洁,但是我运行它结果。。。看不太懂,不知道求的是什么。然后,我按照PM表的规律用递归算法实现了一下:
1 void fun_MN(int num,int next[],SString P,int stationary){ 2 if(P.ch[stationary-1]==P.ch[next[num]-1]){ 3 printf("匹配成功num = %d P.ch[stationary-1] %c 和 P.ch[next[num]-1 = %d ] %c \n",num,P.ch[stationary-1],next[num]-1,P.ch[next[num]-1]); 4 next[stationary+1]=next[num]+1; 5 printf("此刻,next[%d]: %d \n",stationary+1,next[stationary+1]); 6 } 7 else{ 8 printf("匹配失败num= %d ,next[num]-1 %d , P.ch[stationary-1] %c 和 P.ch[next[num]-1] %c \n",num,P.ch[num-1],P.ch[stationary-1],P.ch[next[num]-1]); 9 printf("下面看看还有没有次一级合适的匹配值,此刻num = %d next[num] = %d 下面令 num = next[num]= %d \n",num,next[num],next[num]); 10 if(next[num]==0){ 11 next[stationary+1]=1; 12 printf("可以确定,此刻num= %d 没有合适的匹配值 next[stationary] = %d next[stationary+1] = %d \n",num,next[stationary],next[stationary+1]); 13 } 14 else{ 15 printf("stationary %d",stationary); 16 fun_MN(next[num],next,P,stationary); 17 } 18 } 19 } 20 void Get_next_own(SString P,int next[]){ 21 next[1]=0,next[2]=1; 22 int num=2; 23 for(num;num){ 24 fun_MN(num,next,P,num);} 25 26 printf("制作的表单为:\n"); 27 for(int temp2=0;temp2<=P.length;temp2++){ 28 printf("(%d): %d \n",temp2+1,next[temp2]); 29 }
运行结果展示:
序号num |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
模式串 |
a |
a |
a |
b |
b |
b |
a |
a |
a |
b |
b |
b |
next[num] |
0 |
1 |
2 |
3 |
1 |
1 |
1 |
2 |
3 |
4 |
5 |
6 |
实际上,最左边还有一个next[0],里面也不知道存储的什么上面的-1存粹巧合。最右面的按照这里next[14]理应等于7,不过溢出消除了,而且也用不上就被舍弃了。
至于匹配方法还是很简单的:这里只要用next[j]-1,代替原来的,j前部分最大匹配值Max_march就可以了。
int Index_KPM_Own(SString T,SString P){ //next表KMP匹配算法 int i=1; int j=1; //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 int next[P.length]; Get_next_own(P,next); int temp1=0; int Max_march; while(i<=T.length && j<=P.length){ if(T.ch[i-1]==P.ch[j-1]){ printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); ++i; ++j; //继续比较后面的字符 } else{ printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); // Max_march=next[j]-1;//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 printf("此刻最大的前后缀匹配值= %d \n",next[j]-1); if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 i++; } // else if(Max_march==0){ else if(next[j]==1){ j=1; } else{ // j=Max_march+1; j=next[j]-1+1;//如果匹配串拥有前后缀匹配的字符串的话 continue; } //指针后退,模式串T右移,重新开始匹配 } if(j==P.length+1){return i-P.length;} } return 0; }
运行结果展示:
(五)KPM算法的进一步优化:KPMpro~
原算法实现的时候,存在一定的问题,大概……是这样的:
请关注这里,我么进行了9次b和a的比较,分别是在T[i=9]=a, P[j=9]=b 比较时发生失配。
继而进行了P[j=1]到P[j=9]分别和T[9]的比较,而实际上字串这部分都是a。
关键原因是,在T[i]和P[j]发生失配时,我们会继续用T[i]和P[next[j]进行匹配,但是当P[j]==P[next[j]]的时候这样做变得毫无意义。特别是在字串具有大量的相同字符的时候问题会更突出。
那么怎么进行优化呢?
之前我们进行了制next表,我们可以对next的值进行优化,原本在失配后是要进行T[i]和P[next[j]匹配判断的。如果我们在此之前,在制表的时候,就在确定next[j]的值的时候便将P[j]、P[next[j]]的值和P[next[next[j]]]……进行递归比对一至,我们就直接把后者的next[]值赋给前者的对应新数组varnext,这样当在发生失配的时候会直接计算T[i]和P[varnext[j]的值,进而避免多次匹配。
void fun_MN(int num,int next[],SString P,int stationary){ //求next表单的递归函数,这个鬼函数用完之后,返回给其他函数的时候,有时候会出现“吃一半临时参数P[j]”的情况,到现在也不知道为什么,所以新 //写的计算nextpro函数不能和他在一个三层函数使用,就分开写了。 if(P.ch[stationary-1]==P.ch[next[num]-1]){ printf("匹配成功num = %d P.ch[stationary-1] %c 和 P.ch[next[num]-1 = %d ] %c \n",num,P.ch[stationary-1],next[num]-1,P.ch[next[num]-1]); next[stationary+1]=next[num]+1; printf("此刻,next[%d]: %d \n",stationary+1,next[stationary+1]); } else{ printf("匹配失败num= %d ,next[num]-1 %d , P.ch[stationary-1] %c 和 P.ch[next[num]-1] %c \n",num,P.ch[num-1],P.ch[stationary-1],P.ch[next[num]-1]); printf("下面看看还有没有次一级合适的匹配值,此刻num = %d next[num] = %d 下面令 num = next[num]= %d \n",num,next[num],next[num]); if(next[num]==0){ next[stationary+1]=1; printf("可以确定,此刻num= %d 没有合适的匹配值 next[stationary] = %d next[stationary+1] = %d \n",num,next[stationary],next[stationary+1]); } else{ printf("stationary %d",stationary); fun_MN(next[num],next,P,stationary); } } } void fun_MN_Pro(int next[],SString P,int next_pro[]){ //求nextpro int temp1=1; int temp2; for(int r=1;r<=P.length;r++){ next_pro[r]=next[r]; } // printf("P.ch[0]? %c P.ch[1] %c \n",P.ch[0],P.ch[1]); // printf("赋值后的表单next_pro为:\n"); // for(int temp4=1;temp4<=P.length;temp4++){ // printf("(%d): %d \n",temp4+1,next_pro[temp4]); // } for(temp1;temp1<=P.length-1;temp1++){ for(temp2=temp1+1;temp2<=P.length;temp2++){ if(next_pro[temp2]==temp1){ // printf("匹配成功! temp1 = %d temp2 = %d 此时进行的是 next[temp2]= %d 同 temp1 = %d 的比较,\n",temp1,temp2,next[temp2],temp1); if(P.ch[temp1-1]==P.ch[temp2-1]){ // printf("匹配成功! temp1 = %d temp2 = %d 此刻进行的是P.ch[temp1-1] = %c 同 P.ch[temp2-1] %c 的比较 \n",temp1,temp2,P.ch[temp1-1],P.ch[temp2-1]); // printf("进行赋值, temp1 = %d temp2 = %d next_pro[temp2]原值为 %d ,现将next_pro[temp1] 的值 %d 赋给他。\n",temp1,temp2,next_pro[temp2],next_pro[temp1]); next_pro[temp2]=next_pro[temp1]; } // printf("匹配失败! temp1 = %d temp2 = %d 这时候我们是将 P.ch[temp1-1]= %c 同 P.ch[temp2-1] = %c 进行比较。\n",temp1,temp2,P.ch[temp1-1],P.ch[temp2-1]); } // printf("匹配失败! temp1 = %d temp2 = %d 此时进行的是 next[temp2]= %d 同 temp1 = %d 的比较,\n",temp1,temp2,next[temp2],temp1); } printf("外循环第 %d 遍结束,next_pro为:\n",temp1); for(int temp3=1;temp3<=P.length;temp3++){ printf("(%d): %d \n",temp3,next_pro[temp3]); } } printf("fun_MN_Pro函数over!"); } void Get_next_own_Pro(SString P,int next[]){ //调用第一个函数计算next表 next[1]=0; next[2]=1; int num=2; for(num;num){ fun_MN(num,next,P,num);} printf("制作的表单next为:\n"); for(int temp2=1;temp2<=P.length;temp2++){ printf("(%d): %d \n",temp2+1,next[temp2]); }} int Index_KPM_Own_Pro(SString T,SString P){ //next表KMP匹配算法 int i=1; int j=1; //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 int next[P.length]; int next_pro[P.length]; Get_next_own_Pro(P,next); fun_MN_Pro(next,P,next_pro); printf("制作的表单next_pro为:\n"); for(int temp2=1;temp2<=P.length;temp2++){ printf("(%d) %d \n",temp2+1,next_pro[temp2]); } printf("baoxianqijian制作的表单next_pro为:\n"); for(int temp2=1;temp2<=P.length;temp2++){ printf("(%d): %d \n",temp2+1,next_pro[temp2]); } while(i<=T.length && j<=P.length){ if(T.ch[i-1]==P.ch[j-1]){ printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); ++i; ++j; //继续比较后面的字符 } else{ printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 i++; } else if(next_pro[j]==0){ j=1; i++; } else{ j=next_pro[j];//如果匹配串拥有前后缀匹配的字符串的话 continue; } //指针后退,模式串T右移,重新开始匹配 } if(j==P.length+1){return i-P.length;} } return 0; }
结果展示:
母串:aaabaaaaaaaab
模式串:aaaaab
关注这里,已经不会再进行之前大量无用匹配了!
ps:最后把左右代码进行统一贴附:
1 #include2 #include 3 #include <string.h> 4 #define MaxSize 100 5 6 typedef struct String{ 7 char ch[MaxSize]; 8 int length; 9 }SString,*String; 10 11 int Index(SString T,SString P){ 12 //暴力匹配方法 13 int i=1; 14 int j=1; 15 while(i<=T.length && j<=P.length){ 16 if(T.ch[i-1]==P.ch[j-1]){ 17 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 18 ++i; 19 ++j; 20 //继续比较后面的字符 21 } 22 else{ 23 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 24 i = i-j+2; 25 j=1; 26 //指针后退,模式串T右移,重新开始匹配 27 } 28 if(j==P.length+1){return i-P.length;} 29 } 30 return 0; 31 } 32 33 int SectionMarch(SString P,int temp1){//模式串和已匹配数,返回最长的前缀等于后缀的缀的长度 34 for(int Q=1;Q //从第大到小进行部分匹配,每次匹配长度为temp1-Q 35 for(int i=0;i<=temp1-Q-1;i++){ 36 if(P.ch[temp1-Q-i-1]==P.ch[temp1-i-1]){} 37 else{ break;} 38 39 if(i==temp1-Q-1){return temp1-Q;} 40 } 41 } 42 return 0; 43 } 44 45 int PMIndex(SString T,SString P){ 46 //模拟人工匹配方法 47 int i=1; 48 int j=1; 49 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 50 int temp1=0; 51 int Max_march; 52 53 while(i<=T.length && j<=P.length){ 54 if(T.ch[i-1]==P.ch[j-1]){ 55 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 56 ++i; 57 ++j; 58 temp1++;//记录匹配次数 59 //继续比较后面的字符 60 } 61 else{ 62 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 63 Max_march=SectionMarch(P,temp1);//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 64 printf("最大匹配值为:%d \n temp1=%d \n",Max_march,temp1); 65 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 66 i++; 67 } 68 else if(Max_march==0){ 69 j=1; 70 } 71 else{ 72 j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 73 } 74 temp1 = 0; 75 //指针后退,模式串T右移,重新开始匹配 76 } 77 if(j==P.length+1){return i-P.length;} 78 } 79 return 0; 80 } 81 82 int PMListIndex(SString T,SString P){ 83 //模拟PM表匹配方法 84 int i=1; 85 int j=1; 86 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 87 int temp1=0; 88 int Max_march; 89 int section_march[P.length];//对应模式串的PM匹配表 90 section_march[0]=0;//将P[1]前面的空置字符和T[i]对齐 91 section_march[1]=0; 92 for(int temp2=2;temp2<=P.length-1;temp2++){ 93 section_march[temp2]=SectionMarch(P,temp2); 94 } 95 printf("制作的表单为:\n"); 96 for(int temp2=0;temp2<=P.length-1;temp2++){ 97 printf("当第j=%d 个字符失配时,其已匹配字符串的最大部分匹配值为:%d \n",temp2+1,section_march[temp2]); 98 } 99 100 while(i<=T.length && j<=P.length){ 101 if(T.ch[i-1]==P.ch[j-1]){ 102 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 103 ++i; 104 ++j; 105 temp1++;//记录匹配次数 106 //继续比较后面的字符 107 } 108 else{ 109 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 110 Max_march=section_march[temp1];//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 111 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 112 i++; 113 } 114 else if(Max_march==0){ 115 j=1; 116 } 117 else{ 118 j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 119 } 120 temp1 = 0; 121 //指针后退,模式串T右移,重新开始匹配 122 } 123 if(j==P.length+1){return i-P.length;} 124 } 125 return 0; 126 } 127 int ProPMListIndex(SString T,SString P){ 128 //模拟PM表匹配方法 129 int i=1; 130 int j=1; 131 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 132 int temp1=0; 133 int Max_march; 134 int section_march[P.length];//对应模式串的PM匹配表 135 section_march[0]=0;//将P[1]前面的空置字符和T[i]对齐 136 section_march[1]=0; 137 if(P.length>6){ 138 for(int temp2=2;temp2<6;temp2++){ 139 section_march[temp2]=SectionMarch(P,temp2); 140 printf("(%d) 暴力计算:%d \n",temp2,section_march[temp2]); 141 } 142 for(int temp2=6;temp2<=P.length-1;temp2++){ 143 printf("顺利进入第 %d 次外循环;\n",temp2-5); 144 int section_march_temp1; 145 section_march_temp1=temp2; 146 for(int section_march_temp=section_march[temp2-1];section_march_temp1!=0;section_march_temp=section_march[section_march_temp]){ 147 int h=1; 148 printf("顺利进入第 %d 次内循环;\n",h); 149 printf("此刻,section_march_temp: %d P.ch[section_march_temp]: %c temp2-1: %d P.ch[temp2-1]: %c \n",section_march_temp,P.ch[section_march_temp],temp2-1,P.ch[temp2-1]); 150 if(P.ch[section_march_temp]==P.ch[temp2-1]){ 151 printf("P.ch[section_march_temp= %d]= %c 和 P.ch[temp2-1= %d] = %c 比较判断成功 \n",section_march_temp,P.ch[section_march_temp],P.ch[temp2-1]); 152 section_march[temp2]=section_march[section_march_temp1-1]+1; 153 printf("判断成功后,section_march[temp2 = %d ]= %d",temp2, section_march[temp2]); 154 break;} 155 156 printf("匹配失败,进行深一层内循环比对。\n"); 157 158 159 if(section_march[section_march_temp]==0){ 160 section_march[temp2+1]=0; 161 } 162 section_march_temp1= section_march_temp; 163 h++; 164 } 165 } 166 } 167 else{ 168 for(int temp2=2;temp2<=P.length-1;temp2++){ 169 section_march[temp2]=SectionMarch(P,temp2); 170 } 171 } 172 173 printf("制作的表单为:\n"); 174 for(int temp2=0;temp2<=P.length-1;temp2++){ 175 printf("当第j=%d 个字符失配时,其已匹配字符串的最大部分匹配值为:%d \n",temp2+1,section_march[temp2]); 176 } 177 178 while(i<=T.length && j<=P.length){ 179 if(T.ch[i-1]==P.ch[j-1]){ 180 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 181 ++i; 182 ++j; 183 temp1++;//记录匹配次数 184 //继续比较后面的字符 185 } 186 else{ 187 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 188 Max_march=section_march[temp1];//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 189 printf("此刻最大的前后缀匹配值= %d \n",Max_march); 190 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 191 i++; 192 } 193 else if(Max_march==0){ 194 j=1; 195 } 196 else{ 197 j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 198 temp1 = Max_march; 199 continue; 200 } 201 temp1 = 0; 202 //指针后退,模式串T右移,重新开始匹配 203 } 204 if(j==P.length+1){return i-P.length;} 205 } 206 return 0; 207 } 208 209 int KMPIndex(SString T,SString P){ 210 //模拟PM表匹配方法 211 int i=1; 212 int j=1; 213 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 214 int temp1=0; 215 int Max_march; 216 int section_march[P.length];//对应模式串的PM匹配表 217 section_march[0]=-1;//将P[1]前面的空置字符和T[i]对齐 218 section_march[1]=0; 219 220 221 for(int temp2=2;temp2<=P.length-1;temp2++){ 222 printf("顺利进入第 %d 次外循环;\n",temp2-1); 223 int section_march_temp1; 224 section_march_temp1=temp2; 225 int h=1; 226 for(int section_march_temp=section_march[temp2-1];section_march_temp1!=-1;section_march_temp=section_march[section_march_temp]){ 227 printf("顺利进入第 %d 次内循环;\n",h); 228 printf("此刻,section_march_temp: %d P.ch[section_march_temp]: %c temp2-1: %d P.ch[temp2-1]: %c \n",section_march_temp,P.ch[section_march_temp],temp2-1,P.ch[temp2-1]); 229 if(P.ch[section_march_temp]==P.ch[temp2-1]){ 230 printf("P.ch[section_march_temp= %d]= %c 和 P.ch[temp2-1= %d] = %c 比较判断成功 \n",section_march_temp,P.ch[section_march_temp],P.ch[temp2-1]); 231 section_march[temp2]=section_march[section_march_temp1-1]+1; 232 printf("判断成功后,section_march[temp2 = %d ]= %d \n",temp2, section_march[temp2]); 233 break;} 234 printf("匹配失败,进行深一层内循环比对。\n"); 235 section_march_temp1= section_march_temp; 236 if(section_march[section_march_temp]==-1){ 237 printf("没有合适的匹配值,即将退出内循环,section_march[%d]=0 \n",temp2); 238 section_march[temp2]=0; 239 } 240 printf("此刻section_march_temp1= %d \n",section_march_temp1); 241 242 h++; 243 } 244 } 245 246 247 printf("制作的表单为:\n"); 248 for(int temp2=0;temp2<=P.length-1;temp2++){ 249 printf("当第j=%d 个字符失配时,其已匹配字符串的最大部分匹配值为:%d \n",temp2+1,section_march[temp2]); 250 } 251 252 while(i<=T.length && j<=P.length){ 253 if(T.ch[i-1]==P.ch[j-1]){ 254 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 255 ++i; 256 ++j; 257 //继续比较后面的字符 258 } 259 else{ 260 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 261 Max_march=section_march[j-1];//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 262 printf("此刻最大的前后缀匹配值= %d \n",Max_march); 263 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 264 i++; 265 } 266 else if(Max_march==0){ 267 j=1; 268 } 269 else{ 270 j=Max_march+1;//如果匹配串拥有前后缀匹配的字符串的话 271 continue; 272 } 273 //指针后退,模式串T右移,重新开始匹配 274 } 275 if(j==P.length+1){return i-P.length;} 276 } 277 return 0; 278 } 279 void get_next(SString T,int next[]){ 280 int i=1; 281 int j=0; 282 next[1]=0; 283 // next[2]=1; 284 while(i<T.length){ 285 if(j==0||T.ch[i]==T.ch[j]){ 286 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i],T.ch[j]); 287 ++i,++j; 288 next[i]=j; 289 printf("next[i= %d] j= %d \n",i,j); 290 } 291 else{ 292 printf("失配!不能执行+1操作,对j递归,j=next[j= %d ]:%d \n",j,next[j]); 293 j=next[j]; 294 } 295 } 296 printf("制作的表单为:\n"); 297 for(int temp2=0;temp2<=T.length-1;temp2++){ 298 printf("(%d): %d \n",temp2+1,next[temp2]); 299 } 300 } 301 302 int Index_KPM(SString S,SString T,int next[]){//书上给的,运行有点问题。。。。 303 int m=1,n=1; 304 while(m<=S.length&&n<=T.length){ 305 if(n==0||S.ch[m]==T.length){ 306 printf("匹配T[m=%d] P[n=%d]:%c %c \n",m,n,S.ch[m],T.ch[n]); 307 ++m;++n; 308 } 309 else{ 310 n=next[n]; 311 } 312 if(n>T.length){ 313 return m-T.length; 314 } 315 else{return 0;} 316 } 317 } 318 319 320 void fun_MN(int num,int next[],SString P,int stationary){ 321 if(P.ch[stationary-1]==P.ch[next[num]-1]){ 322 printf("匹配成功num = %d P.ch[stationary-1] %c 和 P.ch[next[num]-1 = %d ] %c \n",num,P.ch[stationary-1],next[num]-1,P.ch[next[num]-1]); 323 next[stationary+1]=next[num]+1; 324 printf("此刻,next[%d]: %d \n",stationary+1,next[stationary+1]); 325 } 326 else{ 327 printf("匹配失败num= %d ,next[num]-1 %d , P.ch[stationary-1] %c 和 P.ch[next[num]-1] %c \n",num,P.ch[num-1],P.ch[stationary-1],P.ch[next[num]-1]); 328 printf("下面看看还有没有次一级合适的匹配值,此刻num = %d next[num] = %d 下面令 num = next[num]= %d \n",num,next[num],next[num]); 329 if(next[num]==0){ 330 next[stationary+1]=1; 331 printf("可以确定,此刻num= %d 没有合适的匹配值 next[stationary] = %d next[stationary+1] = %d \n",num,next[stationary],next[stationary+1]); 332 } 333 else{ 334 printf("stationary %d",stationary); 335 fun_MN(next[num],next,P,stationary); 336 } 337 } 338 } 339 340 void Get_next_own(SString P,int next[]){ 341 next[1]=0; 342 next[2]=1; 343 int num=2; 344 for(num;num ){ 345 fun_MN(num,next,P,num);} 346 347 printf("制作的表单为:\n"); 348 for(int temp2=1;temp2<=P.length;temp2++){ 349 printf("(%d): %d \n",temp2+1,next[temp2]); 350 } 351 } 352 353 int Index_KPM_Own(SString T,SString P){ 354 //next表KMP匹配算法 355 int i=1; 356 int j=1; 357 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 358 int next[P.length]; 359 Get_next_own(P,next); 360 int temp1=0; 361 int Max_march; 362 while(i<=T.length && j<=P.length){ 363 if(T.ch[i-1]==P.ch[j-1]){ 364 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 365 ++i; 366 ++j; 367 //继续比较后面的字符 368 } 369 else{ 370 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 371 // Max_march=next[j]-1;//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 372 printf("此刻最大的前后缀匹配值= %d \n",next[j]-1); 373 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 374 i++; 375 } 376 // else if(Max_march==0){ 377 else if(next[j]==1){ 378 j=1; 379 } 380 else{ 381 // j=Max_march+1; 382 j=next[j]-1+1;//如果匹配串拥有前后缀匹配的字符串的话 383 continue; 384 } 385 //指针后退,模式串T右移,重新开始匹配 386 } 387 if(j==P.length+1){return i-P.length;} 388 } 389 return 0; 390 } 391 392 void fun_MN_Pro(int next[],SString P,int next_pro[]){ 393 int temp1=1; 394 int temp2; 395 for(int r=1;r<=P.length;r++){ 396 next_pro[r]=next[r]; 397 } 398 printf("P.ch[0]? %c P.ch[1] %c \n",P.ch[0],P.ch[1]); 399 printf("赋值后的表单next_pro为:\n"); 400 for(int temp4=1;temp4<=P.length;temp4++){ 401 printf("(%d): %d \n",temp4+1,next_pro[temp4]); 402 } 403 for(temp1;temp1<=P.length-1;temp1++){ 404 for(temp2=temp1+1;temp2<=P.length;temp2++){ 405 if(next_pro[temp2]==temp1){ 406 printf("匹配成功! temp1 = %d temp2 = %d 此时进行的是 next[temp2]= %d 同 temp1 = %d 的比较,\n",temp1,temp2,next[temp2],temp1); 407 if(P.ch[temp1-1]==P.ch[temp2-1]){ 408 printf("匹配成功! temp1 = %d temp2 = %d 此刻进行的是P.ch[temp1-1] = %c 同 P.ch[temp2-1] %c 的比较 \n",temp1,temp2,P.ch[temp1-1],P.ch[temp2-1]); 409 printf("进行赋值, temp1 = %d temp2 = %d next_pro[temp2]原值为 %d ,现将next_pro[temp1] 的值 %d 赋给他。\n",temp1,temp2,next_pro[temp2],next_pro[temp1]); 410 next_pro[temp2]=next_pro[temp1]; 411 } 412 printf("匹配失败! temp1 = %d temp2 = %d 这时候我们是将 P.ch[temp1-1]= %c 同 P.ch[temp2-1] = %c 进行比较。\n",temp1,temp2,P.ch[temp1-1],P.ch[temp2-1]); 413 } 414 printf("匹配失败! temp1 = %d temp2 = %d 此时进行的是 next[temp2]= %d 同 temp1 = %d 的比较,\n",temp1,temp2,next[temp2],temp1); 415 } 416 printf("外循环第 %d 遍结束,next_pro为:\n",temp1); 417 for(int temp3=1;temp3<=P.length;temp3++){ 418 printf("(%d): %d \n",temp3,next_pro[temp3]); 419 } 420 } 421 printf("fun_MN_Pro函数over!"); 422 } 423 424 void Get_next_own_Pro(SString P,int next[]){ 425 next[1]=0; 426 next[2]=1; 427 int num=2; 428 for(num;num ){ 429 fun_MN(num,next,P,num);} 430 431 printf("制作的表单next为:\n"); 432 for(int temp2=1;temp2<=P.length;temp2++){ 433 printf("(%d): %d \n",temp2+1,next[temp2]); 434 }} 435 436 437 438 int Index_KPM_Own_Pro(SString T,SString P){ 439 //next表KMP匹配算法 440 int i=1; 441 int j=1; 442 //注意这里的i和j分别代表的是母串和模式串的字符序号而不是角标 443 int next[P.length]; 444 int next_pro[P.length]; 445 Get_next_own_Pro(P,next); 446 447 fun_MN_Pro(next,P,next_pro); 448 printf("制作的表单next_pro为:\n"); 449 for(int temp2=1;temp2<=P.length;temp2++){ 450 printf("(%d) %d \n",temp2+1,next_pro[temp2]); 451 } 452 453 printf("baoxianqijian制作的表单next_pro为:\n"); 454 for(int temp2=1;temp2<=P.length;temp2++){ 455 printf("(%d): %d \n",temp2+1,next_pro[temp2]); 456 } 457 // int Max_march; 458 while(i<=T.length && j<=P.length){ 459 if(T.ch[i-1]==P.ch[j-1]){ 460 printf("匹配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 461 ++i; 462 ++j; 463 //继续比较后面的字符 464 } 465 else{ 466 printf("发生失配T[i=%d] P[j=%d]:%c %c \n",i,j,T.ch[i-1],P.ch[j-1]); 467 // Max_march=next[j]-1;//判断已经匹配的temp1个字符里面,最大的相同前后缀字符子串长度 468 // printf("此刻最大的前后缀匹配值= %d \n",next[j]-1); 469 if(j==1){//当第一个匹配值就不匹配时;匹配串右移一位,母串指针i右移一位 470 i++; 471 } 472 // else if(Max_march==0){ 473 else if(next_pro[j]==0){ 474 j=1; 475 i++; 476 } 477 else{ 478 // j=Max_march+1; 479 j=next_pro[j];//如果匹配串拥有前后缀匹配的字符串的话 480 continue; 481 } 482 //指针后退,模式串T右移,重新开始匹配 483 } 484 if(j==P.length+1){return i-P.length;} 485 } 486 return 0; 487 } 488 489 490 491 492 int main(){ 493 SString T; 494 SString P; 495 496 int situated; 497 printf("请输入字符序列T:\n"); 498 scanf("%s",&T.ch); 499 printf("字符串T: %s \n",T.ch); 500 T.length=strlen(T.ch); 501 printf("T.length: %d \n",T.length); 502 503 printf("请输入字符序列P:\n"); 504 scanf("%s",&P.ch); 505 printf("字符串T: %s \n",P.ch); 506 P.length=strlen(P.ch); 507 printf("P.length: %d \n",P.length); 508 509 // situated =KMPIndex(T,P); 510 // int next[P.length]; 511 // get_next(P,next); 512 situated =Index_KPM_Own_Pro(T,P); 513 printf("匹配完成,相对位置为:%d \n",situated); 514 // Get_next_own(P,next); 515 return 0;}