快速k不匹配字符串匹配算法


10

我正在寻找一种快速的k不匹配字符串匹配算法。给定长度为m的模式字符串P和长度为n的文本字符串T,我需要一种快速(线性时间)算法来查找P匹配T的子字符串(最多不匹配k个)的所有位置。这与k差问题(编辑距离)不同。不匹配意味着子字符串和模式在最多k个位置具有不同的字母。我真的只需要k = 1(最多1个不匹配),因此针对k = 1的特定情况的快速算法也足够了。字母的大小为26(不区分大小写的英文文本),因此空间要求不应随字母的大小而增长太快(例如,我相信FAAST算法占用的字母空间大小是指数的,因此仅适用于蛋白质和基因序列)。

在最坏的情况下,基于动态编程的方法将趋于O(mn),这将太慢。我相信对此有Boyer-Moore算法的修改,但是我无法获得此类论文。我没有订阅访问学术期刊或出版物的权限,因此任何参考文献都必须在公共领域。

我将不胜感激任何指针,或指向免费文档的引用,或针对此问题的算法本身。


2
如果模式是固定的(但要匹配的文本有所不同),则可以潜在地创建一个有限的自动机并通过它运行文本。还有一些使用后缀树的算法(如果文本恒定且模式不同,通常会很好,但如果两者均不同,也可以应用),您也许可以在网上找到一些参考。(尚未添加答案,因为我不太确定基于后缀树的算法,如果有人知道,请随时忽略此评论)。
Aryabhata 2012年

@Aryabhata谢谢!样式和文本都会更改。在这种情况下,建立有限的自动机将太昂贵,尤其是在包括1个不匹配的范围时。至于后缀树/后缀数组,我从未使用过它们,并且对它们知之甚少,但给人的印象是它们的构建速度很慢,并且主要用于精确匹配。但我将进一步探讨该选项。朝这个方向或任何其他方向的任何指针都是最有用的!
Paresh 2012年

1
不,后缀树也可以用于近似匹配。维基至少这么说:en.wikipedia.org/wiki/Suffix_tree
Aryabhata

Answers:


5

后缀数组可以用于此问题。它们包含按字典顺序排序的字符串的每个后缀的起始位置。即使可以以复杂度天真地构造它们,也有一些以复杂度构造它们的方法。例如参见thisthis。让我们将此后缀数组称为SA。Θ n O(nlogn)Θ(n)

构造后缀数组后,我们需要为后缀数组构造一个最长公共前缀(LCP)数组。LCP数组存储后缀数组(按字典顺序的后缀)中两个连续前缀之间最长的公共前缀的长度。因此,LCP [i]包含SA [i]和SA [i + 1]之间最长的公共前缀的长度。该数组也可以线性时间构造:请参见此处此处此处以获取一些参考。

现在,要计算后缀树中任何两个后缀(而不是连续的后缀)所共有的最长前缀的长度,我们需要使用一些RMQ数据结构。上面的参考文献已经显示了这一点(如果将数组可视化为后缀树,则很容易看到),即后缀数组中位置为和()的两个后缀之间的最长公共前缀的长度可以作为。一个好的RMQ可以预先处理阵列中或时间和响应形式的查询在v u < v m i n u < = k < = v 1 L C P [ k ] L C P O n O n log n L C P [ u v ] O 1 uvu<vminu<=k<=v1LCP[k]LCPO(n)O(nlogn)LCP[u,v]O(1)时间。见这里的succint RMQ算法,并在这里对RMQ的一个很好的教程,和LCA和RMQs之间的关系(及还原)。是另一种不错的替代方法。

