数据结构-4.6.KMP算法(旧版下)-朴素模式匹配算法的优化

一.绪论:

当主串字符和模式串字符不匹配时会执行j=next[j]来改变模式串的指针,但主串的指针不变。


二.求模式串的next数组:

1.例一:

如模式串abcabd,当第六个字符d匹配失败时,此时主串中前五个字符abcab都与模式串匹配成功,因此需要后移模式串:
模式串后移一个位置,显然主串中第二个字符b与模式串中第一个字符a不匹配,继续后移:
模式串再后移一个位置,显然主串中第三个字符c与模式串中第一个字符a不匹配,继续后移:
模式串再再后移一个位置,显然主串中第四个字符a与模式串中第一个字符a匹配,无需后移:
因此当模式串指针指向的第六个字符匹配失败时要让模式串指针从6回溯到3即next[6]为3:

2.例二:

如模式串abababcdef,当在第七个字符c匹配失败时,此时模式串中的前六个字符都能与主串匹配成功,最终需要模式串向右移动:
模式串后移一个位置,显然主串第二个字符b与模式串第一个字符a匹配失败,因此继续后移模式串:
模式串再后移一个位置,此时主串第三个字符a与模式串第一个字符a匹配成功,此时模式串无需后移:
因此当模式串指针指向的第七个字符匹配失败时要让模式串指针从7回溯到5即next[7]为5。
如果把模式串继续后移两个位置,此时模式串指针指向模式串的第三个字符a,而且模式串的第一个字符a和第二个字符b都可以匹配成功:
假设主串中字符如下图所示,此时主串指针i指向的字符和模式串第七个字符c不匹配,而且继续往后匹配,主串的c和模式串的第五个字符a匹配失败,如果按照刚才的next[7]为5,配对如下:
因此结论如下:如果有更长的一段能够匹配的上的话,优先考虑更长的这种情况

3.例三:

如模式串为aaaabcd,在第五个字符b和主串匹配失败,可知前四个字符aaaa都可以与主串匹配成功,此时需要把模式串后移:
模式串后移一个位置后,模式串前三个字符aaa都可以与主串匹配成功,此时应该让模式串里的指针回溯到4,判断是否能与主串匹配成功即next[5]为4:
显然,如果模式串再往后移一个位置,模式串和主串开头也能够匹配上,但一定能匹配成功的字符只有模式串中的前两个字符aa,主串中第五个字符还不确定是否为a,不往后移之前一定能匹配成功的字符有三个aaa,因此暂时不移动模式串较好;

4.例四:

如模式串为abcdefg,在第五个字符e时和主串匹配失败,因此模式串前4个字符abcd可以匹配成功,因此模式串需要后移:
模式串后移一个位置后,显然模式串第一个字符a与主串第二个字符b就不匹配,因此继续后移:
模式串后移一个位置后,显然模式串第一个字符a与主串第三个字符c就不匹配,因此继续后移:
模式串后移一个位置后,显然模式串第一个字符a与主串第四个字符d就不匹配,因此继续后移:
模式串后移一个位置后,此时模式串第一个字符a可能与主串第五个字符匹配成功,因此需要模式串指针从5回溯到1即next[5]为1,这个例子的特殊之处在于前几个例子中总是能找到主串已经确定的一些字符和模式串开始的字符能够匹配成功,但这个例子中主串中确定的字符中,无论后移几次模式串,都无法与主串匹配成功,所以之后需要判断主串中未知的字符能否与模式串中的字符匹配成功:

5.例五:

如模式串为abcabd,当检查到模式串中第一个字符a就发现与主串第一个字符匹配失败,此时模式串无需移动,需要检查主串的第二个字符能否与模式串中第一个字符a匹配成功:
如下图所示,此时可以先把模式串里的指针设为0:
然后主串指针和模式串指针同时加一,最终next[1]为0:

6.代码:

7.总结:

a.对于串的前缀,如模式串中第一个字符到第六个字符ababab,以ab为一段,第一个字符到第四个字符abab就是ababab的一个前缀,不包含最后一段;

