字符串相似度 - > Levenshtein距离

时间:2012-07-26 17:47:52

标签: string algorithm levenshtein-distance similarity

我正在使用Levenshtein算法来找到两个字符串之间的相似性。这是我正在制作的计划中非常重要的一部分,因此它需要有效。 问题是该算法没有找到类似的以下示例:

  

CONAIR
  AIRCON

该算法将给出6的距离。因此,对于6个字母的单词(您查看具有最高字母数量的单词),差异为100%=>相似度为0%。

我需要找到一种方法来找到两个字符串之间的相似之处,同时也要考虑像我之前提到的那样的情况。

我可以使用更好的算法吗?或者你们有什么推荐我的?

编辑:我还研究了“Damerau-Levenshtein”算法,该算法增加了换位。问题是这个换位仅适用于相邻的字符(而不适用于多个字符)。

7 个答案:

答案 0 :(得分:10)

我会将这个词分为unigrams,bigrams和trigrams,然后计算余弦相似度。

答案 1 :(得分:5)

我认为这可以通过在其中一个字符串上使用最长公共子字符串/子序列算法(例如“conair”)和另一个附加到其自身的字符串(例如“aircon”)来轻松解决 - >“airconaircon”)。

C:

中的示例代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Returns the length of the longest common substring (LCS)
// between two given strings.
//
// This recursive implementation can be replaced by a
// more performant dynamic programming implementation.
size_t llcs(const char* s1, const char* s2)
{
  size_t len[3];

  if (*s1 == '\0' || *s2 == '\0') return 0;

  len[0] = (*s1 == *s2) + llcs(s1 + 1, s2 + 1);
  len[1] = llcs(s1 + 1, s2);
  len[2] = llcs(s1, s2 + 1);

  if (len[0] < len[1]) len[0] = len[1];
  if (len[0] < len[2]) len[0] = len[2];

  return len[0];
}

// Returns similarity of two given strings in the range
// from 0.0 to 1.0 (1.0 for equal strings).
double similarity(const char* s1, const char* s2)
{
  size_t s1len = strlen(s1);
  size_t s2len = strlen(s2);
  double sim;

  if (s1len == 0 && s2len == 0)
  {
    // Two empty strings are equal
    sim = 1;
  }
  else
  {
    size_t len;
    // Append s1 to itself in s1s1 (e.g. "aircon" -> "airconaircon")
    char* s1s1 = malloc(s1len * 2 + 1);
    strcpy(s1s1, s1);
    strcpy(s1s1 + s1len, s1);

    // Find the length of the LCS between s1s1 and s2
    // (e.g. between "airconaircon" and "conair")
    len = llcs(s1s1, s2);
    // We need it not longer than s1 (e.g. "aircon")
    // since we're actually comparing s1 and s2
    if (len > s1len) len = s1len;

    len *= 2;

    // Prevent 100% similarity between a string and its
    // cyclically shifted version (e.g. "aircon" and "conair")
    if (len == s1len + s2len && strcmp(s1, s2) != 0) len--;

    // Get the final measure of the similarity
    sim = (double)len / (s1len + s2len);

    free(s1s1);
  }

  return sim;
}

int main(int argc, char** argv)
{
  if (argc == 3)
    printf("Similarity of \"%s\" and \"%s\" is %.2f%%\n",
           argv[1], argv[2], 100 * similarity(argv[1], argv[2]));
  else
    printf("Usage:\n  %s string1 string2\n",
           argv[0]);
  return 0;
}

示例输出:

Similarity of "123" and "123" is 100.00%
Similarity of "123" and "1234" is 85.71%
Similarity of "0123" and "123" is 85.71%
Similarity of "a" and "aa" is 66.67%
Similarity of "aa" and "a" is 66.67%
Similarity of "aaaaaaa" and "aaaaaa" is 92.31%
Similarity of "aaaaaa" and "aaaaaaa" is 92.31%
Similarity of "aircon" and "conair" is 91.67%
Similarity of "spit" and "pits" is 87.50%
Similarity of "pits" and "spit" is 87.50%
Similarity of "spits" and "pits" is 88.89%
Similarity of "pits" and "spits" is 88.89%

答案 2 :(得分:2)

听起来你可能想尝试使用音节或音素代替字母来做Levenshtein距离。

答案 3 :(得分:2)

理论上,您使用的方法对于您尝试解决的问题是正确的。但是Levenstein只会考虑两组中的个性。

也可以使用Longest Common Subsequence方法找到字符串的相似性,然后你可以看到Levenstein在其余的不匹配上。

如果您想要采用聚类方法,the following answer似乎有一些细节,但显然实施起来更困难。

答案 4 :(得分:2)

对单词进行排序并找到Levenshtein将为您的示例提供100%的匹配,但它也会给出100%的匹配,例如

CONAIR
RCIAON

可能不是你想要的。

定义相似性的另一种方法是找出2个字符串的公共子串。您可以创建Suffix Tree并查找所有常见的子字符串,并尝试确定它们的相似程度。所以对你的后缀树会给出常见的子串,如CON&amp; AIR覆盖整个单词(对于你的2个字符串),从而将它们视为相似的。

答案 5 :(得分:2)

尝试使用其他相似性测量,如sorenson,jaccard和jaro_winkler

我个人非常喜欢jaro winkler,因为它多次服务于我的目的。

from Levenshtein import jaro_winkler
In [2]: jaro_winkler("conair","aircon")
Out[2]: 0.8333333333333334

答案 6 :(得分:1)

看看Needleman-Wunsch或Smith-Waterman算法。它们用于通过适应DNA序列的编辑距离来处理字符串匹配,其中任何类型的插入,反转,转座子可以发生在任何地方的任何长度。说这个,我需要添加一个足够长的字符串,没有最佳解决方案。并且不要忘记编辑成本取决于算法的使用上下文(语义问题),而任何算法总是语法机器。