如何更有效地计算 n 个字符串之间的不匹配分数?

Posted

技术标签:

【中文标题】如何更有效地计算 n 个字符串之间的不匹配分数?【英文标题】:How to calculate a mismatch score between n number of strings more efficiently? 【发布时间】:2018-05-17 15:31:33 【问题描述】:

假设我有一个包含n 字符串的向量,其中字符串的长度可以是 5...n。每个字符串必须逐个字符地与每个字符串进行比较。如果不匹配,则分数增加一。如果有匹配,则分数不会增加。然后我会将得到的分数存储在一个矩阵中。

我通过以下方式实现了这一点:

for (auto i = 0u; i < vector.size(); ++i)

  // vector.size() x vector.size() matrix
  std::string first = vector[i]; //horrible naming convention
  for (auto j = 0u; j < vector.size(); ++j)
  
    std::string next = vector[j];
    int score = 0;
    for (auto k = 0u; k < sizeOfStrings; ++k)
    
      if(first[k] == second[k])
      
        score += 0;
      
      else
      
        score += 1;
      
    
    //store score into matrix
  

我对这个解决方案不满意,因为它是O(n^3)。所以我一直在想其他方法来提高效率。我曾考虑编写另一个函数来替换我们的 j for 循环的内部结构,但是,它仍然是 O(n^3),因为该函数仍然需要一个 k 循环。

我也考虑过队列,因为与string[1]string[n] 相比,我只关心string[0]String[1]string[2]string[n] 相比。 String[2]string[3]string[n] 相比,等等。所以我的解决方案有不必要的计算,因为每个字符串都在与其他字符串进行比较。这个问题是我不确定如何从中构建我的矩阵。

我终于查看了 std 模板库,但是 std::mismatch 似乎不是我想要的,或者 std::find。大家还有什么想法?

【问题讨论】:

如果性能是问题,那么我建议首先确保您当前的算法有机会表现良好,即使用引用而不是到处复制字符串:std::string&amp; next = vector[j]; @keith,我目前在一个函数中通过引用传递向量,但我不认为通过引用传递字符串。我只是想摆脱O(n^3) 的比较。这似乎是一个经典的动态编程问题。 (1) :将字符串向量转换为矩阵 m,其中 m[n] 是在字符串中位置 #n 找到的每个字符的 #occurence 列表:"aaa","abb","abc" -> @ 987654341@. (2)意识到总分是每个m[n]的分数之和。 (4) 享受....... 【参考方案1】:

我认为您无法轻松摆脱 O(n^3) 比较,但您可以轻松实现您所说的更改。由于只需要以一种方式进行比较(即比较 string[1] 到 string[2] 与比较 string[2] 到 string[1] 相同),正如您所指出的,您不需要迭代每次遍历整个数组,并且可以将内循环的起始值更改为外循环的当前索引:

for (auto i = 0u; i < vector.size(); ++i) 
    // vector.size() x vector.size() matrix
    std::string first = vector[i]; //horrible naming convention
    for (auto j = i; j < vector.size(); ++j) 

要将其存储在矩阵中,请设置您的i x j 矩阵,将其初始化为全零并将每个分数存储在M[i][j]

for (auto k = 0u; k < sizeOfStrings; ++k) 
    if (first[k] != second[k]) 
        M[i][j]++;
    

【讨论】:

我没想过要那样做。为什么你认为没有简单的方法绕过n^3 比较? 好吧,如果您只是比较字符串相等性,您可能会做得更好(通过对列表进行排序或使用哈希表),但您实际上是在计算每对字符串之间的差异数。要查看 2 个字符串之间有多少差异,您必须比较整个字符串 O(n),并且必须将每个字符串与其他字符串进行比较 O(n^2)。 还要注意`O(n^3)`并不意味着你必须做那么多比较,它只是意味着你的比较将以与n^3n相同的速度增长增加。通过将j 更改为从i 的当前值开始,实际上您将进行的比较次数减半,但仍将其视为O(n^3),在描述复杂性时不应用常数因子。【参考方案2】:

如果你有 n 个长度为 m 的字符串,那么无论如何(即使你有队列的想法),你至少必须做 (n-1)+(n-2)+...+(1) =n(n-1)/2 字符串比较,所以你必须做 (n(n-1)/2)*m 字符比较。所以无论如何,你的算法将是 O(mn^2)。

【讨论】:

