我們先看一下紙牌遊戲。一幅紙牌由 52 張不同的紙牌組成,發牌時必須產生不重複的紙牌,而且洗牌過程必須公平,即 52! 中紙牌順序應該等機率出現。很明顯這種隨機排列所產生的隨機數必須均勻分布且獨立。由此代碼如下:
- using System;
- using System.Diagnostics;
- namespace Lucifer.CSharp.Sample
- {
- class Program
- {
- static void Main(string[] args)
- {
- //初始化牌局
- int[] array = new int[52];
- for (int i = 0; i < array.Length; i++)
- {
- array = i;
- }
- //洗牌
- Permute<int>(array);
- //驗證牌局
- for (int i = 0; i < array.Length; i++)
- {
- var value = array;
- for (int j = 0; j < array.Length; j++)
- {
- if (j == i) continue;
- Debug.Assert(array[j] != value);
- }
- }
- }
- static void Permute<T>(T[] array)
- {
- Random random = new Random();
- for (int i = 1; i < array.Length; i++)
- {
- Swap<T>(array, i, random.Next(0, i));
- }
- }
- static void Swap<T>(T[] array, int indexA, int indexB)
- {
- T temp = array[indexA];
- array[indexA] = array[indexB];
- array[indexB] = temp;
- }
- }
- }
複製代碼
程式碼範例中的 Permute<T>(T[] array) 方法產生一個隨機序列。第一個迴圈用 1, 2, 3, …, N 初始化該序列。第二個迴圈完成一次隨機洗牌。在該迴圈的每次迭代中,我們講 array[j] 的值於數組位置在區間[0, j)之間的某個元素相交換(也可能不交換)。
然而,我們要問的是 Permute<T>(T[] array) 方法產生的所有排列等機率嗎?
若根據該演算法,答案為是。因為一共有 N! 種可能的排列,而 Swap<T>(array, i, random.Next(0, i)); 這一句 N-1 次調用 Next 方法出現的不同結果也是 N! 種。但是,事實上答案為否,並非所有排列都是等機率。問題就出在可愛的偽隨機數數產生器(Pseudo-Random Number Generator)上。PRNG 的隨機性很大程度上限制了隨機序列的隨機性。所以,上述代碼需要一個更好的偽隨機數數產生器以使實際與理論更加相符。但是好的 PRNG 往往伴隨著效能的下降,比如 Mt19937 隨機化演算法。