为什么这个简单的洗牌算法会产生有偏差的结果?
看来这个简单的洗牌算法会产生有偏差的结果:
# suppose $arr is filled with 1 to 52
for ($i < 0; $i < 52; $i++) {
$j = rand(0, 51);
# swap the items
$tmp = $arr[j];
$arr[j] = $arr[i];
$arr[i] = $tmp;
}
你可以尝试一下......而不是使用 52,使用 3(假设只使用 3 张牌),并运行它 10,000 次并统计结果,你会看到结果偏向于某些模式...
问题是...对于为什么会发生这种情况有什么简单的解释?
正确的解决方案是使用类似的方法
for ($i < 0; $i < 51; $i++) { # last card need not swap
$j = rand($i, 51); # don't touch the cards that already "settled"
# swap the items
$tmp = $arr[j];
$arr[j] = $arr[i];
$arr[i] = $tmp;
}
但问题是......为什么第一种方法看似完全随机,却使结果产生偏差?
更新1:感谢这里的人们指出它需要是 rand($i, 51) 才能正确洗牌。
It seems that this simple shuffle algorithm will produce biased results:
# suppose $arr is filled with 1 to 52
for ($i < 0; $i < 52; $i++) {
$j = rand(0, 51);
# swap the items
$tmp = $arr[j];
$arr[j] = $arr[i];
$arr[i] = $tmp;
}
You can try it... instead of using 52, use 3 (suppose only 3 cards are used), and run it 10,000 times and tally up the results, you will see that the results are skewed towards certain patterns...
The question is... what is a simple explanation for why it will happen?
The correct solution is to use something like
for ($i < 0; $i < 51; $i++) { # last card need not swap
$j = rand($i, 51); # don't touch the cards that already "settled"
# swap the items
$tmp = $arr[j];
$arr[j] = $arr[i];
$arr[i] = $tmp;
}
But the question is... why does the first method, seemingly also totally random, make the results biased?
Update 1: thanks for folks here pointing out that it needs to be rand($i, 51) for it to shuffle correctly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
并不是说需要另一个答案,但我发现尝试弄清楚为什么 Fisher-Yates 是统一的是值得的。
如果我们谈论的是有 N 个项目的牌组,那么这个问题是:我们如何证明
用条件概率将其分解,
Pr(item i end up at slot j)
等于并且来自在那里它递归地扩展到第一次绘制。
现在,第一次绘制时元素
i
未绘制的概率为N-1 / N
。 第二次抽奖时未抽奖的概率以第一次抽奖时未抽奖为条件为N-2 / N-1
,依此类推。因此,我们得到元素
i
在第一个j-1
绘制中未绘制的概率:当然我们知道它在第
j-1
轮绘制的概率 < code>j 以之前未绘制为条件只是1 / Nj
。请注意,在第一项中,分子全部抵消了后续分母(即
N-1
抵消、N-2
抵消,一直到N-j +1
取消,只剩下Nj / N
)。因此元素
i
出现在槽j
中的总体概率为:正如预期的那样。
为了更笼统地了解“简单随机播放”,它所缺少的特定属性称为可交换性 。 由于创建洗牌方式的“路径依赖性”(即遵循 27 条路径中的哪一条来创建输出),您无法将不同的组件随机变量视为可以以任何顺序出现。 事实上,这也许是解释为什么可交换性在随机抽样中很重要的一个例子。
Not that another answer is needed, but I found it worthwhile to try to work out exactly why Fisher-Yates is uniform.
If we are talking about a deck with N items, then this question is: how can we show that
Breaking it down with conditional probabilities,
Pr(item i ends up at slot j)
is equal toand from there it expands recursively back to the first draw.
Now, the probability that element
i
was not drawn on the first draw isN-1 / N
. And the probability that it was not drawn on the second draw conditional on the fact that it was not drawn on the first draw isN-2 / N-1
and so on.So, we get for the probability that element
i
was not drawn in the firstj-1
draws:and of course we know that the probability that it is drawn at round
j
conditional on not having been drawn earlier is just1 / N-j
.Notice that in the first term, the numerators all cancel the subsequent denominators (i.e.
N-1
cancels,N-2
cancels, all the way toN-j+1
cancels, leaving justN-j / N
).So the overall probability of element
i
appearing in slotj
is:as expected.
To get more general about the "simple shuffle", the particular property that it is lacking is called exchangeability. Because of the "path dependence" of the way the shuffle is created (i.e. which of the 27 paths is followed to create the output), you are not able to treat the different component-wise random variables as though they can appear in any order. In fact, this is perhaps the motivating example for why exchangeability matters in random sampling.
显示第一个算法失败的最清晰答案是将所讨论的算法视为 n 个图上的 n 个步骤的马尔可夫链! n 个自然数的所有排列的顶点。 该算法以转移概率从一个顶点跳到另一个顶点。 第一个算法给出每一跳的转移概率为
1/n
。 有 n^n 条路径,每条路径的概率为 1/n^n。 假设降落在每个顶点上的最终概率是1/n!
,这是一个约简分数。 要实现这一点,必须有 m 条路径具有相同的最终顶点,对于某个自然数,m/n^n=1/n!
或n^n = mn!
m
,或者n^n
能被n!
整除。 但这是不可能的。 否则,n 必须能被n-1
整除,这只有在n=2
时才有可能。 我们有矛盾。The clearest answer to show the first algorithm fails is to view the algorithm in question as a Markov chain of n steps on the graph of n! vertices of all the permutation of n natural numbers. The algorithm hops from one vertex to another with a transition probability. The first algorithm gives the transition probability of
1/n
for each hop. There are n^n paths the probability of each of which is1/n^n
. Suppose the final probability of landing on each vertex is1/n!
which is a reduced fraction. To achieve that there must be m paths with the same final vertex such thatm/n^n=1/n!
orn^n = mn!
for some natural numberm
, or thatn^n
is divisible byn!
. But that is impossible. Otherwise, n has to be divisible byn-1
which is only possible whenn=2
. We have contradiction.Naive 算法如下选择 n 的值:
n = rand(3)
n = rand(3)
n = rand(3)
的可能组合
3^3 n 1,1,1, 1,1,2... .3,3,2 3,3,3(27种组合)lassevk的答案显示了这些组合的卡片之间的分布。
更好的算法是:
n = rand(3)
n = rand(2)
n! n 1,1, 1,2, 2,1 2,2 3,1 3,2 的可能组合
(6 种组合,所有组合都给出不同的结果)
正如其他答案中提到的,如果您尝试 27 次才能得到6 个结果,你不可能获得均匀分布的 6 个结果,因为 27 不能被 6 整除。将 27 个弹珠放入 6 个桶中,无论你做什么,有些桶会比其他桶有更多的弹珠,你能做的最好的就是4,4,4,5,5,5 个弹珠用于桶 1 到 6。
天真的洗牌的根本问题是交换太多次,要完全洗 3 张牌,你只需要进行 2 次交换,第二次交换需要只能在前两张牌中,因为第三张牌已经有 1/3 的机会被交换。 继续交换卡将增加给定卡被交换的机会,并且如果您的总交换组合可以被 6 整除,则这些机会只会均匀为 1/3、1/3、1/3。
The Naive algorithm picks the values of n like so:
n = rand(3)
n = rand(3)
n = rand(3)
3^3 possible combinations of n
1,1,1, 1,1,2....3,3,2 3,3,3 (27 combinations) lassevk's answer shows the distribution among the cards of these combinations.
the better algorithm does:
n = rand(3)
n = rand(2)
n! possible combinations of n
1,1, 1,2, 2,1 2,2 3,1 3,2 (6 combinations, all of them giving a different result)
As mentioned in the other answers, if you take 27 attempts to get 6 results, you cannot possibly attain the 6 results with even distribution, since 27 is not divisible by 6. Put 27 marbles into 6 buckets and no matter what you do, some buckets will have more marbles than others, the best you can do is 4,4,4,5,5,5 marbles for buckets 1 through 6.
the fundamental problem with the naive shuffle is that swaps too many times, to shuffle 3 cards completely, you need only do 2 swaps, and the second swap need only be among the first two cards, since the 3rd card already had a 1/3 chance of being swapped. to continue to swap cards will impart more chances that a given card will be swapped, and these chances will only even out to 1/3, 1/3, 1/3 if your total swap combinations is divisible by 6.
一个说明性的方法可能是这样的:
1)仅考虑 3 张牌。
2) 为了使算法给出均匀分布的结果,“1”最终成为 a[0] 的机会必须是 1/3,“2”最终成为 a[1] 的机会也必须是 1/3 ,等等。
3)所以如果我们看第二个算法:
4)如果我们尝试计算第一个算法中“1”最终成为a[0]的概率,计算会有点长,但正如lassevk的答案中的插图所示,它是9/27 = 1 /3,但“2”最终成为 a[1] 的几率为 8/27,而“3”最终成为 a[2] 的几率为 9/27 = 1/3。
因此,“2”最终作为 a[1] 不是 1/3,因此该算法将产生相当倾斜的结果(大约 3.7% 的错误,而不是任何可忽略的情况,例如 3/10000000000000 = 0.00000000003%)
5 )Joel Coehoorn 的证据,实际上可以证明有些案例会被过度代表。 我认为为什么是n^n的解释是这样的:在每次迭代时,随机数有n种可能性,因此在n次迭代之后,可以有n^n个情况= 27。这个数字不能整除在 n = 3 的情况下,排列数 (n!= 3!= 6) 均匀,因此某些结果被过度表示。 他们的代表性过高,不是出现 4 次,而是出现 5 次,因此,如果您从最初的 1 到 52 顺序洗牌数百万次,那么代表性过高的案例将显示 500 万次次与400万次相比,差距相当大。
6)我认为存在过度代表性,但是“为什么”会发生过度代表性?
7) 算法正确性的最终测试是任何数字都有 1/n 的概率出现在任何位置。
an illustrative approach might be this:
1) consider only 3 cards.
2) for the algorithm to give evenly distributed results, the chance of "1" ending up as a[0] must be 1/3, and the chance of "2" ending up in a[1] must be 1/3 too, and so forth.
3) so if we look at the second algorithm:
4) if we try to calculate the probability of of "1" ending up as a[0] in the first algorithm, the calculation will be a bit long, but as the illustration in lassevk's answer shows, it is 9/27 = 1/3, but "2" ending up as a[1] has a chance of 8/27, and "3" ending up as a[2] has a chance of 9/27 = 1/3.
as a result, "2" ending up as a[1] is not 1/3 and therefore the algorithm will produce pretty skewed result (about 3.7% error, as opposed to any negligible case such as 3/10000000000000 = 0.00000000003%)
5) the proof that Joel Coehoorn has, actually can prove that some cases will be over-represented. I think the explanation that why it is n^n is this: at each iteration, there are n possibility that the random number can be, so after n iterations, there can be n^n cases = 27. This number doesn't divid the number of permuations (n! = 3! = 6) evenly in the case of n = 3, so some results are over-represented. they are over-represented in a way that instead of showing up 4 times, it shows up 5 times, so if you shuffle the cards millions of times from the initial order of 1 to 52, the over-represented case will show up 5 million times as opposed to 4 million times, which is quite big a difference.
6) i think the over-representation is shown, but "why" will the over-representation happen?
7) an ultimate test for the algorithm to be correct is that any number has a 1/n probability to end up at any slot.
简单的答案是,这个算法有 52^52 种可能的运行方式,但只有 52 种! 52 张牌的可能排列。 为了使算法公平,它需要以同等的可能性产生这些排列。 52^52 不是 52! 的整数倍。 因此,某些安排必定比其他安排更有可能发生。
The simple answer is that there are 52^52 possible ways for this algorithm to run, but there are only 52! possible arrangements of 52 cards. For the algorithm to be fair, it needs to produce each of these arrangements equally likely. 52^52 is not an integer multiple of 52!. Therefore, some arrangements must be more likely than others.
请参阅编码恐怖文章天真的危险。
基本上(假设有3张牌):
See the Coding Horror post The Danger of Naïveté.
Basically (suposing 3 cards):
这是另一个直觉:除非至少已经存在双向对称性,否则单次洗牌交换无法在占据位置的概率上创建对称性。 称这三个位置为 A、B 和 C。现在设 a 为卡 2 位于位置 A 的概率,b 为卡 2 位于位置 B 的概率,c 为卡 2 位于位置 C 的概率,先验进行交换移动。 假设没有两个概率相同:a!=b、b!=c、c!=a。 现在计算交换后卡处于这三个位置的概率 a'、b' 和 c'。 假设此交换移动由位置 C 与三个位置之一随机交换组成。 那么:
也就是说,卡片最终出现在位置 A 的概率是它已经在位置 A 的概率乘以位置 A 不参与交换的时间的 2/3,加上它在位置 C 的概率乘以C 与 A 交换的 1/3 概率,等等。现在减去前两个方程,我们得到:
这意味着因为我们假设 a!=b,则 a'!=b' (尽管随着时间的推移,差异将接近 0 ,给予足够的交换)。 但由于a'+b'+c'=1,如果a'!=b',那么两者都不可能等于c',即1/3。 因此,如果这三个概率在交换之前开始时全部不同,那么在交换之后它们也将全部不同。 无论交换哪个位置,这都会成立 - 我们只是交换上面变量的角色。
现在,第一次交换开始于将位置 A 的卡 1 与其他卡交换。 在这种情况下,交换之前存在双向对称性,因为卡 1 在位置 B 的概率 = 卡 1 在位置 C 的概率 = 0。所以事实上,卡 1 可以以对称概率结束,并且最终会出现三个位置中的每一个都有相同的概率。 这对于所有后续交换都是如此。 但是卡 2 在第一次交换后以概率 (1/3, 2/3, 0) 出现在三个位置上,同样,卡 3 以概率 (1/3, 0, 2/3) 出现在三个位置上。 因此,无论我们进行多少次后续交换,我们都不会最终得到卡 2 或 3 拥有完全相同的占据所有三个位置的概率。
Here's another intuition: the single shuffle swap can't create symmetry in the probability of occupying a position unless at least 2-way symmetry already exists. Call the three positions A, B, and C. Now let a be the probability of card 2 being in position A, b be the probability of card 2 being in position B, and c be the probability of it being in position C, prior to a swap move. Assume that no two probabilities are the same: a!=b, b!=c, c!=a. Now compute the probabilities a', b', and c' of the card being in these three positions following a swap. Let's say that this swap move consists of position C being swapped with one of the three positions at random. Then:
That is, the probability that the card winds up in position A is the probability it was already there times the 2/3 of the time position A isn't involved in the swap, plus the probability that it was in position C times the 1/3 probability that C swapped with A, etc. Now subtracting the first two equations, we get:
which means that because we assumed a!=b, then a'!=b' (though the difference will approach 0 over time, given enough swaps). But since a'+b'+c'=1, if a'!=b', then neither can be equal to c' either, which is 1/3. So if the three probabilities start off all different before a swap, they will also all be different after a swap. And this would hold no matter which position was swapped - we just interchange the roles of the variables in the above.
Now the very first swap started by swapping card 1 in position A with one of the others. In this case, there was two way symmetry before the swap, because the probability of card 1 in position B = probability of card 1 in position C = 0. So in fact, card 1 can wind up with symmetric probabilities and it does end up in each of the three positions with equal probability. This remains true for all subsequent swaps. But card 2 winds up in the three positions after the first swap with probability (1/3, 2/3, 0), and likewise card 3 winds up in the three positions with probability (1/3, 0, 2/3). So no matter how many subsequent swaps we do, we will never wind up with card 2 or 3 having exactly the same probability of occupying all three positions.
从您对其他答案的评论来看,您似乎不仅在寻找解释为什么分布不是均匀分布(对于它的整除性答案是一个简单的),而且还在寻找“直观”解释了为什么它实际上远非统一。
这是一种看待它的方法。 假设您从初始数组
[1, 2, ..., n]
(其中 n 可能是 3、52 或其他)开始,并应用两种算法之一。 如果所有排列的可能性一致,则 1 保留在第一个位置的概率应为1/n
。 事实上,在第二个(正确的)算法中,它是1/n
,因为当且仅当它第一次没有被交换时 1 才会保留在它的位置,即当且仅当对rand(0,n-1)
的初始调用返回 0。然而,在第一个(错误的)算法中,只有在第一次和都没有被交换,或者任何其他时间都没有被交换的情况下,1才保持不变——即,只有当第一个< code>rand 返回 0,并且其他
rand
没有一个返回 0,其概率为 (1/n) * (1-1/n )^(n-1) ≈ 1/(ne) ≈ 0.37/n,而不是 1/n。这就是“直观”的解释:在您的第一个算法中,较早的项目比较晚的项目更有可能被替换,因此您获得的排列偏向于早期项目的模式 不在原来的位置。
(比这更微妙,例如 1 可以交换到稍后的位置,但最终仍然通过一系列复杂的交换交换回原来的位置,但是那些概率相对较小。)
From your comments on the other answers, it seems that you are looking not just for an explanation of why the distribution is not the uniform distribution (for which the divisibility answer is a simple one) but also an "intuitive" explanation of why it is actually far from uniform.
Here's one way of looking at it. Suppose you start with the initial array
[1, 2, ..., n]
(where n might be 3, or 52, or whatever) and apply one of the two algorithms. If all permutations are uniformly likely, then the probability that 1 remains in the first position should be1/n
. And indeed, in the second (correct) algorithm, it is1/n
, as 1 stays in its place if and only if it is not swapped the first time, i.e. iff the initial call torand(0,n-1)
returns 0.However, in the first (wrong) algorithm, 1 remains untouched only if it is neither swapped the first time nor any other time — i.e., only if the first
rand
returns 0 and none of the otherrand
s returns 0, the probability of which is (1/n) * (1-1/n)^(n-1) ≈ 1/(ne) ≈ 0.37/n, not 1/n.And that's the "intuitive" explanation: in your first algorithm, earlier items are much more likely to be swapped out of place than later items, so the permutations you get are skewed towards patterns in which the early items are not in their original places.
(It's a bit more subtle than that, e.g. 1 can get swapped into a later position and still end up getting swapped back into place through a complicated series of swaps, but those probabilities are relatively less significant.)
我见过的对此效果的最佳解释来自 Jeff Atwood 在他的 CodingHorror 博客 (天真的危险)。
使用此代码模拟 3 张牌随机洗牌...
...您将得到此分布。
(来源:typepad.com)
随机播放代码(上图)产生 3^3 (27) 种可能的牌组组合。 但数学告诉我们,实际上只有 3 个! 或 3 张牌的 6 种可能组合。 因此,某些组合的代表性过高。
您需要使用 Fisher-Yates shuffle 来正确(随机) )洗一副牌。
The best explanation I've seen for this effect was from Jeff Atwood on his CodingHorror blog (The Danger of Naïveté).
Using this code to simulate a 3-card random shuffle...
...you get this distribution.
(source: typepad.com)
The shuffle code (above) results in 3^3 (27) possible deck combinations. But the mathematics tell us that there are really only 3! or 6 possible combinations of a 3 card deck. So some of the combinations are over-represented.
You would need to use a Fisher-Yates shuffle to properly (randomly) shuffle a deck of cards.
请参阅此:
天真的危险(编码恐怖)
让我们看看你的三张牌:一个例子。 使用 3 张牌的牌组,洗牌后该牌组只有 6 种可能的顺序:
123、132、213、231、312、321。
使用第一个算法,有 27 种可能的路径(结果)对于代码,取决于
rand()
函数在不同点的结果。 这些结果中的每一个都是同等可能的(无偏见的)。 这些结果中的每一个都将映射到上面 6 个可能的“真实”洗牌结果列表中的相同单个结果。 我们现在有 27 个项目和 6 个桶来放入它们。由于 27 不能被 6 整除,因此这 6 个组合中的某些组合必须被过度表示。对于第二种算法,有 6 种可能的结果与 6 种可能的“真实”洗牌结果精确映射,并且随着时间的推移,它们都应该被平等地表示。
这很重要,因为第一个算法中过多表示的桶不是随机的。 为偏差选择的存储桶是可重复且可预测的。因此,如果您正在构建在线扑克游戏并使用第一种算法,黑客可能会发现您使用了朴素排序,并从中计算出某些甲板布置比其他布置更有可能发生。 然后他们就可以相应地下注。 他们会失去一些,但他们赢得的会比失去的多得多,并且很快就会让你破产。
See this:
The Danger of Naïveté (Coding Horror)
Let's look at your three card deck as an example. Using a 3 card deck, there are only 6 possible orders for the deck after a shuffle:
123, 132, 213, 231, 312, 321.
With your 1st algorithm there are 27 possible paths (outcomes) for the code, depending on the results of the
rand()
function at different points. Each of these outcomes are equally likely (unbiased). Each of these outcomes will map to the same single result from the list of 6 possible "real" shuffle results above. We now have 27 items and 6 buckets to put them in. Since 27 is not evenly divisible by 6, some of those 6 combinations must be over-represented.With the 2nd algorithm there are 6 possible outcomes that map exactly to the 6 possible "real" shuffle results, and they should all be represented equally over time.
This is important because the buckets that are over-represented in the first algorithm are not random. The buckets selected for the bias are repeatable and predictable. So if you're building an online poker game and use the 1st algorithm a hacker could figure out you used the naive sort and from that work out that certain deck arrangements are much more likely to occur than others. Then they can place bets accordingly. They'll lose some, but they'll win much more than they lose and quickly put you out of business.
这是这些替换的完整概率树。
假设您从序列 123 开始,然后我们将枚举使用相关代码生成随机结果的所有各种方法。
现在,第四列数字,即交换信息之前的数字,包含最终结果,有 27 种可能的结果。
让我们计算一下每种模式出现的次数:
如果您运行随机交换的代码无限次,则模式 132、213 和 231 会比模式 123、312 和 321 更频繁地出现,原因很简单:代码交换使得这种情况更有可能发生。
当然,现在您可以说,如果您运行代码 30 次 (27 + 3),则最终所有模式可能会出现 5 次,但是在处理统计数据时,您必须着眼于长期趋势。
下面是探索每种可能模式之一的随机性的 C# 代码:
输出:
因此,虽然这个答案实际上很重要,但它不是一个纯粹的数学答案,您只需评估随机函数的所有可能方式,然后查看最终输出。
Here's the complete probability tree for these replacements.
Let's assume that you start with the sequence 123, and then we'll enumerate all the various ways to produce random results with the code in question.
Now, the fourth column of numbers, the one before the swap information, contains the final outcome, with 27 possible outcomes.
Let's count how many times each pattern occurs:
If you run the code that swaps at random for an infinite number of times, the patterns 132, 213 and 231 will occur more often than the patterns 123, 312, and 321, simply because the way the code swaps makes that more likely to occur.
Now, of course, you can say that if you run the code 30 times (27 + 3), you could end up with all the patterns occuring 5 times, but when dealing with statistics you have to look at the long term trend.
Here's C# code that explores the randomness for one of each possible pattern:
This outputs:
So while this answer does in fact count, it's not a purely mathematical answer, you just have to evaluate all possible ways the random function can go, and look at the final outputs.