b.对于串的后缀,如主串中的第一个字符到第六个字符ababab,以ab为一段,第三个字符到第六个字符abab就是ababab的一个后缀,不包含第一段;

c.注:前缀和后缀中前缀和后缀不一定只有一个字符组成,如ababab中a,ab,aba等都可以是它的前缀,后缀同理;

d.对于next[j]=S的长度等于前/后缀对的上的最大长度加一,如模式串中的S串(ababab),前缀最大就是abab,长度为4,所以S的最大长度为4+1等于5;

再比如上述图中右边的例子:模式串指针j在第五个字符时匹配失败 ,那么此时就要看前面的j-1个字符,由这些字符组成的字符串S(S为abcd),此时要找主串的后缀和模式串的前缀对的上的串的最大长度,先主串中从abcd后面开始取后缀d,再在模式串从abcd前面开始取前缀a,发现对不上,之后,主串中从abcd后面开始取后缀cd,模式串从abcd前面开始取前缀ab,发现还是对不上,之后,主串中从abcd后面开始取后缀bcd,模式串从abcd前面开始取前缀abc,发现还是对不上,现在主串和模式串就都不能再取了,因为主串取后缀,取后缀不能包含第一个字符,主串一共4个字符,从后面已经取了3个字符,第一个字符不能再取,模式串取前缀,取前缀不能包含最后一个字符,模式串一共4个字符,从前面已经取了3个字符,最后一个字符不能再取,所以S串最大长度为0,加一为1,因此next[5]为1:

比较特殊的情况,就是例五:

二.练习:求模式串的next数组

1.题目一:

思路:首先可以确定j为1时有next[j]为0;(其实next[1]必定为0,next[2]必定为1)

如果当 j为2即模式串指针指向的模式串第二个字符b时发现匹配失败,按照规则,第二个字符之前即第一个字符a组成的串S(只有一个a,a是第一个字符,也是最后一个字符),显然S串的前缀是空串(长度为0),因为前缀不包含最后一个字符,后缀也是一个空串(长度为0),因为主串中只有a与 模式串匹配成功,后缀不包含第一个字符,所以S串最大长度为0,加一为1,因此next[2]为1;

如果当j为3即模式串指针指向的模式串第三个字符a时发现匹配失败,说明第三个字符之前即ab都与主串匹配成功即串S为ab,串S的一个字符记为ab,显然串S没有前缀和后缀(串S取一个字符时S串最大长度也为0,当S串前缀为a时,就不能要b,对应的主串后缀就是b,就不能要a,显然前缀和后缀匹配失败,S串最大长度也为0),所以S串最大长度为0,加一为1,因此next[3]为1;

如果当j为4即模式串指针指向的模式串第四个字符b时发现匹配失败,说明第四个字符之前即aba都与主串匹配成功即串S为aba,此时前缀取一个字符a,后缀取一个字符a,只剩下b,所以S串最大长度为1,加一为2,因此next[4]为2;(其实前缀可以依次是a,ab,对应的后缀依次是a,ba,将前缀和后缀分别看成两个集合,这两个集合的交集只有一个a,所以S串最大长度为1,前缀和后缀也可以取aba,此时没有前缀和后缀,但这样的话S串最大长度为0,加一为1,因此next[4]为1,显然比next[4]为2低效,不可取,其他取前缀和后缀的原理同理,而且取前/后缀要考虑匹配最多的取);

如果当j为5即模式串指针指向的模式串第五个字符a时发现匹配失败,说明第五个字符之前即abab都与主串匹配成功即串S为abab,取前缀为ab,后缀也取ab(此时主串中有abab),显然前缀和后缀匹配成功,模式串中取了ab,长度为2,因此S串最大长度为2,加一为3,因此next[5]为3;

如果当j为6即模式串指针指向的模式串第六个字符a时发现匹配失败,说明第六个字符之前即ababa都与主串匹配成功即串S为ababa,如果前缀取aba,后缀也取aba(此时主串中有ababa,取的是第一个字符到第三个字符的aba,因为可以与模式串匹配上的字符多,不是第三个字符到第五个字符的aba),前缀和后缀可以匹配成功,模式串中取了aba,长度为3,因此S串最大长度为3,加一为4,因此next[6]为4;

