【发布时间】:2012-11-07 14:34:38
【问题描述】:
谁能给我解释一下?我一直在阅读它,但仍然很难理解。
文字:ababdbaababa
图案:阿巴巴
ababa 的表格是 -1 0 0 1 2。
我想我了解表格的构造方式,但是,一旦发生不匹配,我不明白如何转移。换班的时候好像连桌子都不用?
我们什么时候使用这张桌子?
【问题讨论】:
标签: string algorithm pattern-matching
谁能给我解释一下?我一直在阅读它,但仍然很难理解。
文字:ababdbaababa
图案:阿巴巴
ababa 的表格是 -1 0 0 1 2。
我想我了解表格的构造方式,但是,一旦发生不匹配,我不明白如何转移。换班的时候好像连桌子都不用?
我们什么时候使用这张桌子?
【问题讨论】:
标签: string algorithm pattern-matching
这里我已经简要描述了计算前缀函数并在此处切换文本。
更多信息:Knuth–Morris–Pratt string search algorithm
在文本中移动:
Text: ABC ABCDAB ABCDABCDABDE
Pattern : ABCDABD
场景 1 - Pattern 和 Text 中有一些匹配的字符。
例如 1:这里有 3 个匹配字符。
从表中获取 3 个字符的值。 (索引 2,ABC)即 0
因此 shift = 3 - 0 即 3
例如 2:这里有 6 个匹配字符。
从表中获取 6 个字符的值。 (索引 5,ABCDAB)即 2 因此 shift = 6 - 2 即 4
场景 2 - 如果没有匹配的字符,则移动一位。
【讨论】:
当您发生不匹配时使用该表。让我们将模式应用于您的文本:
您开始将文本与模式匹配并测试您的模式是否可以在文本中,从第一个位置开始。您将text[1] 与pattern[1] 进行比较,结果证明是匹配的。您对 text[2]、text[3] 和 text[4] 执行相同操作。
当您想将 text[5] 与 pattern[5] 匹配时,您没有匹配 (da)。然后您就知道您的模式不会从第一个位置开始。然后,您可以为位置 2 重新开始匹配,但这效率不高。您现在可以使用该表格。
错误发生在pattern[5],因此您转到table[5],即 2。这告诉您可以在当前位置再次开始匹配 2 个已经匹配的字符。不必从匹配位置 2 开始,您可以从之前的位置 (1) + table[5] (2)=3 开始。事实上,如果我们查看text[3] 和text[4],我们会看到它分别等于pattern[1] 和pattern[2]。
表中的数字告诉您发生错误时已经匹配了多少个位置。在这种情况下,下一个模式的 2 个字符已经匹配。然后您可以立即开始匹配位置 3 并跳过位置 2(因为从位置 [2] 开始找不到模式)。
【讨论】:
嗯,这是一个老话题,但希望将来搜索此内容的人会看到它。上面给出的答案很好,但我自己通过一个例子来看看到底发生了什么。
说明的第一部分取自wiki,我真正想详细说明的部分是这个回溯数组是如何构造的。
这里是:
我们通过算法的(相对人为的)运行来工作,其中
W = "ABCDABD" and
S = "ABC ABCDAB ABCDABCDABDE".
在任何给定时间,算法都处于由两个整数确定的状态:
m 表示 S 中的位置,它是 W 预期匹配的开始
i W 中的索引表示当前正在考虑的字符。
在每个步骤中,我们将S[m+i] 与W[i] 进行比较,如果它们相等则前进。这是在运行开始时描绘的,例如
1 2
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456
我们将 W 的连续字符与 S 的“平行”字符进行比较,如果它们匹配,则从一个字符移动到下一个字符。然而,在第四步中, 我们得到 S[3] 是一个空格,而 W[3] = 'D' 是一个不匹配。我们没有在 S[1] 处再次开始搜索,而是注意到在 S 中的位置 0 和 3 之间没有出现“A” 0除外;因此,在之前检查了所有这些字符后,我们知道如果再次检查它们,就不可能找到匹配的开头。 因此我们继续下一个字符,设置 m = 4 和 i = 0。
1 2
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456
当在 W[6] (S[10]) 处再次出现差异时,我们很快获得了几乎完全匹配的“ABCDAB”。然而,就在当前部分结束之前 比赛中,我们传递了一个“AB”,它可能是新比赛的开始,所以我们必须考虑到这一点。我们已经知道这些字符匹配 当前位置之前的两个字符,我们不需要再检查它们;我们只需重置 m = 8, i = 2 并继续匹配当前字符。因此, 我们不仅省略了 S 的先前匹配的字符,而且还省略了 W 的先前匹配的字符。
1 2
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456
这个搜索立即失败,然而,因为模式仍然不包含空格,所以在第一次试验中,我们回到 W 的开头并开始 搜索 S 的下一个字符:m = 11,重置 i = 0。
1 2
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456
我们再次立即找到匹配“ABCDAB”,但下一个字符“C”与单词 W 的最后一个字符“D”不匹配。推理如前, 我们设置m = 15,从两个字符串“AB”开始直到当前位置,设置i = 2,从当前位置继续匹配。
1 2
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456
这次我们可以完成匹配,第一个字符是S[15]。
上面的例子包含了算法的所有元素。目前,我们假设存在一个“部分匹配”表 T,如下所述,它 指示如果当前匹配以不匹配结束,我们需要在哪里寻找新匹配的开始。 T 的条目是这样构造的 如果我们有一个从 S[m] 开始的匹配在比较 S[m + i] 和 W[i] 时失败,那么下一个可能的匹配将从 S 中的索引 m + i - T[i] 开始(即 T[i] 是我们在不匹配后需要做的“回溯”量)。这有两个含义:首先,T[0] = -1,这表明如果 W[0] 是不匹配的, 我们不能回溯,必须简单地检查下一个字符;其次,尽管下一个可能的匹配将从索引 m + i - T[i] 开始,如示例中所示 上面,我们实际上不需要检查任何 T[i] 字符,因此我们继续从 W[T[i]] 搜索。
回溯阵列构造:
所以这个回溯数组T[]我们将调用lps[],让我们看看我们如何计算这个家伙
lps[i] = the longest proper prefix of pat[0..i]
which is also a suffix of pat[0..i].
示例: 对于“AABAACAABAA”模式,
lps[] is [0, 1, 0, 1, 2, 0, 1, 2, 3, 4, 5]
//所以只是快速浏览一下
lps[0] is just 0 by default
lps[1] is 1 because it's looking at AA and A is both a prefix and suffix
lps[2] is 0 because it's looking at AAB and suffix is B but there is no prefix equal to B unless you count B itself which I guess is against the rules
lps[3] is 1 because it's looking at AABA and first A matches last A
lps[4] is 2 becuase it's looking at AABAA and first 2 A matches last 2 A
lps[5] is 0 becuase it's looking at AABAAC and nothing matches C
...
For the pattern “ABCDE”, lps[] is [0, 0, 0, 0, 0]
For the pattern “AAAAA”, lps[] is [0, 1, 2, 3, 4]
For the pattern “AAABAAA”, lps[] is [0, 1, 2, 0, 1, 2, 3]
For the pattern “AAACAAAAAC”, lps[] is [0, 1, 2, 0, 1, 2, 3, 3, 3, 4]
如果你仔细想想这完全有道理......如果你不匹配,你想尽可能地回溯很明显,你回溯了多远(后缀 part) 本质上是前缀,因为您必须根据定义再次从第一个字符开始匹配。所以如果你的字符串看起来像
aaaaaaaaaaaaaaa..b..aaaaaaaaaaaaaaac 并且你在最后一个字符 c 上不匹配,那么你想重用 aaaaaaaaaaaaaaa 作为你的新头,仔细考虑一下
【讨论】:
使用 Java 的完整解决方案:
package src.com.recursion;
/*
* This Expains the Search of pattern in text in O(n)
*/
public class FindPatternInText {
public int checkIfExists(char[] text, char[] pattern) {
int index = 0;
int[] lps = new int[pattern.length];
createPrefixSuffixArray(pattern, lps);
int i = 0;
int j = 0;
int textLength = text.length;
while (i < textLength) {
if (pattern[j] == text[i]) {
j++;
i++;
}
if (j == pattern.length)
return i - j;
else if (i < textLength && pattern[j] != text[i]) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return index;
}
private void createPrefixSuffixArray(char[] pattern, int[] lps) {
lps[0] = 0;
int index = 0;
int i = 1;
while (i < pattern.length) {
if (pattern[i] == pattern[index]) {
lps[i] = index;
i++;
index++;
} else {
if (index != 0) {
index = lps[index - 1];
} else {
lps[i] = 0;
i++;
}
}
}
}
public static void main(String args[]) {
String text = "ABABDABACDABABCABAB";
String pattern = "ABABCABAB";
System.out.println("Point where the pattern match starts is "
+ new FindPatternInText().checkIfExists(text.toCharArray(), pattern.toCharArray()));
}
}
【讨论】: