






















The Knuth-Morris-Pratt Algorithm in my own words - jBoxer

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

9.1 Knuth-Morris-Pratt KMP String Matching Algorithm
s=aaaaaaab
t=aaab
用基础的逐字符暴力比较算法,假设s的字符串长度是n, t的字符串长度是m。算法的时间复杂度是n*m
t=abcdabc
prefix:
suffix:
在 KMP 算法 中,LPS 是 Longest Prefix Suffix(最长前缀后缀)的缩写。它是 KMP 算法中用于优化匹配过程的核心概念之一。
在计算机科学中,特别是字符串匹配算法(如KMP算法)中,LPS通常指的是**最长公共前后缀(Longest Proper Prefix which is also a Suffix)**数组。
对于字符串 $S$,LPS 数组中的第 $i$ 个元素 $LPS[i]$ 表示子字符串 $S[0 \dots i]$ 最长的那个真前缀,同时它也是 $S[0 \dots i]$ 的后缀的长度。
对于字符串 $S = \text{"abaabc"}$,我们来计算它的 LPS 数组:
字符串s=abaabaabaca
t=abaabc
第一次匹配失败的时候,i和j分别是多少?
下一次匹配的时候,i和j分别是多少
这个问题考察的是KMP(Knuth-Morris-Pratt)字符串匹配算法的核心机制,特别是匹配失败时的回溯(next/LPS 数组的应用)。
我们使用以下标记:
$S$: 主字符串($\text{"abaabaabaca"}$)
$T$: 模式字符串($\text{"abaabc"}$)
$i$: $S$ 字符串的当前匹配位置(指针)
$j$: $T$ 字符串的当前匹配位置(指针)
$LPS$ 数组: $T$ 模式字符串的最长公共前后缀数组。
这个问题考察的是KMP(Knuth-Morris-Pratt)字符串匹配算法的核心机制,特别是匹配失败时的回溯(next/LPS 数组的应用)。
我们使用以下标记:
$S$: 主字符串($\text{"abaabaabaca"}$)
$T$: 模式字符串($\text{"abaabc"}$)
$i$: $S$ 字符串的当前匹配位置(指针)
$j$: $T$ 字符串的当前匹配位置(指针)
$LPS$ 数组: $T$ 模式字符串的最长公共前后缀数组。
首先,我们需要计算模式字符串 $T = \text{"abaabc"}$ 的 LPS 数组(或称 $next$ 数组):
$LPS = [0, 0, 1, 1, 2, 0]$
第一次匹配失败时:
$i$ 停留在 $S$ 中不匹配的位置:$i = 5$
$j$ 停留在 $T$ 中不匹配的位置:$j = 5$
当匹配失败时(在 $j=5$ 处失败),KMP 算法利用 $LPS$ 数组来确定 $T$ 模式字符串应该从哪个位置开始新的匹配,而主串指针 $i$ 不回溯。
计算 j 的新值:
新的 j 值由失败位置的前一个位置 j−1 的 LPS 值决定。
$$\text{新的 } j = LPS[j-1]$$
失败时的 $j = 5$
$j-1 = 4$
$LPS[4] = 2$
新的 $j = 2$
这意味着在 $T[0 \dots 4] = \text{"abaab"}$ 中,最长公共前后缀是 $\text{"ab"}$,长度为 2。我们已经知道 $S[i-j \dots i-1]$ 与 $\text{"abaab"}$ 匹配,所以现在我们知道 $S[i-2 \dots i-1]$ 已经和 $T[0 \dots 1]$ 匹配。因此,我们从 $T[2]$ ($\text{'a'}$) 处开始尝试匹配 $S[5]$ ($\text{'a'}$)。
确定 i 的值:
在 KMP 算法中,主串指针 i 在匹配失败时保持不变,继续指向 S 中发生不匹配的位置。
新的 $i = 5$
下一次匹配时:
$i = 5$
$j = 2$
对于字符串 "aaab",让我逐步计算其LPS数组:
LPS数组的定义:LPS[i] 表示子串 s[0...i] 的最长相等前后缀的长度(前缀和后缀不能是整个字符串本身)。
计算过程:
答案:
字符串: a a a b
索引: 0 1 2 3
LPS: 0 1 2 0
所以 "aaab" 的LPS数组是 [0, 1, 2, 0]
ababaaababaa
0 a 0
1 ab 0
2 aba 1
3 abab 2
4 ababa 3
5 ababaa 1
6 ababaaa 1
7 ababaaab 2
8 ababaaaba 3
9 ababaaabab 4
10 ababaaababa 5
11 ababaaababaa 1
LPS 的优化数组”通常指的是 KMP 算法中的 $next$ 数组的 改进/优化版本,也常被称为 $nextval$ 数组(或 $next'$ 数组)。
标准的 LPS 数组(或 $next$ 数组)虽然能防止主串回溯,但在某些极端情况下,模式串的回溯仍然可能发生多次无效比较。
缺陷示例:
假设模式串 T="aaaaab",其 LPS 数组为 [0,1,2,3,4,0]。
现在主串 $S$ 正在匹配,在 $T[5]=\text{'b'}$ 处失败(假设 $S[i]=\text{'c'}$)。
匹配失败于 $j=5$.
回溯到 $j = LPS[4] = 4$. ($T[4]=\text{'a'}$)
比较 $S[i]=\text{'c'}$ 和 $T[4]=\text{'a'}$。失败! (无效比较:$S[i]$ 仍然是一个新的不匹配字符,它不可能与 $T[4]$ 匹配,因为 $T[4]$ 和 $T[5]$ 字符相同都是 'a',而 $T[5]$ 已经失败了)
回溯到 $j = LPS[3] = 3$. ($T[3]=\text{'a'}$)
比较 $S[i]=\text{'c'}$ 和 $T[3]=\text{'a'}$。失败!
...直到 $j=0$ 失败。
这种情况下,每次回溯到的字符都与原失败字符 $T[5]$ 相同(都是 'a'),导致一系列冗余且注定失败的比较。
优化数组($nextval$ 数组) 的目标是消除这种冗余的比较。
定义:
对于 T 中的任何位置 j,nextval[j] 存储的是:
如果 $T[j] \ne T[nextval[j]]$,则 $nextval[j] = LPS[j]$ (即标准回溯位置)。
如果 $T[j] = T[nextval[j]]$,则 $nextval[j] = nextval[nextval[j]]$ (继续回溯,直到找到一个与 $T[j]$ 不同的字符位置 $k$)。
优化后的回溯逻辑:
当匹配在 T[j] 处失败时,模式串直接回溯到 T[nextval[j]] 处继续匹配。由于 T[nextval[j]]=T[j],可以确保回溯后的第一次比较不是冗余的。
T=a a a a a b
LPS=[0,1,2,3,4,0]
最终 $nextval$ 数组 (为简化,通常 $nextval[0]$ 设为 0): $[0, 0, 0, 0, 0, 0]$
优化后的回溯:
在 T[5]=’b’ 处失败,回溯到 j=nextval[5]=0.
直接比较 S[i]=’c’ 和 T[0]=’a’。只有一次比较,大大减少了冗余操作。
简而言之,LPS 的优化数组($nextval$ 数组)就是通过跳过那些与失败字符相同的公共前后缀,来提高 KMP 算法的实际执行效率。
!从KMP算法的实际使用角度来看,LPS数组的最后一位在模式匹配过程中确实不会被用到。
原因分析:
在KMP算法中,当我们在位置 j 匹配失败时:
j = lps[j-1]lps[j-1] 而不是 lps[j]由于 j 的最大值是 m-1(m是模式串长度),所以:
j = m-1 时,匹配失败会跳转到 lps[m-2]lps[m-1]但是 LPS 数组的最后一位仍然有其他用途:
lps[m-1] > 0 且 m % (m - lps[m-1]) == 0,说明字符串有周期总结:你的理解是正确的!在标准的KMP模式匹配中,LPS数组的最后一位不会在匹配过程中被访问到。
让我检查一下 "ababaaababaa" 是否满足周期性条件:
检查条件:
lps[m-1] > 0 ✓ (1 > 0)m % (m - lps[m-1]) == 0
12 % (12 - 1) == 012 % 11 == 0 ✗ (结果是1,不是0)结论:不适用!"ababaaababaa" 不满足周期性条件。
为什么? 这个字符串确实不是周期性的。让我们看看:
m - lps[m-1] = 12 - 1 = 11周期性的例子:
所以你的质疑是对的!我之前举的判断周期性的方法需要同时满足两个条件,而 "ababaaababaa" 只满足第一个条件,不满足第二个,因此它不是周期性字符串。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。