这两种算法之间是否存在改变IEnumerable的性能差异?

这两个问题给出了改组IEnumerable的类似算法:

  • C#:使用Random和OrderBy是一个很好的shuffle算法吗?
  • 你可以在C#中乱序列举一个集合吗?

以下是两种方法并排:

public static IEnumerable Shuffle1 (this IEnumerable source) { Random random = new Random (); T [] copy = source.ToArray (); for (int i = copy.Length - 1; i >= 0; i--) { int index = random.Next (i + 1); yield return copy [index]; copy [index] = copy [i]; } } public static IEnumerable Shuffle2 (this IEnumerable source) { Random random = new Random (); List copy = source.ToList (); while (copy.Count > 0) { int index = random.Next (copy.Count); yield return copy [index]; copy.RemoveAt (index); } } 

它们基本相同,除了一个使用List ,一个使用数组。 从概念上讲,第二个似乎对我来说更清楚。 但使用arrays可以获得显着的性能优势吗? 即使Big-O时间相同,如果它快几倍,它也会产生明显的差异。

由于RemoveAt,第二个版本可能会慢一点。 列表实际上是在向其添加元素时增长的数组,因此,中间的插入和删除速度很慢(实际上,MSDN声明RemoveAt具有O(n)复杂度)。

无论如何,最好的方法是简单地使用分析器来比较两种方法。

第一个不编译,虽然很明显你试图重新枚举可枚举,然后实现Fisher-Yates; 这可能是正确的方法,并且任何以前曾经洗过arrays的人都不应该不清楚。 第二个使用RemoveAt是不好的,因为其他评论者说的原因。

编辑:你的顶级实现现在看起来是正确的,这是一个很好的方法。

第一个算法是O(n),因为它有一个循环,在每次迭代时执行O(1)交换。 第二个算法是O(n ^ 2),因为它在每次迭代时执行O(n)RemoveAt操作。 另外,列表上的索引器比数组上的索引慢,因为前者是方法调用,而后者是IL指令。

所以在第一个中,第一个可能会更快。 也就是说,如果你在表演之后,为什么还要费心取得结果呢? 它已经转换为一个数组,所以只是在适当的位置进行随机播放并直接返回数组(或者如果你担心人们改变它就包裹在ReadOnlyCollection ),这可能更快。

另外,两种方法都有错误,当多个线程使用Random时, Random的行为是未定义的,因此它们应该使用线程安全的随机数生成器 。