2.题目二:

思路:首先可以确定j为1时有next[j]为0;可以确定j为2时有next[j]为1;

如果当j为3即模式串指针指向的模式串第三个字符a时发现匹配失败,说明第三个字符之前即aa都与主串匹配成功即串S为aa,显然前/后缀能匹配的上的最长的长度为1(取a,如果取aa的话就没有前缀和后缀了,此时前缀和后缀的长度为0),因此S串最大长度为1,加一为2,因此next[3]为2;

如果当j为4即模式串指针指向的模式串第四个字符a时发现匹配失败,说明第四个字符之前即aaa都与主串匹配成功即串S为aaa,显然前/后缀能匹配的上的最长的长度为2(取aa,如果取aaa的话前/后缀的长度为0,取a的话主串与模式串能匹配的字符最少,只有一个,不可取),因此S串最大长度为2,加一为3,因此next[4]为3;

如果当j为5即模式串指针指向的模式串第五个字符b时发现匹配失败,说明第五个字符之前即aaaa都与主串匹配成功即串S为aaaa,显然前/后缀能匹配的上的最长的长度为3(取aaa,前缀和后缀都能匹配上),因此S串最大长度为3,加一为4,因此next[5]为4。


三.求模式串的next数组的代码:

1.演示:

a.第二条求next[j]的语句中:等号左边的是模式串里的字符(即前缀),注意下标k-1,等号右边的是主串里的字符(即后缀),注意下标j-1,他们之间能够匹配的上的(相等)最大长度;k为模式串的指针,j为主串的指针;注意最长相等前后缀长度加一

b.第三条求next[j]的语句中:其他情况一般指前缀和后缀匹配失败时;

2.代码:

时间复杂度分析:从Index_KMP函数(Index_KMP函数中i为主串指针,i初始值为1,j为模式串指针,j初始值为1)开始,之后到get_next函数,然后到get_next函数里的while循环,其中i初始值为1,j初始值为0,T就是模式串,T.length就是模式串的长度,get_next函数里的while循环要循环1到T.length-1次,设T.length-1为m,那么get_next函数的while循环的时间复杂度为O(m),所以get_next函数的时间复杂度为O(m),执行完get_next函数,到了Index_KMP函数里的while循环,不需要考虑j,因为j是模式串的指针,主串的长度一般远大于模式串的长度,因此i的变化远大于j的变化,所以j就可以忽略不计了,主串指针i永远不回溯,最多不会超过主串的最后一个位置,所以while循环执行1到S.length次(S代表主串,S.length代表主串的长度),设主串的长度为n,那么Index_KMP函数里的while循环的时间复杂度为O(n),所以Index_KMP函数的时间复杂度为O(n),由此可得,Index_KMP函数的平均时间复杂度为O( (n+m)/2 ),等价于O(n+m)。


四.总结:

1.KMP算法让主串指针不回溯,只让模式串指针回溯;

2.朴素算法是主串指针和模式串指针都需要回溯;


相关推荐
luthane20 分钟前
python 实现algorithm topo卡恩拓扑算法
数据结构·python·算法
Mr_Xuhhh2 小时前
数据结构阶段测试2的一点小补充
android·开发语言·汇编·数据结构·c++·算法
青山瀚海2 小时前
多模态简单了解
深度学习·算法·机器学习·transformer
sp_fyf_20242 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-04
人工智能·深度学习·算法·机器学习·计算机视觉·语言模型·数据挖掘
Helene19002 小时前
Leetcode 135-分发糖果
算法·leetcode
嗯? 嗯。3 小时前
立志最细,FreeRtos中的信号量Semaphore教程详解!!!
算法
aqua35357423583 小时前
蓝桥杯:求平均年龄
算法·职场和发展·蓝桥杯
LluckyYH4 小时前
代码随想录Day 62|Floyd 算法精讲、A \* 算法精讲 (A star算法),题目:97. 小明逛公园、127. 骑士的攻击
数据结构·c++·算法·leetcode·图论
luthane4 小时前
python 实现多启发式a star(A*)算法
python·算法·机器学习