算法提升-2-KMP
引入问题
给定两个字符串str
和match
,长度为 $N$ 和 $M$ 。如果字符串str
中含有子串match
,返回match
在str
中开始位置。
如果match
的长度大于str
长度( $M>N$ ),str
必然不会含有match
,可直接返回-1
。但如果 $N≥M$ ,要求算法复杂度为 $O(M)$
next数组
next数组含义
next[i]
的含义是在 match[i]
之前的字符串 match[0..i-1]
中,必须以 match[i-1]
结尾的 后缀子串 与必须以match[0]
开头的 前缀子串 最大匹配长度
后缀字符不能包含
match[0]
,即整个后缀不能是本身前缀字符不能包含
match[i-1]
,即整个前缀不能是本身
求解next数组
match[0]
:他之前没有字符,next[0]
规定为 -1
match[1]
:next数组定义要求任何子串后缀不能包含第一个字符,故 match[1]
之前的字符串只有长度为0的后缀字符串,next[1]
规定为0
match[i]
1.从左至右依次求解next,求解 next[i]
时,next[i-1]
已经求出,如图I
区域是最长匹配前缀,k
区域是最长匹配后缀
2.如果C
和B
相等,A
之前最长公共前后缀就可以确定,前缀子串为I区域+C
,后缀子串为k区域+B
,即 next[i]=next[i-1]+1
3.如果C
和B
不相等,就要看C
之前的前缀和后缀匹配情况
假设字符C
是第cn个字符,那么next[cn]
就是其最长前缀和最长后缀的匹配长度
m
、n
区域是相等的,m'
区域为k
区域最右的区域且长度与m
区域相等,字符D
是n
区域后面一个字符,
所以接下来比较字符D和字符B是否相等
如果
B
、D
相等,A
字符之前的最长前缀与后缀匹配区域就可以确定,前缀区域为n区域+D
,后缀区域是m'区域+B
,则令next[i]=next[cn]+1
如果
B
、D
不等,继续往前跳到D
字符,每一步都有新的字符和B
比较,只要相等的情况,next[i]
就确定
4.如果跳到最左位置(match[0]
位置),此时next[0]=-1
,说明字符A
之前的字符串不存在前缀和后缀的匹配情况,则next[i]=0
KMP算法
假设从str[i]
字符出发时,匹配到j位置的字符发现与match
中的字符不一致,
现在有match
字符串的next数组,next[j-i]
表示 match[0..j-i-1]
这段字符前缀与后缀的最长匹配
下一次匹配检查让str[i]
与match[k]
进行匹配检查,对于match[] 来说,相当于向右滑动,让match[k]
滑动到与str[j]
在同一个位置上,然后进行后续的匹配检查,一直进行这样的滑动匹配,直到在str
某一位置把match
完全匹配,说明str
中有match
,如果滑动到最后也没匹配出来,说明str
中没有match
为什么中间不要检查,必然匹配不了呢
在str[j]
位置匹配失败,b
区域与c
区域相等,a
区域与b
区域相等,必然a
区域与c
区域相等
中间的区域不需要检查,直接将a
区域滑动到与c
区域对齐即可
假设d
区域开始字符是不要检查区域的一个位置,如果这位置开始匹配出match
,整个d区域要和从match[0]
开始的e区域匹配,d
和e
的长度一样,d
区域比c
区域大,e
区域比a
区域大
时间复杂度
str
匹配位置是不退回的,match
一直向右移动,如果在str
某个位置完全匹配出match
,整个过程停止,否则match
滑动到str
最右侧停止,滑动最大长度为 $N$,所以时间复杂度为 $O(N)$
算法实现
getNext
1 | public int[] getNext(char[] match) { |
KMP
1 | public int KMP(String str, String match) { |