有效地计算两个字符串之间的编辑距离



我有一个长度为1000的字符串S和长度为100的查询字符串Q。我想计算查询字符串Q与长度为100的字符串S的每个子字符串的编辑距离。一种简单的方法是动态计算每个子串的编辑距离,即edDist(q,s[0:100]), edDist(q,s[1:101]), edDist(q,s[2:102]) .......edDist(q,s[900:1000])

def edDist(x, y):
""" Calculate edit distance between sequences x and y using
    matrix dynamic programming.  Return distance. """
D = zeros((len(x)+1, len(y)+1), dtype=int)
D[0, 1:] = range(1, len(y)+1)
D[1:, 0] = range(1, len(x)+1)
for i in range(1, len(x)+1):
    for j in range(1, len(y)+1):
        delt = 1 if x[i-1] != y[j-1] else 0
        D[i, j] = min(D[i-1, j-1]+delt, D[i-1, j]+1, D[i, j-1]+1)
return D[len(x), len(y)]

谁能提出一种有效计算编辑距离的替代方法?我的看法是,我们知道edDist(q,s[900:1000])。我们能不能用这些知识来计算edDist[(q,s[899:999])]…因为我们只有1个字符的差异,然后使用先前计算的编辑距离向后进行edDist[(q,s[1:100])] ?

提高空间复杂度

使Levenshtein距离算法更有效的一种方法是减少计算所需的内存量。

要使用整个矩阵,需要使用O(n * m)内存,其中n表示第一个字符串的长度,m表示第二个字符串的长度。

如果你仔细想想,我们真正关心的矩阵的部分是我们要检查的最后两列——前一列和当前列。

知道了这个,我们可以假装我们有一个矩阵,但实际上只创建了这两列;在需要更新数据时重写数据

我们只需要两个大小为n + 1的数组:

var column_crawler_0 = new Array(n + 1);
var column_crawler_1 = new Array(n + 1);

初始化这些伪列的值:

for (let i = 0; i < n + 1; ++i) {
  column_crawler_0[i] = i;
  column_crawler_1[i] = 0;
}

然后执行常规算法,但要确保在执行过程中用新值更新了这些数组

for (let j = 1; j < m + 1; ++j) {
  column_crawler_1[0] = j;
  for (let i = 1; i < n + 1; ++i) {
    // Perform normal Levenshtein calculation method, updating current column
    let cost = a[i-1] === b[j-1] ? 0 : 1;
    column_crawler_1[i] = MIN(column_crawler_1[i - 1] + 1, column_crawler_0[i] + 1, column_crawler_0[i - 1] + cost);
  }
  // Copy current column into previous before we move on
  column_crawler_1.map((e, i) => {
    column_crawler_0[i] = e;
  });
}
return column_crawler_1.pop()

如果您想进一步分析这种方法,我使用这种特定的技术编写了一个小的开源库,如果您感兴趣,请随意查看。

提高时间复杂度

没有非平凡的方法来改进Levenshtein距离算法,使其比O(n^2)执行得更快。有一些复杂的方法,一个使用VP-Tree数据结构。如果你好奇的话,这里有一些很好的来源,这些方法可以达到O(nlgn)的渐近速度。

最新更新