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
相关产品推荐
相关产品推荐

