Levenshtein与特定数字组的距离

时间:2012-12-13 18:35:02

标签: algorithm

我的输入是三个数字 - s的数字b以及e范围的结束0 <= s,b,e <= 10^1000。任务是找到s与范围[b, e]中所有数字之间的最小Levenstein距离。没有必要找到最小化距离的数字,最小距离就足够了。

显然我必须将数字作为字符串读取,因为标准C ++类型不会处理如此大的数字。计算可能在很大范围内的每个数字的Levenstein距离是不可行的。

有什么想法吗?

2 个答案:

答案 0 :(得分:3)

[编辑10/8/2013:DP算法中考虑的一些案例实际上并不需要考虑,但考虑到它们不会导致错误:)] < / p>

在下文中,我描述了一个采用O(N ^ 2)时间的算法,其中N是b,e或s中任何一个中数字的最大数量。由于所有这些数字都限制在1000位,这意味着最多只有几百万个基本操作,在任何现代CPU上都需要毫秒

假设s有n位数。在下面,&#34;之间&#34;意味着&#34;包容性&#34 ;;我会严格地说&#34;&#34;如果我的意思是&#34;排除其端点&#34;。指数以1为基础。 x [i]表示x的第i位,例如x [1]是它的第一个数字。

解决问题

首先要做的是将问题分解为一系列子问题,其中每个b和e具有相同的位数。假设e的k> = 0比s更多的数字:将问题分解为k + 1个子问题。例如。如果b = 5且e = 14032,则创建以下子问题:

  • b = 5,e = 9
  • b = 10,e = 99
  • b = 100,e = 999
  • b = 1000,e = 9999
  • b = 10000,e = 14032

我们可以解决这些子问题,并采用最小的解决方案。

简单案例:中间

简单的案例就是中间的案例。每当e具有比b更多的k> = 1的数字时,将存在k-1个子问题(例如上面的3个),其中b是10的幂,e是10的下一个幂,减1.假设b是10 ^米注意,选择1到9之间的任何数字,然后是0到9之间的任何m个数字,产生的数字x在b <= x <= e的范围内。此外,在这个范围内没有数字不能以这种方式生产。 s之间的最小Levenshtein距离(或实际上任何给定的长度为n的数字字符串,不以0开头)和任何数字x在10 ^ m <= x&lt的范围内; = 10 ^(m + 1)-1必然是abs(m + 1-n),因为如果m + 1> = n,则可以简单地选择x的前n个数字是相同的作为s中的那些,并删除余数,如果m + 1&lt;然后选择第一个m + 1与s中的相同,并插入余数。

事实上,我们可以在一次固定时间操作中处理所有这些子问题:如果最小的&#34; easy&#34;子问题有b = 10 ^ m,最大的&#34; easy&#34;子问题具有b = 10 ^ u,则s与任何这些范围中的任何数之间的最小Levenshtein距离是m-n,如果n <1。 m,n-u如果n>你,否则为0。

困难案例:结束

硬情况是当b和e不限于分别具有b = 10 ^ m和e = 10 ^(m + 1)-1的形式时。任何主要问题都可以产生这样的最多两个子问题:两个&#34;结束&#34; (由主要问题产生,其​​中b和e具有不同的数字位数,例如顶部的示例)或单个子问题(即主要问题本身,其根本不需要细分,因为b并且e已经有相同的位数)。请注意,由于先前分解问题,我们可以假设子问题的b和e具有相同的位数,我们称之为m。

超级莱文斯坦!

我们要做的是设计一个Levenshtein DP矩阵的变体,用于计算给定数字串与任何数x之间的最小Levenshtein距离,其范围为b <= x&lt; ; = e。尽管添加了#34; power&#34;,算法仍将在O(n ^ 2)时间运行:)

