You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Fisher-Yates洗牌:.NET 8与维基百科实现谁更正确?

.NET 8 Random.Shuffle 与维基百科 Fisher-Yates Inside-Out 变体的正确性对比

我在Unity中需要对数组进行洗牌操作,想到了.NET 8提供的Random.Shuffle方法。确认它基于Fisher-Yates算法实现后,对比维基百科的实现发现二者存在差异,想知道哪一种是正确的。

.NET 8 源码实现

public void Shuffle<T>(Span<T> values)
{
    int n = values.Length;

    for (int i = 0; i < n - 1; i++)
    {
        int j = Next(i, n);

        if (j != i)
        {
            T temp = values[i];
            values[i] = values[j];
            values[j] = temp;
        }
    }
}

维基百科 Fisher-Yates Inside-Out 变体

To initialize an array a of n elements to a randomly shuffled copy of source, both 0-based:
  for i from 0 to n − 1 do
      j ← random integer such that 0 ≤ j ≤ i
      if j ≠ i
          a[i] ← a[j]
      a[j] ← source[i]

二者的核心差异在于:.NET版本的交换操作完全在if代码块内,而维基百科版本的部分操作在if块外。


结论:两种实现都是正确的,只是适用场景不同

1. .NET 8 的原地洗牌变体

这是Fisher-Yates算法的原地打乱实现,专为直接修改原数组设计:

  • 循环遍历到倒数第二个元素(最后一个元素无需再交换)
  • 每次从[i, n-1]的范围内随机选取索引j,将其与i位置的元素交换
  • 当j=i时跳过交换(自身交换无意义,不影响概率分布)
    该实现的优势是不需要额外内存空间,所有操作都在原数组上完成,且能保证每个元素出现在任意位置的概率完全均等,是标准的公平原地洗牌实现。

2. 维基百科的 Inside-Out 变体

这是Fisher-Yates算法的非原地生成变体,用于从源数组创建一个新的打乱数组,不会修改原数据:

  • 循环遍历所有元素,每次从[0, i]的范围内随机选取索引j
  • 若j≠i,先将已生成的新数组中j位置的元素移到i位置
  • 再将源数组的source[i]放入新数组的j位置
    这种实现的核心是逐步构建新的乱序数组,每一步都保证已处理的元素分布均匀,适合需要保留原数组的场景,同样满足公平洗牌的要求。

两种实现的差异是为了适配不同的使用场景,但都严格遵循Fisher-Yates算法的核心逻辑——确保所有可能的排列组合出现的概率相等,因此都是正确的公平洗牌实现。


内容的提问来源于stack exchange,提问作者aybe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 14:45:57