算法:
KMP主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
最长公共前后缀:
字符串的前缀:不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀:不包含第一个字符的所有以最后一个字符结尾的连续子串。
举例:字符串"aabaa"
其前缀有:“a” "aa" "aab" "aaba" "aaba"
其后缀有:"a" "aa" "baa" "abaa"
最长公共前后缀:
字符串"aabaa"是"aa"。
前缀表:
前缀表要求的就是相同前后缀的长度。字符串"aabaa"的最长公共前后缀的长度为2
next数组就是一个前缀表
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
前缀表的计算:
长度为前1个字符的子串a
,最长相同前后缀的长度为0。(注意字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)
长度为前2个字符的子串aa
,最长相同前后缀的长度为1。
长度为前3个字符的子串aab
,最长相同前后缀的长度为0。
以此类推: 长度为前4个字符的子串aaba
,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa
,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf
,最长相同前后缀的长度为0。
那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
缀表的实现:
next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。
为什么这么做呢?
其实这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。
next数组实现代码:
前缀表统一减一:
#在文本串s里 找是否出现过模式串t
def getNext(next, s):
#j 指向模式串起始位置
#因为next数组里记录的起始位置为-1,所以j初始值为-1
j = -1
#next[x] 表示 x(包括x)之前最长相等的前后缀长度(其实就是j)
next[0] = j
#i指向文本串起始位置
#因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。
for i in range(1, len(s)):
#处理前后缀不相同的情况:向前回退
while j >= 0 and s[i] != s[j+1]:
# s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
j = next[j]
#处理前后缀相同的情况:后移
if s[i] == s[j+1]:
j += 1
next[i] = j
代码里面只有字符串s和前缀表next,每次比较都是把`s[i]
`和`s[j+1]
`匹配。
在循环中,`i
`表示文本串中的当前位置,`j
`表示模式串中与当前位置对应的最长相等的前后缀的末尾位置。我们通过比较`s[i]
`和`s[j+1]
`来判断是否能够继续匹配。如果相等,则将`j
`向后移动一位,并将`next[i]
`设置为`j
`,表示当前位置的最长相等前后缀的末尾位置。如果不相等,则通过`next
`数组回溯到上一个最长相等前后缀的末尾位置,继续尝试匹配。
前缀表不减一:
def getNext(self, next: List[int], s: str) -> None:
j = 0
next[0] = 0
for i in range(1, len(s)):
while j > 0 and s[i] != s[j]:
j = next[j - 1]
if s[i] == s[j]:
j += 1
next[i] = j
改一下j的初始值
主要就是j=next[x]这一步最为关键!