首先,观察如果b和e具有相同的位数并且b!= e,那么它必须是由左边的相同数字的一些数字q> = 0组成的情况,接着是e中的数字大于b中的数字。现在考虑以下用于生成随机数字串x的过程:

  1. 将x设为b的前q位数。
  2. 在b [i]和e [i]之间附加一个随机选择的数字d到x。
  3. 如果d == b [i],我们&#34;拥抱&#34;下限:
    • 对于i从q + 1到m:
      • 如果b [i] == 9则附加b [i]。 [编辑10/8/2013:实际上这不可能发生,因为我们选择q使e [i]大于b [i],并且没有大于9的数字!] < /强>
      • 否则,翻硬币:
        • 负责人:附加b [i]。
        • 尾巴:附加随机选择的数字d&gt; b [i],然后转到6。
    • 停止。
  4. 如果d == e [i],我们&#34;拥抱&#34;上限:
    • 对于i从q + 1到m:
      • 如果e [i] == 0则附加e [i]。 [编辑10/8/2013:实际上这不会发生,因为我们选择q使得b [i]会小于e [i],并且没有小于0的数字!] < /强>
      • 否则,翻硬币:
        • 负责人:追加e [i]。
        • 尾巴:附加随机选择的数字d&lt; e [i],然后转到6。
    • 停止。
  5. 否则(如果d严格在b [i]和e [i]之间),请转到步骤6.
  6. 将随机选择的数字附加到x,直到它有m位数。
  7. 基本思路是,在包含您必须包含的所有数字后,您可以拥抱&#34;拥抱&#34;只要你想要的下限数字,或者#34;拥抱&#34;您想要的上限数字,一旦您决定停止&#34;拥抱&#34;,您可以随后选择您想要的任何数字。对于合适的随机选择,该过程将生成所有且仅生成数字x,使得b <= x <= e。

    在&#34;通常&#34; Levenshtein距离计算两个字符串s和x之间,长度分别为n和m,我们有一个从(0,0)到(n,m)的矩形网格,在每个网格点(i,j)我们记录Levenshtein距离在前缀s [1..i]和前缀x [1..j]之间。 (i,j)的得分是使用自下而上动态编程从(i-1,j),(i,j-1)和(i-1,j-1)的得分计算的。为了使其适应x作为一组可能的字符串之一(具体而言,对应于b和e之间的数字的数字字符串)而不是特定的给定字符串,我们需要做的是记录每个网格点不是一个而是两个分数:一个是我们假设选择位置j的数字来拥抱下限的情况,另一个是我们假设选择拥抱上限的情况。第三种可能性(上面的步骤5)实际上并不需要DP矩阵中的空间,因为我们可以立即计算出输入字符串的其余部分的最小Levenshtein距离,非常类似于我们为其工作的方式。 &#34;易&#34;第一部分的子问题。

    Super-Levenshtein DP递归

    在网格点(i,j)v(i,j)处调用总体最小分数。如果字符a和b不同,则设diff(a,b)= 1,否则设为0。如果字符a在b..c范围内,则将inrange(a,b..c)设为1,否则设为0。计算结果如下:

    # The best Lev distance overall between s[1..i] and x[1..j]
    v(i, j) = min(hb(i, j), he(i, j))
    
    # The best Lev distance between s[1..i] and x[1..j] obtainable by
    # continuing to hug the lower bound
    hb(i, j) = min(hb(i-1, j)+1, hb(i, j-1)+1, hb(i-1, j-1)+diff(s[i], b[j]))
    
    # The best Lev distance between s[1..i] and x[1..j] obtainable by
    # continuing to hug the upper bound
    he(i, j) = min(he(i-1, j)+1, he(i, j-1)+1, he(i-1, j-1)+diff(s[i], e[j]))
    

    在计算v(i,j)的时间点,我们还将计算Levenshtein距离,这是因为选择&#34;停止拥抱&#34;,即通过选择严格介于两者之间的数字b [j]和e [j](如果j == q)或(如果j!= q)高于b [j]或低于e [j],此后自由选择数字使x的后缀匹配s的后缀尽可能接近:

    # The best Lev distance possible between the ENTIRE STRINGS s and x, given that
    # we choose to stop hugging at the jth digit of x, and have optimally aligned
    # the first i digits of s to these j digits
    sh(i, j) = if j >= q then shc(i, j)+abs(n-i-m+j)
               else infinity
    
    shc(i, j) = if j == q then
                  min(hb(i, j-1)+1, hb(i-1, j-1)+inrange(s[i], (b[j]+1)..(e[j]-1)))
                else
                  min(hb(i, j-1)+1, hb(i-1, j-1)+inrange(s[i], (b[j]+1)..9),
                      he(i, j-1)+1, he(i-1, j-1)+inrange(s[i], (0..(e[j]-1)))
    

    shc(i,j)的公式不需要考虑&#34;向下&#34;移动,因为这些移动不涉及x的任何数字选择。

    总的最小Levenshtein距离是v(n,m)和sh(i,j)的最小值,对于所有0 <= i <= n且0 <= j <= m。

    复杂性

    将N设为s,b或e中任意一个的最大位数。原始问题可以在线性时间内分成最多1组简单问题,这些问题共同花费O(1)时间来解决,2个硬子问题每个需要O(N ^ 2)时间来使用超Levenshtein算法求解,总的来说,问题可以在O(N ^ 2)时间内解决,即时间与数字的平方成正比。

答案 1 :(得分:0)

加速计算的第一个想法(如果|e-b|不太大,则有效):

  • 问题:当我们将sn然后与n+1进行比较时,Levestein距离会改变多少?

  • 答案:不要太多!

让我们看一下s = 12007和两个连续n的动态规划表

n = 12296

0 1 2 3 4 5 
1 0 1 2 3 4 
2 1 0 1 2 3 
3 2 1 1 2 3 
4 3 2 2 2 3 
5 4 3 3 3 3 

n = 12297

0 1 2 3 4 5 
1 0 1 2 3 4 
2 1 0 1 2 3 
3 2 1 1 2 3 
4 3 2 2 2 3 
5 4 3 3 3 2 

正如您所见,只有最后一列更改,因为nn+1的数字相同,但最后一列除外。

如果您拥有s = 12001n = 12296编辑距离的动态编程表,那么您已经拥有了n = 12297的表格,您只需要更新最后一列!

显然,如果n = 12299然后n+1 = 12300并且您需要更新上一个表的最后3列..但这种情况每100次迭代就会发生一次。

一般来说,你必须

  • 更新每次迭代的最后一列(因此,长度(s)单元格)
  • 同样更新倒数第二次,每10次迭代一次
  • 每隔100次迭代更新第三个也是最后一次

所以请L = length(s)D = e-b。首先,您计算sb之间的编辑距离。然后,您可以找到在[b,e]上的最小Levenstein距离,该距离在区间中的每个整数上循环。其中有D个,因此执行时间大约为:

enter image description here

现在

enter image description here

我们有一个算法

enter image description here