【问题标题】:Finding closest neighbour using optimized Levenshtein Algorithm使用优化的 Levenshtein 算法寻找最近的邻居
【发布时间】:2011-03-12 20:04:40
【问题描述】:

我最近在posted a question 谈论优化算法以计算 Levenshtein 距离,这些回复将我带到了Levenshtein Distance 上的维基百科文章。

文章提到如果在最大距离上有一个界限k,则可能的结果可以来自给定的查询,那么运行时间可以从O(mn) 到 O(kn)mn 是字符串的长度。我查了算法,但我真的不知道如何实现它。我希望在这里得到一些线索。

优化是“可能的改进”下的#4。

让我困惑的部分是说我们只需要计算宽度为 2k+1 的对角线条纹,以主对角线为中心(主对角线定义为坐标(i ,i))。

如果有人可以提供一些帮助/见解,我将不胜感激。如果需要,我可以在这里发布书中算法的完整描述作为答案。

【问题讨论】:

  • 你从哪里得到“以主对角线为中心......定义为坐标 (i, i)”?引用的文章没有这么说。条纹需要以 TRAILING 对角线(以 (m, n) 结尾)为中心...整个想法是计算 d[m][n] ...d[i][j] 是 Lev。 s 中前 i 个字符与 t 中前 j 个字符之间的距离。
  • @John:我在维基百科文章引用的书中得到了这一点。它特别指出最后一个元素不必是 d[m][n],而是 [i][i]。虽然这不是两个词的正确 Levenshtein 距离,但它会告诉我们这两个词是否比我们的边界 k 更近,这就是它仍然有用的原因。

标签: algorithm optimization levenshtein-distance


【解决方案1】:

我已经做过很多次了。我这样做的方法是对可能变化的游戏树进行递归深度优先树遍历。有一个预算 k 的更改,我用它来修剪树。有了这个例程,我首先以 k=0,然后 k=1,然后 k=2 运行它,直到我获得成功或者我不想再更高了。

char* a = /* string 1 */;
char* b = /* string 2 */;
int na = strlen(a);
int nb = strlen(b);
bool walk(int ia, int ib, int k){
  /* if the budget is exhausted, prune the search */
  if (k < 0) return false;
  /* if at end of both strings we have a match */
  if (ia == na && ib == nb) return true;
  /* if the first characters match, continue walking with no reduction in budget */
  if (ia < na && ib < nb && a[ia] == b[ib] && walk(ia+1, ib+1, k)) return true;
  /* if the first characters don't match, assume there is a 1-character replacement */
  if (ia < na && ib < nb && a[ia] != b[ib] && walk(ia+1, ib+1, k-1)) return true;
  /* try assuming there is an extra character in a */
  if (ia < na && walk(ia+1, ib, k-1)) return true;
  /* try assuming there is an extra character in b */
  if (ib < nb && walk(ia, ib+1, k-1)) return true;
  /* if none of those worked, I give up */
  return false;
}

添加解释 trie-search:

// definition of trie-node:
struct TNode {
  TNode* pa[128]; // for each possible character, pointer to subnode
};

// simple trie-walk of a node
// key is the input word, answer is the output word,
// i is the character position, and hdis is the hamming distance.
void walk(TNode* p, char key[], char answer[], int i, int hdis){
  // If this is the end of a word in the trie, it is marked as
  // having something non-null under the '\0' entry of the trie.
  if (p->pa[0] != null){
    if (key[i] == '\0') printf("answer = %s, hdis = %d\n", answer, hdis);
  }
  // for every actual subnode of the trie
  for(char c = 1; c < 128; c++){
    // if it is a real subnode
    if (p->pa[c] != null){
      // keep track of the answer word represented by the trie
      answer[i] = c; answer[i+1] = '\0';
      // and walk that subnode
      // If the answer disagrees with the key, increment the hamming distance
      walk(p->pa[c], key, answer, i+1, (answer[i]==key[i] ? hdis : hdis+1));
    }
  }
}
// Note: you have to edit this to handle short keys.
// Simplest is to just append a lot of '\0' bytes to the key.

现在,为了限制预算,如果 hdis 太大就拒绝下降。

【讨论】:

  • 没有DP或者memoization,这个算法的运行时间似乎不是O(kn)
  • 在平均情况下,由于 k 的界限,这应该仍然比 O(mn) 快。但是,在最坏的情况下,这将比 O(mn) 慢。
  • @Dharmesh:我承认我在拼写纠正方面使用得更多,它与字典的 trie-walk 结合使用以查找最接近的匹配词。
  • @Mike:我还有一个 trie 演练来查找是否存在完全匹配。如果不存在完全匹配,我将它与另一种算法结合使用以找到最接近的匹配。在分析我的代码后,我发现绝大多数时间都花在计算 Levenshtein 距离上,这就是我现在尝试优化该位的原因。
  • @Dharmesh:那么我的方法可能会对您有所帮助,因为我将距离计算结合到 trie-walk 中。这不是一个单独的计算。我已经在 SO 的其他地方发布了这段代码:stackoverflow.com/questions/2918771/…(已经有 30 年了,所以可能有点生疏了。)
猜你喜欢
  • 1970-01-01
  • 2011-06-24
  • 1970-01-01
  • 2021-02-21
  • 2020-11-05
  • 1970-01-01
  • 2018-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多