【问题标题】:Random Shuffling in Java (or any language) Probabilities [duplicate]Java(或任何语言)中的随机洗牌概率[重复]
【发布时间】:2025-12-03 06:50:02
【问题描述】:

所以,我正在 Coursera 上观看 Robert Sedgewick 的视频,而我目前正在 Shuffling 中。他展示了在线扑克的“写得不好”的洗牌代码(它还有一些其他错误,我已将其删除,因为它们与我的问题无关)这就是算法的工作原理:

for(int i = 0; i < N; i++)
int r = new Random().nextInt(53);
swap(cardArray, i, r);

它迭代所有卡片一次。在每次迭代中,都会生成一个随机数,并将第 i 个卡与第 r 个卡交换。很简单吧?

虽然我了解算法,但我不了解他的概率计算。他说,因为 Random 使用 32 位种子(或 64,这似乎无关紧要),所以这仅限于 2^32 种不同的排列。

他还说 Knuth 的算法更好(同样的 for 循环,但是选择一个介于 1 和 i 之间的数字)因为它给了你 N!排列。

我可以同意 Knuth 的算法计算。但我认为在第一个(应该是错误的)上应该有 N^N 不同的排列。

是塞奇威克错了还是我错过了一个事实?

【问题讨论】:

  • 虽然我不明白使用所有 Random(long seed) 种子位而不是其中的一半应该重要,但在每次迭代中实例化一个新的 Random 对象不仅浪费,而且每个人都有明确的种子,每个人都有义务提供相同的nextXyz()-Value。

标签: java algorithm random shuffle


【解决方案1】:

Sedgewick 的解释方式在我看来非常奇怪和迟钝。

假设您有一副只有 3 张牌的牌,并应用了所示的算法。

在交换第一张牌后,将有 3 种可能的结果。在第二次交换之后是 9。在第三次交换之后是 27。因此,我们知道使用交换算法我们将有 27 种不同的可能结果,其中一些将与其他结果重复。

现在,我们知道一副 3 张牌有 3 * 2 * 1 = 6 种可能的排列方式。然而,27 不能被 6 整除。因此,我们知道有些排列会比其他排列更常见,即使不计算它们是什么。因此,交换算法不会导致这6种可能性中的概率相等,即会偏向某些排列。

同样的逻辑延伸到 52 张卡片的情况。

我们可以通过查看三张牌案例中的结果分布来调查哪些安排是首选的,它们是:

1 2 3 5 次出现
1 3 2 5 次出现
2 1 3 4 次出现
2 3 1 4 次出现
3 1 2 4 次出现
3 2 1 5 次出现

总共 27 个

检查这些,我们注意到需要 0 或 1 次交换的组合比需要 2 次交换的组合出现更多。一般来说,组合所需的交换次数越少,可能性就越大。

【讨论】:

    【解决方案2】:

    由于随机数生成器生成的数字序列由种子唯一确定,因此该论点是正确的——但它也适用于 Knuth 算法以及任何其他洗牌算法:如果 N! > 2^M(其中 N 是卡片的数量,M 是种子中的位数),某些排列将永远不会生成。但即使种子足够大,算法之间的实际区别在于概率分布:the first algorithm does not produce an uniform probability for the different permutations, while Knuth's does(假设随机生成器足够“随机”)。请注意,Knuth 的算法也称为Fisher-Yates shuffle

    【讨论】:

      【解决方案3】:

      当然,Sedgwick 是对的。要获得真正的随机卡片顺序,您必须首先使用一种在 N 个中均等选择的算法!可能的排列,这意味着选择 N 之一、N-1 之一、N-2 之一等,并为每种组合产生不同的结果,例如 Fisher-Yates 算法。

      其次,PRNG 的内部状态必须大于 log2(N!) 位,否则它将在到达所有组合之前重复自身。对于 52 张卡,这是 226 位。 32 甚至不接近。

      【讨论】:

        【解决方案4】:

        对不起,我不同意 Aasmund 和 Lee Daniel 的回答。 N 个元素的每个排列可以表示为 1 和 1 和 N 之间的某个索引 i 之间的 3(N - 1) 个转置(这很容易通过对 N 的归纳来证明 - 见下文)因此,为了生成随机排列,它足以生成 3(N-1) 个介于 1 和 N 之间的随机整数。换句话说,你的随机生成器只需要能够生成 3(N-1) 个不同的整数。



        定理

        {1, ..., N}的每一个排列都可以表示为N-1个转置的组合

        证明(通过对 N 的归纳)

        案例 N = 1。

        {1}的唯一排列是(1),可以写成0个转置的组合(0个元素的组合就是恒等式)

        CASE N = 2。(仅适用于不被上述案例 N = 1 说服的人)

        有 2 个元素 (1,2) 和 (2,1) 的两种排列。排列(1,2)是1与1的转置。排列(2,1)是1和2的转置。

        归纳 N -> 案例 N + 1。

        取 {1, ..., N, N+1} 的任意排列 s。如果 s 不移动 N+1,那么 s 实际上是 {1, ..., N} 的置换,并且可以写成索引 i,j 之间的 N-1 个转置的组合,其中 1

        所以让我们假设 s 将 N+1 移动到 K。让 t 在 N+1 和 K 之间进行换位。那么 ts 不会移动 N+1 (N+1 -> K -> N+1),因此ts 可以写成 N-2 个转置的组合,即

        ts = t1..tN-1.

        因此,s = t1..tN-1t

        由 N 个转置组成(比 N+1 少一个)。


        推论

        {1, ..., N} 的每个排列都可以写成 1 和 i 之间的(最多)3(N-1) 个排列的组合,其中 1

        证明

        根据定理,足以证明两个索引 i 和 j 之间的任何转置都可以写成 1 和某个索引之间的 3 个转置的组合。但是

        swap(i,j) = swap(1,i)swap(1,j)swap(1,i)

        其中交换的串联是这些转置的组合。

        【讨论】:

        • 我们并不是说您需要那么多数字来产生所有可能的结果——事实上,Fisher-Yates 只使用 N-1 个随机数,而不是 3(N-1) 个。问题是这些结果中的每一个是否以相等的概率发生,以及 PRNG 是否能够在没有相关性的情况下产生所有这些结果,并且有足够的时间使每个结果都是等概率的。这是一个非常不同的问题。