【问题标题】:Algorithms: Interesting diffing algorithm算法:有趣的差异算法
【发布时间】:2009-05-07 14:54:30
【问题描述】:

这是在现实世界中出现的,我想我会分享它,因为它可能会带来一些有趣的解决方案。本质上,算法需要区分两个列表,但让我给你一个更严格的问题定义。

数学公式

假设您有两个列表,LR,每个列表都包含来自某些底层字母表 S 的元素。此外,这些列表具有它们所具有的公共元素按顺序出现的属性:也就是说,如果L[i] = R[i*]L[j] = R[j*],并且i j 然后i* j* .这些列表根本不需要任何共同的元素,并且其中一个或两个都可以是空的。 [澄清:您可以假设元素没有重复。]

问题是产生一种列表的“差异”,可以将其视为有序对的新列表(x,y),其中x来自Ly来自R,具有以下属性:

  1. 如果x 出现在两个列表中,则(x,x) 出现在结果中。
  2. 如果x 出现在L 中,但不在R 中,则(x,NULL) 出现在结果中。
  3. 如果y 出现在R 中,但没有出现在L 中,则(NULL,y) 出现在结果中。

最后

  • 结果列表与每个输入列表具有“相同”的排序:粗略地说,它与每个单独的列表共享与上述相同的排序属性(参见示例)。

示例

L = (d)
R = (a,b,c)
Result = ((NULL,d), (a,NULL), (b,NULL), (c,NULL))

L = (a,b,c,d,e)  
R = (b,q,c,d,g,e)
Result = ((a,NULL), (b,b), (NULL,q), (c,c), (d,d), (NULL,g), (e,e))

有没有人有任何好的算法来解决这个问题?复杂度是多少?

【问题讨论】:

  • 如果您测试结果,请告诉我。我也想知道作业的有效答案。
  • 我想 NULL 的相对顺序是任意的?也就是说,在您的第一个示例中,(NULL,d) 可以出现在任何地方,对吧?
  • 你知道排序算法吗? (如果是前者,这很简单而且 O(n))
  • 澄清一下:您知道排序算法,还是只知道列表都按某种未知算法排序? (你的 "b,q,c,d,g,e" 就是一个明显的例子)
  • @David 是的,这是正确的。没有定义交集中的元素应出现在何处的条件。

标签: algorithm list language-agnostic diff


【解决方案1】:

如果您愿意在不同的数据结构中复制其中一个列表,则有一种方法可以在 O(n) 中完成。这是经典的时间/空间权衡。

创建列表R的哈希映射,键是元素,值是数组的原始索引;在 C++ 中,您可以使用 tr1 或 boost 中的 unordered_map。

为列表 R 的未处理部分保留索引,初始化为第一个元素。

对于列表 L 中的每个元素,检查列表 R 中的匹配项的哈希映射。如果没有找到,则输出(L 值,NULL)。如果有匹配,则从哈希图中获取对应的索引。对于列表 R 中直到匹配索引的每个未处理元素,输出(NULL,R 值)。对于匹配,输出(值,值)。

当你到达列表 L 的末尾时,遍历列表 R 的剩余元素并输出(NULL,R 值)。

编辑: 这是 Python 中的解决方案。对于那些说这个解决方案取决于一个好的散列函数的存在的人——当然是这样。如果这是一个问题,原始发布者可能会对该问题添加额外的限制,但在那之前我会采取乐观的立场。

def FindMatches(listL, listR):
    result=[]
    lookupR={}
    for i in range(0, len(listR)):
        lookupR[listR[i]] = i
    unprocessedR = 0
    for left in listL:
        if left in lookupR:
            for right in listR[unprocessedR:lookupR[left]]:
                result.append((None,right))
            result.append((left,left))
            unprocessedR = lookupR[left] + 1
        else:
            result.append((left,None))
    for right in listR[unprocessedR:]:
        result.append((None,right))
    return result

>>> FindMatches(('d'),('a','b','c'))
[('d', None), (None, 'a'), (None, 'b'), (None, 'c')]
>>> FindMatches(('a','b','c','d','e'),('b','q','c','d','g','e'))
[('a', None), ('b', 'b'), (None, 'q'), ('c', 'c'), ('d', 'd'), (None, 'g'), ('e','e')]

