【问题标题】:C# fast Except method for sorted listC# fast 除了排序列表的方法
【发布时间】:2015-10-20 04:40:17
【问题描述】:

我正在开发一个瓶颈是 list1.Except(list2) 的应用程序。来自这篇文章:should i use Except or Contains when dealing with HashSet or so in Linq except 的复杂度是 O(m+n) (m 和 n 代表列表的大小)。但是,我的列表已排序。这有帮助吗?

我能想到的第一个实现:

foreach element in list2 (m operations)
    look for it in list1 (ln(n) operations)
    if present 
        set it to null (O(1), removing has a O(n))
    else continue

它的复杂度为 O(m*ln(n)),当 m 小而 n 大时,这非常有趣(这正是我的数据集的情况:m 约为 50,n 约为 1 000 000 )。然而,它产生 null 的事实可能对使用它的函数有很多影响......有没有办法保持这种复杂性,而不必编写 null(然后跟踪它们)

任何帮助将不胜感激!

【问题讨论】:

  • 创建自己的扩展方法,使用BinarySearch?
  • 第一件事是你已经注意到这两个东西的行为是不同的。此外,如果您的算法是复杂度 m*ln(n),那么对于大 m 和小 n,乍一看,它看起来将具有大致相同的性能,因此复杂性可能无济于事,您可能只需要对事物进行基准测试和看看哪个表现更好。
  • 其实我忘了说m在50左右,n在1 000 000左右
  • @Damien 我想我没有得到你的建议。据我了解,依次遍历两个列表应该是 O(m+n)

标签: c# algorithm linq list


【解决方案1】:
using System;
using System.Collections.Generic;

public class Test
{
    public static void Main()
    {
        var listM = new List<int>();
        var listN = new List<int>();
        for(int i = 0, x = 0; x < 50; i+=13, x++) { 
            listM.Add(i);
        }
        for(int i = 0, x = 0; x < 10000; i+=7, x++) { 
            listN.Add(i);
        }
        Console.WriteLine(SortedExcept(listM, listN).Count);
    }

    public static List<T> SortedExcept<T>(List<T> m, List<T> n) {
        var result = new List<T>();
        foreach(var itm in m) {
            var index = n.BinarySearch(itm);
            if(index < 0) { 
                result.Add(itm); 
            }
        }
        return result;
    }
}

编辑这也是 O(M + N) 版本

public static List<T> SortedExcept2<T>(List<T> m, List<T> n) where T : IComparable<T> {
    var result = new List<T>();
    int i = 0, j = 0;
    if(n.Count == 0) {
        result.AddRange(m);
        return result;
    }
    while(i < m.Count) {
        if(m[i].CompareTo(n[j]) < 0) {
            result.Add(m[i]);
            i++;
        } else if(m[i].CompareTo(n[j]) > 0) {
            j++;
        } else {
            i++;
        }
        if(j >= n.Count) {
            for(; i < m.Count; i++) { 
                result.Add(m[i]); 
            }
            break;
        }
    }
    return result;
}

在一个快速而肮脏的基准测试中http://ideone.com/Y2oEQD M + N 总是更快,即使 N 是 1000 万。 BinarySearch 受到惩罚,因为它以非线性方式访问数组内存;这会导致缓存未命中,从而减慢算法速度,因此 N 越大,BinarySearch 的内存访问惩罚就越多。

【讨论】:

  • 实际上,恐怕我无法逃脱 O(n) 复杂性 - 它对应于最大列表的大小,我必须复制它...另一个解决方案是跟踪我想删除的索引(最多 m 个)并在其他方法中使用它们。
【解决方案2】:

如果两个列表都已排序,那么您可以轻松实现自己的解决方案:

listA 除了 listB 算法然后工作如下:

1. Start from the beginning of both lists
2. If listA element is smaller than the listB element,
   then include the listA element in the output and advance listA
3. If listB element is smaller than the listA element, advance listB
4. If listA and listB elements are equal,
   advance both lists and do not push the element to the output

重复直到 listA 用完。请特别注意 listB 可能会在 listA 之前用完。

【讨论】:

  • 对不起,我的问题不清楚。我正在寻找 O(m*ln(n)) 的复杂度。在我看来,您的方法运行时间为 O(m+n)。
  • O(m+n) 将比 O(m*ln(n)) 运行得更快。
  • 视情况而定。不是在 ln(n)
  • 可能是。您可以对较短的列表进行二进制搜索,然后对这两种方法进行测量。此外,还有一个强大的想法通常会有所帮助 - 将较短的列表转换为普通数组并使用顺序搜索。 For 循环非常快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多