【问题标题】:String pattern matching with one or zero mismatch匹配一个或零个不匹配的字符串模式
【发布时间】:2012-04-24 15:39:17
【问题描述】:

给定一个要匹配的字符串和一个模式,如何有效地找到零个或一个不匹配的匹配项。

e.g) 
S = abbbaaabbbabab
P = abab

Matches are abbb(index 0),aaab(index 4),abbb(index 6),abab(index 10)

我尝试修改 KMP 算法,但我不确定该方法。

请给我解决问题的想法。

谢谢。

【问题讨论】:

  • S和P的最大长度是多少?

标签: string algorithm pattern-matching string-matching knuth-morris-pratt


【解决方案1】:

好的,我找到了!我找到了最好的算法!

这听起来有点勇敢,但只要我要提出的算法同时具有运行时间O(m + n) 和内存消耗O(m + n) 并且条目数据本身具有相同的属性,那么算法只能在常数。

使用的算法

我将混合使用 KMP 和 Rabin Karp 算法来解决我的问题。 Rabin Karp 使用rolling hashes 比较初始字符串的子字符串。它需要使用线性附加内存的线性时间预计算,但从那时起,两个字符串的子字符串之间的比较是恒定的O(1)(如果您正确处理冲突,这将摊销)。

我的解决方案不能做什么

我的解决方案不会在第一个字符串中找到 all 匹配第二个字符串最多有 1 个差异的匹配项。但是,可以修改算法,以便对于第一个字符串中的每个起始索引,如果存在这样的匹配,则至少会找到其中一个(这留给读者)。

观察

m 是第二个字符串的长度,n - 第一个字符串的长度。我将把任务分成两部分:如果我的目标是找到最多有一个差异的匹配项,我想找到第一个字符串的子字符串:PREF 将成为单一差异之前的子字符串,并且SUFF 差后的子串。我想要len(PREF) + len(SUFF) + 1 = m,如果需要,PREFSUFF 将被人为缩短(当字符串匹配没有差异时)。

我的解决方案将基于一个非常重要的观察:假设第一个字符串的子字符串从索引i 开始,长度为m,与第二个字符串匹配,最多有一个差异。那么如果我们尽可能长时间地使用PREFSUFF 的解决方案仍然存在。这很明显:我只是尽可能地将差异推到最后。

算法

现在遵循算法本身。从通常的 KMP 开始。每次当前缀扩展失败并且要遵循失败链接时,首先检查是否跳过下一个字母剩余的后缀是否匹配第二个字符串的剩余部分。如果是这样,则找到最多具有一个字符差异的所寻求的匹配。如果不是 - 我们继续使用普通 KMP 进行 Rabin Karp 检查,每次要遵循失败链接。

让我通过一个示例进一步阐明 Rabin Karp 检查。假设我们在 KMP 的某个步骤,我们发现 first.substring[i, i + k - 1] 匹配第二个字符串的第一个 k 字母。还假设字母first[i + k]second[k] 不同。然后使用 Rabin Karp 检查 first.substring[i + k + 1, i + m - 1] 是否与 second.substring[k + 1, m - 1] 完全匹配。这正是您已尽可能扩展起始前缀形式索引i 并且您现在尝试是否存在最多一个差异的匹配的情况。

Rabin Karp 只有在跟随失败链接时才会使用,它将前缀的起始索引至少移动一个,这意味着最多使用O(n) Rabin Karp 调用,每个都具有复杂性O(1)总的线性复杂度。

【讨论】:

  • 我认为当 Text=adbca 和 pattern=eca 时此方法失败。答案应该是 Text[2,4] 但你描述的方法无法检测到它。如果我错了,请纠正我!!!
  • @ritesh_nitw 你错了。考虑 KMP。让它达到将 Text[2] 与模式的第一个字母进行比较的迭代。失败。所以它尝试使用 Rabin Karp 是否 Text[3,4] == pattern[1,2]。真的。所以它会返回你所期望的。
  • 对不起@Boris Strandjev,我现在明白了。我误读了 Rabin karp 的解释段落。我还要求您在解释中添加一个示例作为编辑,这将很有帮助,因为它是一个不常见且棘手的问题。
【解决方案2】:

这被称为approximate string matching 问题。在您的特定情况下,您希望最大编辑距离为 1。

bitap algorithm 是一种相当快速的解决方法。

【讨论】:

  • 我认为距离最多为 1 的事实应该允许更快的算法。
  • 我认为bitap算法的运行时间复杂度为O(mn)。有没有 O(m+n) 的算法。谢谢!
【解决方案3】:

要查找包括一个不匹配在内的所有子匹配,您需要 2 个 z 函数(一个用于原始 P,另一个用于反向 P)。 在原始和反向字符串 S 的最长前缀子匹配的 buld 数组之后。 稍后您需要反转第二个数组。 最后一切都很简单:遍历第一个数组并检查最长前缀的长度是否等于 P 的长度。如果是,那么它是一个没有错误的匹配。 如果它更短,则检查位置 (i + length(P) - 1) 处的第二个数组。如果总和 两个值等于length(P) - 1,那么它是一个错误的子匹配。

复杂度为 O(len(P) + len(S))

【讨论】:

    【解决方案4】:

    Gonzalo Navarro 在他的A guided tour to approximate string matching 中全面概述了各种算法以及它们之间的比较。第 80、81 和 82 页显示了复杂性结果,包括最坏和平均情况,以及各种算法的空间要求。

    (在此处使用的符号中,n 表示您搜索的文本的长度,m 表示模式的长度,σ 表示字母的大小,k 表示最大编辑距离,即1 在你的情况下。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-28
      • 1970-01-01
      • 1970-01-01
      • 2011-06-28
      相关资源
      最近更新 更多