【讨论】:

  • hashmap 的高效速度取决于是否存在良好的散列函数。杰克甚至没有承诺存在一个。如果一个确实存在,您可以轻松地按它们的哈希码对它们进行排序并进行标准的有序比较,当然这是 O(nlogn)。
  • 现在我很好奇 - 为什么添加一个工作代码示例值得 -1 票?
【解决方案2】:

最坏的情况,如定义和仅使用相等,必须是 O(n*m)。考虑以下两个列表:

A[] = {a,b,c,d,e,f,g}

B[] = {h,i,j,k,l,m,n}

假设这两个“有序”列表之间恰好存在一个匹配项。它将进行 O(n*m) 次比较,因为不存在以后无需进行其他比较的比较。

因此,您提出的任何算法都将是 O(n*m),或更糟。

【讨论】:

  • 有没有办法在小于 O(n^2) 的时间内取两个列表的交集?如果是这样,我们可以加快速度。
  • 不,没有。如果您有两个包含 n 和 m 项的列表,并且交集的大小为 1。那么即使您事先知道交集的大小,您也需要平均 0.5*n*m 次比较才能找到交集。
  • 请参阅 Mark Ransom 的帖子。
  • 他的解决方案行不通,因为它需要一个散列函数。相等检查的存在并不能保证散列函数的存在。此外,它不能保证散列函数不会像“return 1;”这样愚蠢。
  • -1。在 O(log n) 时间内,按您喜欢的任何顺序对每个列表进行独立排序。 (这将破坏杰克第二段所保证的“结构”,但这没关系。)然后使用 O(n) 列表合并来找到交集——本质上,比较前两个元素,适当地输出较小的元素,将其从列表顶部删除,冲洗,重复。
【解决方案3】:

可以通过遍历两个列表并随时匹配来在线性时间内完成区分有序列表。我会尝试在更新中发布一些伪 Java 代码。

由于我们不知道排序算法并且无法根据小于或大于运算符确定任何排序,因此我们必须考虑无序列表。此外,考虑到结果的格式,您将面临扫描两个列表(至少在找到匹配项之前,然后您可以添加书签并从那里重新开始)。它仍然是 O(n^2) 性能,或者更具体地说是 O(nm)。

【讨论】:

  • 这需要你知道排序算法。第二个例子 (b,q,c,d,g,e) 有一个神秘的顺序。 (注意“q”和“g”)
  • 是的,请注意字母代表任意元素。
  • 好的,所以构建自定义小于和大于考虑神秘排序的运算符。如果我们有一个有序列表,我必须假设我们知道如何订购它,否则我们不能认为它是有序的。
  • @Mike 我只是指数学意义上的“有序”:将列表视为一个 n 元组。我对强加在底层字母表S 上的任何部分或全部顺序一无所知。
  • @Mike:既然你意识到你可以构建自己的“自定义”小于,是什么让你认为这仍然需要 O(nm) 时间?为什么你不能根据你的“自定义”小于对两个列表进行排序,在 O(n) 时间内合并它们,然后根据 L 或 R 的神秘排序与 O(log n+m) 重新排序输出列表) 排序?
【解决方案4】:

这和序列比对一模一样,可以使用Needleman-Wunsch算法来解决。该链接包含 Python 中的代码。只需确保设置得分,使不匹配为负,匹配为正,最大化时与空白对齐为 0。该算法在O(n * m)时间和空间上运行,但是这样的空间复杂度可以提高。

评分功能

int score(char x, char y){
    if ((x == ' ') || (y == ' ')){
        return 0;
    }
    else if (x != y){
        return -1;
    }
    else if (x == y){
        return 1;
    }
    else{
        puts("Error!");
        exit(2);
    }
}

代码

#include <stdio.h>
#include <stdbool.h>

int max(int a, int b, int c){
    bool ab, ac, bc;
    ab = (a > b);
    ac = (a > c);
    bc = (b > c);
    if (ab && ac){
        return a;
    }
    if (!ab && bc){
        return b;
    }
    if (!ac && !bc){
        return c;
    }
}

