字符串模式匹配(查找算法)

目录

一、概念

1.模式匹配 

 2.KMP算法

(1)定义

(2)手撕next数组

(3)快速求解next数组

(4) 优化

三、代码

四、题目

1.统计单词数

2.KMP

3. 剪花布条


一、概念

1.模式匹配 

假设有两个串S,T,设S为主串,也称正文串,T为子串,也成为模式,在主串S中查找与模式T相匹配的子串,如果查找成功,返回匹配的子串第一个字符在主串中的位置。

最笨的办法就是穷举所有S的所有子串,判断是否与T匹配,即BF算法(暴力穷举)。

 2.KMP算法

(1)定义

KMP算法——著名的模式匹配算法。KMP算法的特点是i不回退,如果S[i]=T[j]时,则i++,j++,继续比较;如果S[i]≠T[j],j回退到next[j],重新开始比较。算法关键是计算next[]数组

此例从图中观察可看出T的前两个字符无需比较,那么一般情况怎么确定T的前几个字符不需要比较了呢? 

 i指向字符的前两个字符和T串中j指向字符的前两个字符一样,不一样就不会i++,j++比较当前位置元素了。那么T的前几个字符无需比较可以根据T串确定

继续探究不需要比较几个字符 

假设T串中当前j指向的字符前面的所有字符为{T}',比较T^{​{}'}的前缀和T^{​{}'}的后缀,无需比较的字符数为T^{​{}'}相等前缀和后缀的最大长度。

j指向e,那么T^{​{}'}为abaab,则T^{​{}'}的前缀有{a、ab、aba、abaa},后缀有{b、ab、aab、baab},T^{​{}'}前缀和后缀相等且长度最大的为ab,所以无需比较2个字符。

注意:前缀和后缀不能包含T^{​{}'}本身

(2)手撕next数组

next[j]表示S[i]≠T[j]时,j需要回退的位置,T^{​{}'}="t_{0}t_{1}...t_{j-1}"

next[0]表示S[i]≠T[0](T的第一个字符与S不一致)时,j要退回到-1位置,因为后续代码会有i++,j++的操作,那么就会让T的第一个字符和S的下一个位置进行比较

纯手工计算T=”abaabe“的next[]数组

 

j=0,next[j]=-1;

j=1,T^{​{}'}=a,没有前缀和后缀,next[j]=0;

j=2,T^{​{}'}=ab,前缀{a},后缀{b},没有相等的前缀,next[j]=0;

j=3,T^{​{}'}=aba,前缀{a,ab},后缀{a,ba},相等前缀后缀的最大长度为1,next[j]=1;

j=4,T^{​{}'}=abaa,前缀{a,ab,aba},后缀{a,aa,baa},相等前缀后缀的最大长度为1,next[j]=1;

j=5,T^{​{}'}=abaab,前缀{a,ab,aba,abaa},后缀{b,ab,aab,baab},相等前缀后缀的最大长度为2,next[j]=2;

j=6,T^{​{}'}=abaabe,前缀{a,ab,aba,abaa,abaab},后缀{e,be,abe,aabe,baabe},没有相等的前缀,next[j]=0;

T的长度明明是6,为什么还要计算next[6]?

模式匹配只能计算T是否是S的子串,有了next[6]就可以计算T在S中出现的次数。如下图

 这样枚举T^{​{}'}的前缀和后缀求相同前缀后缀的最大长度和暴力枚举一样时间复杂度高,那么有什么办法能够快速求解next数组?

(3)快速求解next数组

假设已知next[j]=k,{T}'=t_{0}t_{1}t_{2}...t_{j-1}那么T^{​{}'}最长的相同前缀后缀如下所示