有稀疏的编辑距离算法吗?

时间:2018-08-03 17:24:07

标签: algorithm levenshtein-distance

假设您有两个长度为100,000的字符串,其中包含零和一。您可以通过大约10 ^ 10个运算来计算其edit distance

如果每个字符串只有100个,其余为零,那么我可以使用100个整数表示每个字符串,表示它们在哪里。

  

有没有更快的算法可以使用以下方式计算编辑距离   这种稀疏的表示?更好的算法是也使用100 ^ 2空间而不是10 ^ 10空间。

要进行测试,请考虑这两个字符串,每个字符串十个。整数表明每个字符串在哪里。

[9959, 10271, 12571, 21699, 29220, 39972, 70600, 72783, 81449, 83262]

[9958, 10270, 12570, 29221, 34480, 37952, 39973, 83263, 88129, 94336]

用算法的术语来说,如果我们有两个长度分别为n的{​​{1}}稀疏二进制字符串,那么是否存在k时间编辑距离算法?

1 个答案:

答案 0 :(得分:9)

当然!有太多0的可能操作很少。我的意思是,答案最多为200。

看看

10001010000000001
vs       ||||||
10111010100000010

用管道查看所有零。您最终删除的那一个有关系吗?不。那是关键。


解决方案1 ​​

让我们考虑正常的n * m解决方案:

dp(int i, int j) {
    // memo & base case
    if( str1[i-1] == str1[j-1] ) {
        return dp(i-1, j-1);
    }
    return 1 + min( dp(i-1, j), dp(i-1, j-1), dp(i, j-1) );
}

如果几乎​​每个字符都为0,那么最多的时间会花在什么地方?

if( str1[i-1] == str1[j-1] ) { // They will be equal so many times, (99900)^2 times!
    return dp(i-1, j-1);
}

我可以想象一下,在数以万计的递归中滴滴答答。您实际需要的逻辑只是200个关键点。您可以忽略其余部分。一个简单的修改就是

if( str1[i-1] == str1[j-1] ) {
    if( str1[i-1] == 1 )
        return dp(i-1, j-1); // Already hit a critical point

    // rightmost location of a 1 in str1 or str2, that is <= i-1
    best = binarySearch(CriticalPoints, i-1);
    return dp(best + 1, best + 1); // Use that critical point
    // Important! best+1 because we still want to compute the answer at best
    // Without it, we would skip over in a case where str1[best] is 1, and str2[best] is 0.
}

CriticalPoints是包含str1或str2中每1的索引的数组。在二进制搜索之前,请确保已对它进行了排序。请记住那些gochya的。我的逻辑是:好的,我需要确保在索引best本身上计算答案,所以我们以best + 1作为参数。但是,如果best == i - 1,我们将陷入循环。我将通过快速str1[i-1] == 1检查来解决这一问题。做完了,。

您可以通过以下方法快速检查正确性:注意在最坏的情况下,您会击中构成临界点的i *和j的所有200 * 100000组合,并且当这些临界点调用min(a, b, c)时,它只会使三个递归函数调用。如果这些功能中的任何一个是关键点,那么它就是我们已经计算的200 * 100000的一部分,我们可以忽略它。如果不是,则在O(log(200))中它会落入另一个关键点的单个调用中(现在,这是我们已经知道的200 * 100000的一部分,我们知道)。因此,每个关键点花费的时间最差3*log(200),不包括对其他关键点的呼叫。同样,第一个函数调用将在log(200)时间内陷入临界点。因此,我们的上限为O(200 * 100000 * 3 * log(200)+ log(200))。

此外,请确保您的备忘录表是哈希表,而不是数组。 10 ^ 10的内存将不适合您的计算机。


解决方案2

您知道答案最多为200,因此请防止自己进行过多的深度运算。

dp(int i, int j) { // O(100000 * 205), sounds good to me.
    if( abs(i - j) > 205 )
        return 205; // The answer in this case is at least 205, so it's irrelevant to calculating the answer because when min is called, it wont be smallest.
    // memo & base case
    if( str1[i-1] == str1[j-1] ) {
        return dp(i-1, j-1);
    }
    return 1 + min( dp(i-1, j), dp(i-1, j-1), dp(i, j-1) );
}

这是一个非常简单的方法,但我将其留给解决方案二,因为该解决方案似乎是凭空提出的,而不是分析问题并弄清慢零件的位置以及如何对其进行优化。不过,请保留在工具箱中,因为您应该对此解决方案进行编码。


解决方案3

就像解决方案2一样,我们可以这样做:

dp(int i, int j, int threshold = 205) {
    if( threshold == 0 )
        return 205;
    // memo & base case
    if( str1[i-1] == str1[j-1] ) {
        return dp(i-1, j-1);
    }
    return 1 + min( dp(i-1, j, threshold - 1), dp(i-1, j-1, threshold - 1), dp(i, j-1, threshold - 1) );
}

您可能会担心dp(i-1,j-1)滴落,但是阈值使i和j靠得很近,因此它计算了解决方案2的子集。这是因为每次i和j时,阈值都会递减。 j分开得更远。 dp(i-1, j-1, threshold)将使其与解决方案2相同(因此,该解决方案要快一些)。


空格

这些解决方案将很快为您提供答案,但是,如果您还想要优化空间的解决方案,则可以使用哈希图将str1[i]替换为(i in Str1CriticalPoints) ? 1 : 0。这将提供最终的解决方案,该解决方案仍然非常快(尽管速度会慢10倍),而且避免将长字符串保留在内存中(以至于它可以在Arduino上运行)。我认为这不是必需的。

请注意,原始解决方案使用10 ^ 10的空间。您提到“更好,少于10 ^ 10的空间”,这意味着10 ^ 10的空间是可以接受的。不幸的是,即使有足够的RAM,迭代该空间仍需要10 ^ 10的时间,这绝对是不可接受的。我的解决方案都不使用10 ^ 10的空间:仅2 * 10 ^ 5来容纳字符串-如上所述,可以避免这种情况。 10 ^ 10字节,相当于10 GB。


编辑:如maniek所言,您只需要检查abs(i - j) > 105,因为将ij等同的其余100次插入将使操作数超过200。