这不一定是真的。可以重新制定问题以避免必须执行所有这些比较。例如,如果字符串 A 和 B 在位置 k 上一致,则 B 也与在该位置上与 A 一致的所有其他字符串一致。但是,Helium_1s2 的回答应该被视为表明,如果不对算法结构进行重大更改,就无法简化问题。 我同意@KristoferBjörnson,这似乎是一个使用动态编程的经典应用程序。矩阵中的结果将是对称的。这意味着我正在计算我已经知道的结果。但是 Kristofer,变化会有多剧烈? @Sailanarmo Stephen Docy 已经给出了一个解决方案,通过使用等号的对称属性,大致可以将时间缩短一半。这不会改变缩放行为,但很重要。我的建议是缩放行为本身可以使用等号的传递属性来改变。但是,我现在没有具体的建议,也不知道情况可以改善到什么程度。【参考方案3】: 一般性评论:

您不必相互比较相同的字符串。更重要的是,您在第二个循环中每次从头开始,而您已经计算了这些差异,因此将第二个循环更改为从 i+1 开始。 通过这样做,您的复杂性将会降低,因为您不会检查您已经检查过或相同的字符串。

改进

对向量进行排序并删除重复的条目,而不是浪费计算来检查相同的字符串,您只会检查不同的字符串。

【讨论】:

排序不会打乱我的矩阵的顺序吗?例如,string["abc"]、string["aab"] 和 string["aaa"]。 string["abc"] 和 string["aab"] 之间的比较需要进入 Matrix[0,1]。但是,如果我对它们进行排序,string["aaa"], string["aab"], string["abc"],那会抛出数字,不是吗?【参考方案4】:

说这至少是 O(mn^2) 或 O(n^3) 的其他答案是不正确的。这可以在 O(mn) 时间内完成,其中 m 是字符串大小,n 是字符串数。

为简单起见,我们先假设所有字符都是 ascii。

你有一个数据结构:

int counts[m][255]

其中 counts[x][y] 是字符串中索引 x 处具有 ascii 字符 y 的字符串数。

现在,如果您不限于 ascii,那么您将需要使用 std::map

map counts[m]

但它的工作方式相同,在索引 m 处,您有一个映射,其中映射 y,z 中的每个条目告诉您有多少字符串 z 在索引 m 处使用字符 y。您还需要选择具有恒定时间查找和恒定时间插入的地图以匹配复杂性。

回到ascii和数组

int counts[m][255] // start by initializing this array to all zeros

首先初始化数据结构:

m 是字符串的大小, vec 是带有字符串的 std::vector

for (int i = 0; i < vec.size(); i++) 
    std::string str = vec[i];
    for(int j = 0; j < m; j++) 
        counts[j][str[j]]++;
    

现在你有了这个结构,你可以很容易地计算分数:

for (int i = 0; i < vec.size(); i++) 
    std::string str = vec[i];
    int score = 0;
    for(int j = 0; j < m; j++) 
            score += counts[j][str[j]] - 1; //subtracting 1 gives how many other strings have that same char at that index
    
    std::cout << "string \"" << str << "\" has score " << score;

从这段代码可以看出,这是 O(m * n)

【讨论】:

我感觉有一个类似的解决方案,因为这感觉这是一个经典的动态编程问题。但我能问一下,你的思维过程是怎样的?我一直在想,“我正在计算我已经知道的值。”但我不知道如何进入下一步。 好吧,我猜想的过程是对于字符串中的每个字符,您不需要继续检查每个字符串是否有相同的字符,您只需要知道有多少其他字符串那个角色。您可以预先计算出有多少具有该字符的其他字符串。所以在这样的问题中,寻找不需要重复的东西。在这种情况下,您不需要重复遍历所有其他字符串来查找给定字符的过程。一般来说,尽量找出那些不需要重复的东西,计算一次并重复使用。

以上是关于如何更有效地计算 n 个字符串之间的不匹配分数?的主要内容,如果未能解决你的问题,请参考以下文章

计算来自 2 个不同表的 2 行之间的不匹配 - MySQL

如何使用 dynamodb aws 中的索引有效地检索记录

如何更有效地找到 PL/SQL 中分隔符之间的子字符串?

如何让 elasticsearch 为匹配顺序的标记字符串分配更高的分数?

如何更有效地存储距离矩阵?

如何更有效地从n组中找到满足给定条件的最小组合?