高效计算两个字符串之间的编辑距离
Efficiently calculate edit distance between two strings
我有一个长度为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])]
?
改善Space复杂性
提高编辑距离算法效率的一种方法是减少计算所需的内存量。
要使用整个矩阵,需要使用 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()
如果你想进一步分析这个方法,我写了一篇small open sourced library using this specific technique,有兴趣的可以看看。
提高时间复杂度
没有非平凡的方法可以改进 Levenshtein 距离算法,使其比 O(n^2)
执行得更快。有一些复杂的方法,一个使用 VP-Tree data structures. There are a few good sources if you're curious to read about them here and here,这些方法可以达到 O(nlgn)
.
的渐近速度
我有一个长度为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])]
?
改善Space复杂性
提高编辑距离算法效率的一种方法是减少计算所需的内存量。
要使用整个矩阵,需要使用 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()
如果你想进一步分析这个方法,我写了一篇small open sourced library using this specific technique,有兴趣的可以看看。
提高时间复杂度
没有非平凡的方法可以改进 Levenshtein 距离算法,使其比 O(n^2)
执行得更快。有一些复杂的方法,一个使用 VP-Tree data structures. There are a few good sources if you're curious to read about them here and here,这些方法可以达到 O(nlgn)
.