遊戲經常使用算法-洗牌算法

洗牌算法是一個比較常見的面試題。c++

一副撲克54張牌,有54!種排列方式。最佳的洗牌算法,應該可以等機率地生成這54!種結果中的一種git

基於Unity的洗牌算法代碼實現

GitHub連接github

抽牌洗牌

原理

這是徹底合乎現實洗牌邏輯的算法。面試

就是抽出紙牌的最後一張隨機插入到牌庫中,這般抽54次就完成了對撲克牌的洗牌算法

複雜度

空間O(1),時間O(n^2)數組

優缺點

若是牌庫是以一個數組描述,這種插入式的洗牌不可避免地要大量移動元素。dom

Fisher_Yates算法

原理

取兩個列表,一個是洗牌前的序列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算法

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   }

是最佳的洗牌算法

Inside_Out算法

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   }

random_shuffle

關於c++ stl 的random_shuffle

它的算法原理和Knuth_Durstenfeld相似

先從全部元素中選一個與位置1的元素交換,而後再從剩下的n-1個元素中選擇一個放到位置2,以此類推

參考連接

維基百科-Fisher–Yates shuffle

相關文章
相關標籤/搜索