克努斯-莫里斯-普拉特算法(Knuth-Morris-Pratt 算法,简称 KMP 算法),是常用的字符串搜索算法之一。
部分匹配表
部分匹配表(Partial Match Table),又称为失配函数(Failure Function)。
假设有个字符串 "abababca"
,可以得出它的部分匹配表为:
s: | a | b | a | b | a | b | c | a | index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | table: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |
其中的 table[i]
表示子模式串 s[0...i]
的匹配值。例如:i = 5
,子模式串就是 string[0:5] = "ababab"
,它的匹配表 table[5] = 4
。
在此之前,解释一下模式串的前缀和后缀。
前缀(Proper prefix):字符串从首位开始的所有子串,不包括最后一个字符。例如:"hello"
的前缀有:"h"
、"he"
、"hel"
、"hell"
。
后缀(Proper suffix):字符串到末尾结束的所有子串,不包括第一个字符。例如:”world” 的后缀有:"orld"
、"rld"
、"ld"
、"d"
。
jBoxer 的这篇文章,用一句话解释了其中的部分匹配表的实质。
The length of the longest proper prefix in the (sub)pattern that matches a proper suffix in the same (sub)pattern.
子模式串的前缀和后缀的共同元素中最长元素的长度。
例
例如:上面的字符串 "abababca"
,对子模式串 s[0:4] = "ababa"
来说,它的前缀有:
["a", "ab", "aba", "abab"]
后缀有:
["baba", "aba", "ba", "a"]
可以看出其中的共同元素为 ["a", "aba"]
,其中最长元素为 "aba"
,它的长度为 3 ,所以它的部分匹配表 table[4] = 3
。
部分匹配表作用
暴力匹配算法中,遇到不匹配的字符,将模式串向前移动一位,重新开始比较。
在 KMP 算法中,在遇到不匹配的字符时,根据部分匹配表中的值,将模式串向前移动,跳过不必要的匹配。
If a partial match of length partial_match_length is found and
table[partial_match_length] > 1
, we may skip aheadpartial_match_length - table[partial_match_length - 1]
characters.如果进行到模式串的第
i
个字符不匹配,即已部分匹配到的长度为len = i
,并且table[len] > 0
,那么下一步匹配可以跳过len - table[len-1]
个字符。
例
以 "abababca"
为例,可以得出它的部分匹配表:
s: | a | b | a | b | a | b | c | a | index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | table: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |
目标字符串为 "bacbababaabcbab"
,第一次匹配位置:
// 绿色为已匹配字符,红色为不匹配字符,黄色为跳过字符 1 index: 012345678901234 m: bacbababaabcbab | s: abababca index: 01234567 table: 00123401
因为 s[1] != m[2]
,已部分匹配的模式串 "a"
长度为 len = 1
。部分匹配表中 table[len-1]
即 table[0]
的值为 0,所以不需要跳过字符,只需将指向母串字符的指针加一。下一个匹配位置如下:
// 绿色为已匹配字符,红色为不匹配字符,黄色为跳过字符 1 index: 012345678901234 m: bacbababaabcbab ||||| s: abababca index: 01234567 table: 00123401
已部分匹配的模式串 "ababa"
长度为 len = 5
。部分匹配表中 table[5-1]
即 table[4]
值为 3。所以需要向前跳过 len - table[4] = 5 - 3 = 2
个字符:
// 绿色为已匹配字符,红色为不匹配字符,黄色为跳过字符 1 index: 012345678901234 m: bacbababaabcbab xx||| s: abababca index: 01234567 table: 00123401
此时部分匹配的模式串变为 "aba"
长度为 len = 3
。table[3-1] = table[2]
值为 1,所以需要向前跳过 len - table[2] = 3 - 1 = 2
个字符:
// 绿色为已匹配字符,红色为不匹配字符,黄色为跳过字符 1 index: 012345678901234 m: bacbababaabcbab xx| s: abababca index: 01234567 table: 00123401
此时模式串的长度已经超过了母串的剩余字符数,所以余下的字符无法匹配。
代码
1 | // JavaScript |
参考链接
- 字符串匹配的KMP算法,by 阮一峰。
- The Knuth-Morris-Pratt Algorithm in my own words, by jBoxer.
- KMP Algorithm for Pattern Searching, GeeksforGeeks.