首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

演算法逐步推導(一):洗牌演算法 — 全域亂序 vs 費雪-耶茨優化版

     編輯:關於C

問題背景

撲克牌 54 張,如何用 rand() 產生均勻隨機排列?介紹兩種演算法演進。

演算法一:全域洗牌法 (Naive Shuffle)

邏輯:對每個位置 i,從 全範圍 [0, 53] 隨機選一張交換。


void shuffle_naive(int *deck, int n) {
    for (int i = 0; i < n; i++) {
        int r = rand() % n;          // 0 ~ n-1
        int tmp = deck[i];
        deck[i] = deck[r];
        deck[r] = tmp;
    }
}

缺陷:機率分佈不均勻。總排列數 n!,但此演算法產生 n^n 種等概率路徑,n^n 無法整除 n! (n>2 時),導致某些排列機率較高。且已洗好的牌會被再次交換,邏輯不直觀。

演算法二:費雪-耶茨洗牌法 / Knuth Shuffle (最佳解)

核心洞見:第 i 張牌只需從 剩餘未洗牌 [i, n-1] 中隨機選一張交換。已洗區段 [0, i-1] 確定不再動。


void shuffle_fisher_yates(int *deck, int n) {
    for (int i = 0; i < n; i++) {
        int r = i + rand() % (n - i);  // i ~ n-1
        int tmp = deck[i];
        deck[i] = deck[r];
        deck[r] = tmp;
    }
}

數學證明均勻性

  • 第 0 張:從 n 張中等概率選 1 張 → 1/n
  • 第 1 張:從剩餘 n-1 張中等概率選 1 張 → 1/(n-1)
  • ...
  • 任意排列機率 = 1/n × 1/(n-1) × ... × 1/1 = 1/n! ✓

關鍵細節與避坑

陷阱後果修正
rand() % n 模數偏差RAND_MAX 不被 n 整除時,低數值機率略高用拒絕採樣或 arc4random_uniform / C++11 uniform_int_distribution
未設定種子每次執行序列相同srand(time(NULL)) 或更強熵源
迴圈寫成 i < n-1最後一張牌永不動i < n (最後一次 r=n-1 自我交換無害)

現代 C++ 標準庫一鍵調用


#include 
#include 

std::random_device rd;
std::mt19937 g(rd());
std::shuffle(deck, deck + n, g);  // 內部即 Fisher-Yates

教訓:直觀的「全域隨機交換」是錯的。演算法設計必須嚴謹證明機率分佈。Fisher-Yates 以 O(n) 時間、O(1) 空間、嚴格均勻,成為洗牌演算法的黃金標準。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved