c++
一副撲克54張牌,有54!種排列方式。最佳的洗牌算法,應該可以等機率地生成這54!種結果中的一種git
GitHub連接github
這是徹底合乎現實洗牌邏輯的算法。面試
就是抽出紙牌的最後一張隨機插入到牌庫中,這般抽54次就完成了對撲克牌的洗牌算法
空間O(1),時間O(n^2)數組
若是牌庫是以一個數組描述,這種插入式的洗牌不可避免地要大量移動元素。dom
取兩個列表,一個是洗牌前的序列A{1,2….54),一個用來放洗牌後的序列B,B初始爲空ide
while A不爲空spa
隨機從A取一張牌加入B末尾code
空間O(n),時間O(n^2)
1 List<int> list = new List<int>(pukes.pukes);//洗牌前的序列A 2 List<int> newlist = new List<int>(list.Count);//洗牌後的序列B 3 for(int i = 0 ; i < pukes.pukes.Length ; ++i) 4 { 5 int randomIndex = Random.Range(0, list.Count); 6 int r = list[randomIndex];//隨機取牌 7 newlist.Add(r); 8 list.RemoveAt(randomIndex); 9 } 10 pukes.ResetPuke(newlist.ToArray());//序列B爲洗牌後的結果
算法原理清晰,但額外開闢了一個List,並且爲List刪除元素是不可避免地須要移動元素
經過54次生成的隨機數取1/54,1/53,…1/1能等機率地生成這54!種結果中的一種
Knuth 和Durstenfeld 在Fisher 等人的基礎上對算法進行了改進。 每次從未處理的數據中隨機取出一個數字,而後把該數字放在數組的尾部, 即數組尾部存放的是已經處理過的數字 。 這是一個原地打亂順序的算法,算法時間複雜度也從Fisher算法的 O ( n 2 )提高到了 O ( n )。
1 for(int i = pukes.pukes.Length - 1;i>0;--i) 2 { 3 int randomIndex = Random.Range(0, i+1); 4 pukes.Swap(randomIndex, i); 5 }
是最佳的洗牌算法
C++ stl中random_shuffle使用的就是這種算法
在[0, i]之間隨機一個下標j,而後用位置j的元素替換掉位置i的數字
經過54次生成的隨機數取1/1,1/2,…1/54能等機率地生成這54!種結果中的一種
空間O(1),時間O(n)
1 public static void Shuffle(Pukes pukes) 2 { 3 int len = pukes.pukes.Length; 4 for (int i = 0; i < len; ++i) 5 { 6 int randomIndex = Random.Range(0, i + 1); 7 pukes.Swap(i, randomIndex); 8 } 9 }
關於c++ stl 的random_shuffle
它的算法原理和Knuth_Durstenfeld相似
先從全部元素中選一個與位置1的元素交換,而後再從剩下的n-1個元素中選擇一個放到位置2,以此類推