使用回溯的近似字符串匹配

时间:2011-09-26 14:54:40

标签: algorithm string-matching backtracking

我想使用回溯来搜索长字符串中的所有子字符串,允许可变长度匹配 - 这是允许最大给定数量的不匹配,插入和删除的匹配。我找不到任何有用的例子。我发现的最接近的是本文here,但这非常复杂。任何人吗?

干杯,

马丁

3 个答案:

答案 0 :(得分:3)

算法

下面的函数ff()使用递归(即回溯)来解决您的问题。基本思路是,在对f()的任何调用开始时,我们都会尝试将原始“needle”字符串的后缀t与“haystack”的后缀s进行匹配。字符串,同时只允许每种类型的编辑操作。

// ss is the start of the haystack, used only for reporting the match endpoints.
void f(char* ss, char* s, char* t, int mm, int ins, int del) {
    while (*s && *s == *t) ++s, ++t;    // OK to always match longest segment
    if (!*t) printf("%d\n", s - ss);    // Matched; print endpoint of match
    if (mm && *s && *t) f(ss, s + 1, t + 1, mm - 1, ins, del);
    if (ins && *s) f(ss, s + 1, t, mm, ins - 1, del);
    if (del && *t) f(ss, s, t + 1, mm, ins, del - 1);
}

// Find all occurrences of t starting at any position in s, with at most
// mm mismatches, ins insertions and del deletions.
void ff(char* s, char* t, int mm, int ins, int del) {
    for (char* ss = s; *s; ++s) {
//      printf("Starting from offset %d...\n", s - ss);
        f(ss, s, t, mm, ins, del);
    }
}

示例电话:

ff("xxabcydef", "abcdefg", 1, 1, 1);

输出:

9
9

因为有两种方法可以在“xxabcydef”中找到“abcdefg”,每种变化中最多只有1种,并且这两种方式都在第9位结束:

Haystack: xxabcydef-
Needle:     abc-defg

有1个插入(y)和1个删除(g),

Haystack: xxabcyde-f
Needle:     abc-defg

有1个插入(y),1个删除(f),1个g替换为f

优势关系

为什么使用第3行的while循环在两个字符串的开头贪婪地匹配尽可能多的字符,这可能并不明显。事实上,这可能会减少特定结束位置将被报告为匹配的的数量,但它永远不会导致完全忘记结束位置 - 因为我们通常对只是在大海捞针的给定位置是否有匹配结束,如果没有这个while循环,算法总是需要时间指数的针大小,这似乎是一个胜利 - 取胜。

由于支配关系,它可以保证正常工作。为了看到这一点,假设相反 - 它实际上是不安全的(即错过了一些匹配)。然后会有一些匹配,其中两个字符串中相等字符的初始段不相互对齐,例如:

Haystack: abbbbc
Needle:   a-b-bc

但是,任何此类匹配都可以转换为另一种匹配,每种类型的操作数相同,并且在相同位置结束,方法是在间隙左侧的间隙之后分流最左边的字符:

Haystack: abbbbc
Needle:   ab--bc

如果你反复这样做,直到它不再需要替换而无法分流字符,你将得到一个匹配,其中两个字符串中相同字符的最大初始段相互对齐:

Haystack: abbbbc
Needle:   abb--c

我的算法会找到所有这些匹配,因此不会忽略匹配位置。

指数时间

与任何回溯计划一样,此功能将对某些输入呈现指数减速。当然,可能在您碰巧使用的输入上,这种情况不会发生,并且它比DP算法的特定实现更快。

答案 1 :(得分:0)

我会从Levenshtein's distance算法开始,这是通过不匹配,插入和删除检查字符串相似性时的标准方法。

由于算法使用bottom up dynamic programming,您可能无需为每个潜在的子字符串执行算法就能找到所有子字符串。

答案 2 :(得分:0)

我知道的最好的算法是Gene Myers的A Fast Bit-Vector Algorithm for Approximate String Matching Based on Dynamic Programming。给定要搜索长度为n的文本,搜索长度为m的模式字符串以及最大错配/插入/删除次数k,此算法需要时间O(mn / w),其中w是计算机的字大小(32或64)。如果你对字符串上的算法了解很多,那么实际上存在一个独立于k的时间算法实际上是非常不可思议的 - 很长一段时间,这似乎是一个不可能实现的目标。

我不知道上述算法的现有实现。如果您需要工具,agrep可能正是您所需要的。它使用较早的算法,需要时间O(mnk / w),但在最坏的情况下,它在回溯搜索之前的低k英里足够快。

agrep基于Shift-Or (or "Bitap") algorithm,这是一个非常聪明的动态编程算法,可以将其状态表示为整数位并获取二进制加法来做更新状态的大多数工作,在更典型的实现中,这将使算法加速32或64倍。 :)迈尔斯的算法也使用这个想法得到它的1 / w速度因子。