小编典典

使用 Random 和 OrderBy 是否是一个好的 shuffle 算法?[关闭]

all

我在CodingHorror阅读了一篇关于各种
shuffle 算法的文章。我已经看到人们在某处这样做是为了对列表进行洗牌:

var r = new Random();
var shuffled = ordered.OrderBy(x => r.Next());

这是一个好的洗牌算法吗?它是如何工作的?这是一种可接受的方式吗?


阅读 55

收藏
2022-08-24

共1个答案

小编典典

这不是我喜欢的洗牌方式,主要是因为它是 O(n log n) 没有充分理由,因为它很容易实现 O(n)
洗牌。问题中的代码通过基本上给每个元素一个随机(希望是唯一的!)数字来“工作”,然后根据该数字对元素进行排序。

我更喜欢 Durstenfeld 的Fisher-Yates shuffle变体,它可以交换元素。

实现一个简单的Shuffle扩展方法基本上包括调用ToListToArray输入,然后使用现有的 Fisher-Yates
实现。(Random作为参数传递,使生活总体上更美好。)周围有很多实现......我可能在某个地方得到了一个答案。

这种扩展方法的好处是,读者会很清楚你实际上想要做什么。

编辑:这是一个简单的实现(没有错误检查!):

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
    T[] elements = source.ToArray();
    // Note i > 0 to avoid final pointless iteration
    for (int i = elements.Length-1; i > 0; i--)
    {
        // Swap element "i" with a random earlier element it (or itself)
        int swapIndex = rng.Next(i + 1);
        T tmp = elements[i];
        elements[i] = elements[swapIndex];
        elements[swapIndex] = tmp;
    }
    // Lazily yield (avoiding aliasing issues etc)
    foreach (T element in elements)
    {
        yield return element;
    }
}

编辑:下面对性能的评论提醒我,我们实际上可以在洗牌时返回元素:

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
    T[] elements = source.ToArray();
    for (int i = elements.Length - 1; i >= 0; i--)
    {
        // Swap element "i" with a random earlier element it (or itself)
        // ... except we don't really need to swap it fully, as we can
        // return it immediately, and afterwards it's irrelevant.
        int swapIndex = rng.Next(i + 1);
        yield return elements[swapIndex];
        elements[swapIndex] = elements[i];
    }
}

这现在只会做它需要做的工作。

请注意,在这两种情况下,您都需要小心Random使用的实例:

  • Random大致同时创建两个实例将产生相同的随机数序列(以相同方式使用时)
  • Random不是线程安全的。

我有一篇文章Random更详细地介绍了这些问题并提供了解决方案。

2022-08-24