[英]Explanation of card shuffling algorithm (java)
我試圖在常見的 Java 洗牌算法中更好地理解這段代碼:
// Random for remaining positions.
int r = i + rand.nextInt(52 - i);
為什么需要“填充”或將i
索引添加到結果隨機數中? 看起來,當您迭代並添加i
,通過減去i
,您可以將最大可能的隨機數范圍保持在 0 到 51 之間,但為什么不這樣做:
int r = rand.nextInt(52);
完整代碼:
// Function which shuffle and print the array
public static void shuffle(int card[], int n)
{
Random rand = new Random();
for (int i = 0; i < n; i++)
{
// Random for remaining positions.
int r = i + rand.nextInt(52 - i);
//swapping the elements
int temp = card[r];
card[r] = card[i];
card[i] = temp;
}
}
Fisher-Yates shuffle 的工作原理如下:
這是您要問的“剩余價值”部分。
例如,經過 10 次迭代后,您已將 10 個隨機值交換到數組的前 10 個位置,因此對於下一次迭代,您需要在范圍 10-end 中的隨機位置,因此從小於完整的 10 隨機范圍的偏移量 10范圍,又名i + rand.nextInt(52 - i)
。
其他答案沒有解決您的問題“為什么不直接做: int r = rand.nextInt(52);
”。 這有時被稱為 naive shuffle,答案是因為這會導致有偏差的 shuffle。
理想情況下,您希望結果的數量反映同樣可能結果所涉及的概率計算。 這意味着洗牌后的第一張牌可以是 52 張牌中的任意一張,第二張牌可以是剩余的 51 張牌中的任意一張,第三張牌可以是剩余的 50 張牌中的任意一張,……換句話說,有A = 52*51*50*...*3*2*1
(即,52 階乘)可能的卡片排列。 這就是 Fisher-Yates shuffle 及其變體所做的。 但是,如果您按照您的建議選擇任何一張牌在第 i次迭代中移動,則會生成 C = 52 52 個案例。 結果是有偏見的洗牌。
為了說明在幼稚洗牌中偏差是如何以及為什么會出現的,請考慮一副小得多的 3 張牌。 在這種情況下,A = 3*2*1
= 6 個排列,而 C = 3 3 = 27 是到達最終排列的路徑數。 為什么這是個問題? 因為鴿子洞原理。 C 不是 A 的整數倍,所以如果我們把 C 看作鴿子,把 A 看作鴿子洞,那么某些洞一定比其他洞得到更多的鴿子。 用改組的術語來說,獲得某些安排的途徑比其他安排多。 因此,並非所有安排都會同樣頻繁地出現,因此結果不是“公平”的洗牌。
如果您通過分析來解決它並從初始化為['a','b','c']
的數組開始,您將在使用 naive shuffle 時發現以下概率:
outcome probability
------- -----------
['a','b','c'] 4/27
['a','c','b'] 5/27
['b','a','c'] 5/27
['b','c','a'] 5/27
['c','a','b'] 4/27
['c','b','a'] 4/27
而通過無偏洗牌,6 種可能排列中每一種的概率為 1/6。 這就是創建 Fisher-Yates 算法的原因。
您的代碼所做的是創建牌組的洗牌。 在每次迭代中,它會隨機抽取一張牌並將其放回牌組的前面,從 i=0 到 i=n。
如果你會使用int r = rand.nextInt(52);
這意味着在每次迭代中,您都可以取回任何卡片,即使是在牌組開頭的那些已經是牌組新順序的一部分的牌。
通過減去 i 你只選擇剩下的 52-i 張牌中的一張牌,然后你需要加上 +i 以獲得它的實際位置,因為你已經將牌組開頭的前 i 張牌設置為新牌洗牌。
例如,假設您已經在新位置拿到了前 10 張卡片。 現在你需要拿到第 11 張牌,所以你從剩下的 52-10=42 張牌中隨機拿一張牌。 假設我們取回了數字 5,它不是card[4]
的卡片,而是card[10+4]
處的card[10+4]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.