此篇记录了我学习KMP算法时的迷惑和解决过程
(本篇适合学习KMP算法之后,对算法尚有些迷惑的朋友,欢迎大家批判)
1、第一个疑问:KMP算法是否一定能求出正确的首个匹配下标(匹配字符串的第一个字符的下标)
因为KMP算法不像暴力求解算法一样是一个一个字符行进的,有点类似跳跃的行进,跳过了不需要匹配的字符,我产生了一个疑问,在行进的过程中是否会遗漏本可以匹配的字符串,导致得到的匹配下标不是首个匹配下标,而是第二或第三第四个
反证,假设不能。即存在一个被忽略的更小的下标。
k
A B A B A A B........
A B A A B ------>黄色为匹配失败处
i h k (i是假设的,h为最长前缀开始处)
A B A B A A B........
A B A A B ------>不同于暴力求解一位一位右移,而是类似“跳跃”。红色处为最长前缀,黄色处继续匹配
假设我们会遗漏一个“更小的匹配下标i”,使得文本txt[i]~txt[i+length] (length为模式串长度)与模式串pat[0]~pat[length]匹配,那么txt[i]~txt[k]一定是比我们当前的“最长前缀”txt[h]~txt[k]更长的前缀,则与“next数组保存的是最长前缀”相矛盾,故不可能存在更长的前缀,即i不存在。
证得:我们不会遗漏一个更小的匹配下标
2、next数组的求法
0 | 1 | 2 | 3 | 4 | k | 6 | 7 | 8 | 9 | 10 | j | j+1 |
A | B | G | A | B | B | A | B | G | A | B | G | H |
-1 | 0 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | ? |
next[k] = 2
第一行为模式串下标,第二行为模式串,第三行为next值
我们现在要求next[j+1],我们容易知道,如果pat[k] == pat[j],使得pat[0]~pat[k] == pat[j-k]~pat[j]的话,
那么next[j+1] = next[j]+1
那要是不等于呢?即pat[k] != pat[j]
通过学习,我们知道,要比较pat[next[k]]和pat[j],如果pat[next[k]] == pat[j],则next[j+1] = next[k] + 1
问题就是,为什么是比较pat[next[k]]和pat[j] ,为什么是next[k]??????
看上图,黄色标记处pat[k(5)] = B和pat[j(11)] = G匹配失败,而next[k] = 2,pat[next[k]] = pat[2] = G,最长前缀长度变成了3,即A B G,是可以匹配的最长前缀(pat[0~2] == pat[9~j])
事实上,我们可以证明这一事实的正确性
因为匹配失败,那么我们要寻找的最长前缀肯定是一个比A B G A B(0~k-1)更短的前缀
而且这个更短的前缀,一定是包含在A B G A B(0~k-1)里(因为必须从第一个字符开始,且长度更短)
仔细思考,我们得出这样一个结论:
我们假设这个最长前缀存在(pat[j+1]前的最长匹配前缀),设为str
则str - pat[j] 的长度一定等于next[k]
看下图
该图中str = ABG, pat[j] = G
(一定要注意观察和思考下面的三个红色A B)
0 | 1 | 2 | 3 | 4 | k | 6 | 7 | 8 | 9 | 10 | j | j+1 |
A | B | G | A | B | B | A | B | G | A | B | G | H |
-1 | 0 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | ? |
next[k] = 2
我们先不管pat[j],先寻找pat[j]前长度稍短于k的前缀(实际上这个稍短前缀的长度就是next[k]),然后再加上pat[j]得到新前缀,得到next[j+1] = next[k]+1。上例中这个稍短前缀就是AB,pat[j] = G。
为什么稍短前缀的长度就是next[k]?
因为pat[0] ~ pat[k-1]和pat[j-k]~pat[j-1]都是一样的(已经匹配过)
那么str - pat[j]必然和pat[k]前的子串一模一样(而且这两个串都一定等于一个前缀,在上例中就是AB)
由于我们要使得str - pat[j]最长,那这个最长的长度一定就是next[k]
最后,我们再判断是否pat[next[k]] = pat[j],如果等于,则得到next[j+1] = next[k] + 1 (AB + G)
否则,再继续递归寻找,下一步判断pat[next[next]]。。。。道理同上