int score(char x, char y){
    if ((x == ' ') || (y == ' ')){
        return 0;
    }
    else if (x != y){
        return -1;
    }
    else if (x == y){
        return 1;
    }
    else{
        puts("Error!");
        exit(2);
    }
}


void print_table(int **table, char str1[], char str2[]){
    unsigned int i, j, len1, len2;
    len1 = strlen(str1) + 1;
    len2 = strlen(str2) + 1;
    for (j = 0; j < len2; j++){
        if (j != 0){
            printf("%3c", str2[j - 1]);
        }
        else{
            printf("%3c%3c", ' ', ' ');
        }
    }
    putchar('\n');
    for (i = 0; i < len1; i++){
        if (i != 0){
            printf("%3c", str1[i - 1]);
        }
        else{
            printf("%3c", ' ');
        }
        for (j = 0; j < len2; j++){
            printf("%3d", table[i][j]);
        }
        putchar('\n');
    }
}

int **optimal_global_alignment_table(char str1[], char str2[]){
    unsigned int len1, len2, i, j;
    int **table;
    len1 = strlen(str1) + 1;
    len2 = strlen(str2) + 1;
    table = malloc(sizeof(int*) * len1);
    for (i = 0; i < len1; i++){
        table[i] = calloc(len2, sizeof(int));
    }
    for (i = 0; i < len1; i++){
        table[i][0] += i * score(str1[i], ' ');
    }
    for (j = 0; j < len1; j++){
        table[0][j] += j * score(str1[j], ' ');
    }
    for (i = 1; i < len1; i++){
        for (j = 1; j < len2; j++){
            table[i][j] = max(
                table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]),
                table[i - 1][j] + score(str1[i - 1], ' '),
                table[i][j - 1] + score(' ', str2[j - 1])
            );
        }
    }
    return table;
}

void prefix_char(char ch, char str[]){
    int i;
    for (i = strlen(str); i >= 0; i--){
        str[i+1] = str[i];
    }   
    str[0] = ch;
}

void optimal_global_alignment(int **table, char str1[], char str2[]){
    unsigned int i, j;
    char *align1, *align2;
    i = strlen(str1);
    j = strlen(str2);
    align1 = malloc(sizeof(char) * (i * j));
    align2 = malloc(sizeof(char) * (i * j));
    align1[0] = align2[0] = '\0';
    while((i > 0) && (j > 0)){
        if (table[i][j] == (table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]))){
            prefix_char(str1[i - 1], align1);
            prefix_char(str2[j - 1], align2);
            i--;
            j--;
        }
        else if (table[i][j] == (table[i - 1][j] + score(str1[i-1], ' '))){
            prefix_char(str1[i - 1], align1);
            prefix_char('_', align2);
            i--;
        }
        else if (table[i][j] == (table[i][j - 1] + score(' ', str2[j - 1]))){
            prefix_char('_', align1);
            prefix_char(str2[j - 1], align2);
            j--;
        }
    }
    while (i > 0){
        prefix_char(str1[i - 1], align1);
        prefix_char('_', align2);
        i--;
    }
    while(j > 0){
        prefix_char('_', align1);
        prefix_char(str2[j - 1], align2);
        j--;
    }
    puts(align1);
    puts(align2);
}

int main(int argc, char * argv[]){
    int **table;
    if (argc == 3){
        table = optimal_global_alignment_table(argv[1], argv[2]);
        print_table(table, argv[1], argv[2]);
        optimal_global_alignment(table, argv[1], argv[2]);
    }
    else{
        puts("Reqires to string arguments!");
    }
    return 0;
}

示例 IO

$ cc dynamic_programming.c && ./a.out aab bba
__aab
bb_a_
$ cc dynamic_programming.c && ./a.out d abc
___d
abc_
$ cc dynamic_programming.c && ./a.out abcde bqcdge
ab_cd_e
_bqcdge

