【问题标题】:Correctness of Fisher-Yates Shuffle Executed Backward向后执行的 Fisher-Yates 洗牌的正确性
【发布时间】:2021-09-04 21:48:23
【问题描述】:

根据 wikipedia 以及 Java 标准库的实现,改组 ​​https://en.wikipedia.org/wiki/Fisher–Yates_shuffle(Fisher Yates Shuffle)的工作方式如下:

算法 A:

-- To shuffle an array a of n elements (indices 0..n-1):
for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

或等效

算法 B:

-- To shuffle an array a of n elements (indices 0..n-1):
for i from 0 to n−2 do
     j ← random integer such that i ≤ j < n
     exchange a[i] and a[j]

我的问题是针对以下问题(算法 C):

算法 C:

-- To shuffle an array a of n elements (indices 0..n-1):
for i from 1 to n−1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[i] and a[j]

算法 A 和算法 B 完全相同。但是 Algo C 与 Algo A 和 Algo B 不同(实际上 Algo C 是 Algo A 反向执行)

算法 C 正确吗?我很迷茫。我使用列联表做了一些卡方检验,看起来这给出了明显正确的统一顺序。

我的问题是算法 C 是否正确?如果正确,为什么几乎看不到哪里?为什么 F-Y shuffle 到处都是同一个方向。

【问题讨论】:

  • 好吧。有人跟我一样想。 possiblywrong.wordpress.com/2020/12/10/…我认为 Algo C 是正确的。
  • 深奥:为什么要排除 A 和 C 中的索引零?还有exchange a[i] and a[j]当然和exchange a[j] and a[i]是一样的。
  • 嗯,当i = 0时,j肯定是0。不需要交换它们。
  • Algo C 肯定不是你想要的。对于 n=2,它将始终返回原始数组的反转,而不是原始数组。您确定这是您希望我们查看的代码吗?
  • @congyuwang:是的,但i == j 的任何时候都是如此,不是吗?

标签: algorithm computer-science probability shuffle pseudocode


【解决方案1】:

是的,这个算法是正确的,因为它有一个loop invariant,第一个i 元素的每个排列都是同样可能的。当i = 1 时,最初满足不变量,因为一个元素只有一个可能的排列,然后当i = n(即在循环的最后一次迭代之后)时,整个数组的每个排列都是同样可能的。

要了解为什么不变量成立,我们只需要考虑循环的单次迭代。假设第一个i 元素具有所有排列的可能性相同,我们将第一个未混洗的元素(称为x)与直到或包括其自身的随机索引交换。现在考虑初始数组的第一个 i + 1 元素的任意两个排列 P1 和 P2:让 Q1 和 Q2 是第一个 i 元素的排列,这将分别将 P1 和 P2 中的 x 交换为索引 @ 987654330@。由于根据归纳假设,Q1 和 Q2 的可能性相同,并且两种交换的可能性相同,并且 P1 或 P2 发生的唯一方式是分别从 Q1 或 Q2 开始的这些交换,因此得出P1 和 P2 同样可能是结果。


所以你的算法是正确的,但可能不像 Fisher-Yates 那样广为人知,因为它与 Fisher-Yates 相比没有优势,而且不太明显正确。另外值得注意的是,Fisher-Yates 很容易在 O(k) 时间内从数组中统一采样k &lt; n 不同的元素,而您的算法不能以这种方式进行调整。

【讨论】:

    【解决方案2】:

    专注于为什么它没有被更多人看到(因为其他答案已经表明它是正确的)。

    变体在各种情况下可用作优化:

    • 如果您只需要一些随机元素,算法 B 会很有用。想想洗一副牌并分发几手牌,然后收集所有牌重新洗牌。使用算法 B,您不必洗牌。
    • 算法 A 很有用,因为您可以跳过初始化,https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_%22inside-out%22_algorithm
    • 如果集合在高级时未知,Algo C 会很有用,但这似乎很深奥。 (所以你随机洗5张牌,然后得到一张新牌,再走一步,然后你就有6张洗牌等)

    这些额外的用途意味着算法 B 和 A 将被编码得更多,甚至在其他情况下也会使用。

    【讨论】:

    • 太棒了。这说得通。很多应用程序也有 sample(k) API,所以 Algo A 和 Algo B 支持以等概率从 n 中采样 k,而 Algo C 不能。
    猜你喜欢
    • 2013-10-26
    • 1970-01-01
    • 2017-07-08
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    • 2019-03-06
    • 2011-03-21
    相关资源
    最近更新 更多