利用此信息,我们构造了后缀数组和相关数组(如上所述),用于两个字符串之间的分隔符(例如T#P,其中两个字符串中均不出现“#”)。然后,我们可以使用“袋鼠”方法执行k个不匹配字符串匹配。说明中后缀树的上下文中,袋鼠方法,而是可以直接施加到后缀阵列太。对于每一个索引的文本的,发现的后缀的开始在和后缀从0开始。这给出了在此之后,第一个不匹配时发生匹配的位置T L C P T i P P T [ i ] l 0 T P L C P T [ i + l 0 + 1 ] P [ l 0 + 1 ] k L C P O 1 O k L C P ŤiTLCPTiPP与。将该长度设为。跳过和不匹配的字符,然后尝试匹配其余的字符串。即,再次发现的和。重复此过程,直到获得不匹配项,或者任一字符串完成。每个为。有的每个索引的,这给人的总的复杂性。T[i]l0TPLCPT[i+l0+1]P[l0+1]kLCPO(1)O(k) LCPiTO(nk)

我使用了一种更容易实现的RMQ来实现总复杂度或如果,但是它如上所述,也可以在完成。可能还有其他直接方法可以解决此问题,但这是一种功能强大且通用的方法,可以应用于许多类似的问题。O(nk+(n+m)log(n+m))O(nk+nlogn)m=O(n)O(nk)


大!我现在在我的待办事项列表上有读物:-)
Aryabhata '10年

第二段中的siam.org链接已损坏,但可以在此处找到链接的论文epubs.siam.org/doi/pdf/10.1137/1.9781611972917.3
leecbaker

4

以下是预期的算法(可以扩展到其他,使其成为)。(不过,我没有进行计算来证明是这样)。O(n+m)kO(nk+m)

这个想法类似于Rabin-Karp滚动哈希算法,用于精确的子字符串匹配。

想法是将每个长度为字符串分成个大小为块,并计算每个块的滚动哈希(给出哈希值),并将这些哈希值与模式中的值进行比较。m2km/2k2k2k

在这些值中,我们最多允许不匹配。k

如果发生不匹配,我们将拒绝并继续。否则,我们尝试确认近似匹配。k

我期望(注意:自己没有尝试过)与使用基于后缀树的方法相比,这可能会在实践中更快,并且可能更易于编码/维护。


只需要澄清一下。“将每个长度为m的字符串分成2k个块,每个块的m / 2k大小...”是指将T中长度为m的每个子串(长度为n)分成2k个块。并且可以通过滚动哈希方法在O(n)中计算此哈希。然后,模式字符串也将分为2k个块,并且将比较相应的哈希,从而允许最多k个块不匹配。如果是这样,那么我们将有可能丢弃所有不匹配数大于k的情况。我明白吗?
Paresh 2012年

@Paresh:是的,您做对了,除了因为有哈希,它是,而不是。Ω Ñ ķ ø Ñ kΩ(nk)O(n)
Aryabhata 2012年

我喜欢这种方法!但是,这种方法通常很快,但是如果匹配数很高(O(n)个匹配),则会降级为O(mnk)。牢记这一点,我假设两个滚动散列不能在同一个输入发生冲突的情况下进行(我不做数学运算,因为我想观察速度)。这样,如果两个哈希值一致,我们就不必逐个字符验证匹配。通常,这相当快,但是如果匹配数目很大,这也很慢。通过这种方式和您建议的方式,大型比赛的速度很慢。
Paresh 2012年

在最坏的情况下,如果将文本分成大小的块而不是块,则可以使速度更快。该模式还将划分为(如果不是完美的正方形,则为+1)块,我们将比较每个块。如果不匹配的数目很小,这将比您的方法慢,但是我认为最坏的情况下,它应该仅为(尽管我没有正确检查)。我还没有尝试过,但是我将首先按照您的建议探索后缀树/数组。他们似乎提供了很好的界限。谢谢!/2ķmm/2k øÑķmO(nkm)
Paresh 2012年

@Paresh:您看不到它(在修订历史中),但是我最初使用方法,但是将其更改为当前方法。我认为使用更好。您不必要地计算了太多的哈希值。当然,哪个更好取决于您的数据。当然,除了,您还可以尝试或等。顺便说一句,对于和方法,最坏的情况是 .../2ķ2ķķ+1ķ+ÇΩÑmm/2k2kk+1k+cΩ(nm) m/2kmm/2k
Aryabhata
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.