这两个问题对于改组IEnumerable提供了类似的算法:
以下两种方法是并排的:
public static IEnumerable<T> Shuffle1<T> (this IEnumerable<T> 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<T> Shuffle2<T> (this IEnumerable<T> source)
{
Random random = new Random ();
List<T> copy = source.ToList ();
while (copy.Count > 0) {
int index = random.Next (copy.Count);
yield return copy [index];
copy.RemoveAt (index);
}
}
它们基本相同,除了一个使用List
,一个使用数组。从概念上讲,第二个似乎对我来说更清楚。但使用阵列是否可以获得显着的性能优势?即使Big-O时间相同,如果它快几倍,也会产生明显的差异。
答案 0 :(得分:7)
由于RemoveAt,第二个版本可能会慢一些。列表实际上是在向元素添加元素时增长的数组,因此,中间的插入和删除速度很慢(实际上,MSDN声明RemoveAt具有O(n)复杂度)。
无论如何,最好的方法是简单地使用分析器来比较两种方法。
答案 1 :(得分:2)
第一个算法是O(n),因为它有一个循环,在每次迭代时执行O(1)交换。第二个算法是O(n ^ 2),因为它在每次迭代时执行O(n)RemoveAt操作。另外,列表上的索引器比数组上的索引慢,因为前者是方法调用,而后者是IL指令。
所以在第一个中,第一个可能会更快。也就是说,如果你在表演之后,为什么还要费心取得结果呢?它已经转换为一个数组,所以只是在适当的位置进行随机播放并直接返回数组(如果你担心人们改变它,则将其包裹在ReadOnlyCollection<T>
中),这可能会更快。
另外,两种方法都有错误,当多个线程使用Random
时,{{1}}的行为是未定义的,所以它们应该使用thread-safe random number generator。
答案 2 :(得分:2)
第一个不编译,虽然很明显你试图重新枚举可枚举,然后实现Fisher-Yates;这可能是正确的方法,任何以前曾经洗过阵列的人都不应该不清楚。使用RemoveAt
的第二个因其他评论者所说的原因而不好。
编辑:你的顶级实现现在看起来是正确的,这是一个很好的方法。