【讨论】:

    【解决方案5】:

    没有真正具体的答案,只有模糊的直觉。因为您不知道排序算法,只知道数据在每个列表中排序,这听起来有点像用于“区分”文件(例如在 Beyond Compare 中)并将行序列匹配在一起的算法。或者也隐约类似于正则表达式算法。

    也可以有多种解决方案。(没关系,如果没有严格排序的重复元素就不会。我在文件比较方面想太多了)

    【讨论】:

      【解决方案6】:

      这是一个非常简单的问题,因为您已经有一个有序列表。

      //this is very rough pseudocode
      stack aList;
      stack bList;
      List resultList;
      char aVal;
      char bVal;
      
      while(aList.Count > 0 || bList.Count > 0)
      {
        aVal = aList.Peek; //grab the top item in A
        bVal = bList.Peek; //grab the top item in B
      
        if(aVal < bVal || bVal == null)
        {
           resultList.Add(new Tuple(aList.Pop(), null)));
        }
        if(bVal < aVal || aVal == null)
        {
           resultList.Add(new Tuple(null, bList.Pop()));
        }
        else //equal
        {
           resultList.Add(new Tuple(aList.Pop(), bList.Pop()));
        }
      }
      

      注意...此代码不会编译。它只是作为一个指南。

      编辑基于 OP cmets

      如果排序算法没有公开,那么列表必须被认为是无序的。 如果列表是无序的,则该算法的时间复杂度为 O(n^2),具体为 O(nm),其中 n 和 m 是每个列表中的项目数。

      编辑 解决这个问题的算法

      L(a,b,c,d,e) R(b,q,c,d,g,e)

      //pseudo code... will not compile
      //Note, this modifies aList and bList, so make copies.
      List aList;
      List bList;
      List resultList;
      var aVal;
      var bVal;
      
      while(aList.Count > 0)
      {
         aVal = aList.Pop();
         for(int bIndex = 0; bIndex < bList.Count; bIndex++)
         {
            bVal = bList.Peek();
            if(aVal.RelevantlyEquivalentTo(bVal)
            {
               //The bList items that come BEFORE the match, are definetly not in aList
               for(int tempIndex = 0; tempIndex < bIndex; tempIndex++)
               {
                   resultList.Add(new Tuple(null, bList.Pop()));
               }
               //This 'popped' item is the same as bVal right now
               resultList.Add(new Tuple(aVal, bList.Pop()));
      
               //Set aVal to null so it doesn't get added to resultList again
               aVal = null;
      
               //Break because it's guaranteed not to be in the rest of the list
               break;
            }
         }
         //No Matches
         if(aVal != null)
         {
            resultList.Add(new Tuple(aVal, null));
         }
      }
      //aList is now empty, and all the items left in bList need to be added to result set
      while(bList.Count > 0)
      {
         resultList.Add(new Tuple(null, bList.Pop()));
      }
      

      结果集将是

      L(a,b,c,d,e) R(b,q,c,d,g,e)

      结果 ((a,null),(b,b),(null,q),(c,c),(d,d),(null,g),(e,e))

      【讨论】:

      • 不。与 Mike Pone 的回答相同的评论;这需要您了解排序算法。第二个例子 (b,q,c,d,g,e) 有一个神秘的顺序。 (注意“q”和“g”)
      • 这不起作用,因为元素不一定是数字,可能无法与进行比较。
      • 您可以替换任何必要的比较。在“排序算法部分”,如果您不知道对象是如何排序的,那么从编程的角度来看,它们不能被视为“有序列表”。 IE。给定两个“我最喜欢的电影”和“他最喜欢的电影”的顺序列表,算法必须将它们视为无序列表。
      • @Devin 你是对的,我在混淆数学和计算机科学术语。我会修复的。
      • 如果你只能测试相等性,那么它是 O(m*n) 有办法加速算法,所以这是最坏的情况,但复杂性总是如此。
      【解决方案7】:

      我认为你没有足够的信息。您所断言的是匹配的元素以相同的顺序匹配,但是找到第一个匹配对是一个 O(nm) 操作,除非您有其他可以确定的顺序。

      【讨论】:

        【解决方案8】:

        选择不同的 l.element,r.element
        FROM LeftList l
        OUTER JOIN RightList r
        ON l.element = r.element
        按 l.id、r.id 排序

        假设每个元素的 ID 是它的排序。当然,您的列表包含在关系数据库中:)

        【讨论】:

          猜你喜欢
          • 2012-09-14
          • 2010-10-22
          • 1970-01-01
          • 2010-11-09
          • 1970-01-01
          • 2014-04-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多