目录

前言

基于Unity的洗牌算法代码实现

内容

抽牌洗牌

原理

复杂度

优缺点

Fisher_Yates算法

原理

复杂度

代码实现

优缺点

Knuth_Durstenfeld算法(最佳洗牌算法)

详解

Inside_Out算法

原理

复杂度

代码实现

random_shuffle

总结


前言

洗牌算法是一个比较常见的面试题。

一副扑克54张牌,有54!种排列方式。而最佳的洗牌算法,应该能够等概率地生成这54!种结果中的一种。

基于Unity的洗牌算法代码实现

GitHub链接:LinHowe_GameAlgorithm/Assets/Scripts/03-shuffle at master · IceLanguage/LinHowe_GameAlgorithm · GitHub

内容

抽牌洗牌

原理

这是完全合乎现实洗牌逻辑的算法。

就是抽出纸牌的最后一张随机插入到牌库中,这般抽54次就完成了对扑克牌的洗牌。

复杂度

空间O(1),时间O(n^2)

优缺点

优点:逻辑简单。

缺点:如果牌库是以一个数组描述,这种插入式的洗牌不可避免地要大量移动元素。

Fisher_Yates算法

原理

取两个列表,一个是洗牌前的序列A{1,2….54),一个用来放洗牌后的序列B,B初始为空

while A不为空

随机从A取一张牌加入B末尾

复杂度

空间O(n),时间O(n^2)

代码实现

List<int> list = new List<int>(pukes.pukes);//洗牌前的序列A
List<int> newlist = new List<int>(list.Count);//洗牌后的序列B
for(int i = 0 ; i < pukes.pukes.Length ; ++i)
{int randomIndex = Random.Range(0, list.Count);int r = list[randomIndex];//随机取牌newlist.Add(r);list.RemoveAt(randomIndex);
}
pukes.ResetPuke(newlist.ToArray());//序列B为洗牌后的结果

优缺点

优点:算法原理清晰,通过54次生成的随机数取1/54,1/53,…1/1能等概率地生成这54!种结果中的一种。

缺点:额外开辟了一个List,而且为List删除元素是不可避免地需要移动元素。

Knuth_Durstenfeld算法(最佳洗牌算法)

Knuth 和Durstenfeld 在Fisher 等人的基础上对算法进行了改进。 每次从未处理的数据中随机取出一个数字,然后把该数字放在数组的尾部, 即数组尾部存放的是已经处理过的数字 。 这是一个原地打乱顺序的算法,算法时间复杂度也从Fisher算法的 O ( n 2 )提升到了 O ( n )。

for(int i = pukes.pukes.Length - 1;i>0;--i){int randomIndex = Random.Range(0, i+1);pukes.Swap(randomIndex, i);}
void Knuth_Durstenfeld_Shuffle(vector<int>&arr)
{for (int i=arr.size()-1;i>=0;--i){srand((unsigned)time(NULL));swap(arr[rand()%(i+1)],arr[i]);}
} 

详解

是时候仔细的看一下,这个简单的算法,为什么能做到保证:对于生成的排列,每一个元素都能等概率的出现在每一个位置了。

其实,简单的吓人:)

在这里,我们模拟一下算法的执行过程,同时,对于每一步,计算一下概率值。

我们简单的只是用 5 个数字进行模拟。

假设初始的时候,是按照 1,2,3,4,5 进行排列的

那么,根据这个算法,首先会在这五个元素中选一个元素,和最后一个元素 5 交换位置。

假设随机出了 2

下面,我们计算 2 出现在最后一个位置的概率是多少?非常简单,因为是从 5 个元素中选的嘛,就是 1/5。

实际上,根据这一步,任意一个元素出现在最后一个位置的概率,都是 1/5

下面,根据这个算法,我们就已经不用管 2 了,而是在前面 4 个元素中,随机一个元素,放在倒数第二的位置。假设我们随机的是 3。

3 和现在倒数第二个位置的元素 4 交换位置

下面的计算非常重要。3 出现在这个位置的概率是多少?计算方式是这样的:

结果是1/5!!!

其实很简单,因为 3 逃出了第一轮的筛选,概率是 4/5,但是 3 没有逃过这一轮的选择。在这一轮,一共有4个元素,所以 3 被选中的概率是 1/4。因此,最终,3 出现在这个倒数第二的位置,概率是 4/5 * 1/4 = 1/5。

还是 1/5 !

实际上,用这个方法计算,任意一个元素出现在这个倒数第二位置的概率,都是 1/5。


到这里已经大概了解了。我们再进行下一步,在剩下的三个元素中随机一个元素,放在中间的位置。假设我们随机的是 1。

关键是:“ 1 ”出现在这个位置的概率是多少?计算方式是这样的:

答案1/5

即 1 首先在第一轮没被选中,概率是 4/5,在第二轮又没被选中,概率是 3/4 ,但是在第三轮被选中了,概率是 1/3。乘在一起,4/5 * 3/4 * 1/3 = 1/5。

用这个方法计算,任意一个元素出现在中间位置的概率,都是 1/5。


这个过程继续,现在,我们只剩下两个元素了,在剩下的两个元素中,随机选一个,比如是4。

将4放到第二个位置

然后,4 出现在这个位置的概率是多少?4 首先在第一轮没被选中,概率是 4/5;在第二轮又没被选中,概率是 3/4;第三轮还没选中,概率是 2/3,但是在第四轮被选中了,概率是 1/2。乘在一起,4/5 * 3/4 * 2/3 * 1/2 = 1/5。

用这个方法计算,任意一个元素出现在第二个位置的概率,都是 1/5。


最后,就剩下元素5了。它只能在第一个位置呆着了。

那么 5 留在第一个位置的概率是多少?即在前 4 轮,5 都没有选中的概率是多少?

在第一轮没被选中,概率是 4/5;在第二轮又没被选中,概率是 3/4;第三轮还没选中,概率是 2/3,在第四轮依然没有被选中,概率是 1/2。乘在一起,4/5 * 3/4 * 2/3 * 1/2 = 1/5。

得到仍是1/5!!!

算法结束。


你看,在整个过程中,每一个元素出现在每一个位置的概率,都是 1/5 !

所以,这个算法是公平的。

当然了,上面只是举例子。这个证明可以很容易地拓展到数组元素个数为 n 的任意数组。整个算法的复杂度是 O(n) 的。

通过这个过程,大家也可以看到,同样的思路,我们也完全可以从前向后依次决定每个位置的数字是谁。不过从前向后,代码会复杂一些。

(因为生成 [0, i] 范围的随机数比生成 [i, n) 范围的随机数简单,直接对 i+1 求余就好了)

Inside_Out算法

C++ stl中random_shuffle使用的就是这种算法。

原理

在[0, i]之间随机一个下标j,然后用位置j的元素替换掉位置i的数字。

通过54次生成的随机数取1/1,1/2,…1/54能等概率地生成这54!种结果中的一种。

复杂度

空间O(1),时间O(n)

代码实现

public static void Shuffle(Pukes pukes){int len = pukes.pukes.Length;for (int i = 0; i < len; ++i){int randomIndex = Random.Range(0, i + 1);pukes.Swap(i, randomIndex);}}

详解请参考:PCFG中inside和outside算法详解_算法码上来的博客-CSDN博客

random_shuffle

关于c++ stl 的random_shuffle。

它的算法原理和Knuth_Durstenfeld类似。

先从所有元素中选一个与位置1的元素交换,然后再从剩下的n-1个元素中选择一个放到位置2,以此类推。

测试代码如下:

#include <iostream>using namespace std;#include <algorithm>
#include <vector>
#include <ctime>class myPrint
{
public:void operator()(int val){cout << val << " ";}
};void test01()
{srand((unsigned int)time(NULL));vector<int> v;for(int i = 0 ; i < 10;i++){v.push_back(i);}for_each(v.begin(), v.end(), myPrint());cout << endl;//打乱顺序random_shuffle(v.begin(), v.end());for_each(v.begin(), v.end(), myPrint());cout << endl;
}int main() {test01();system("pause");return 0;
}

此方法比较实用,使用时记得加随机数种子。

总结

算法从来不是枯燥的逻辑堆砌,而是神一样的逻辑创造。

尽管这个世界很复杂,但竟也如此的简洁,优雅。

一起加油!

游戏洗牌算法——常用+详解最优Knuth_Durstenfeld算法相关推荐

  1. zuc算法代码详解_最短路算法-dijkstra代码与案例详解

    引言 在研究路径选择和流量分配等交通问题时,常常会用到最短路算法.用最短路算法解决交通问题存在两个难点: 一.算法的选择和程序的编写.最短路算法有很多种改进算法和启发式算法,这些算法的效率不同,适用的 ...

  2. 推荐系统架构与算法流程详解

    你知道的越多,不知道的就越多,业余的像一棵小草! 成功路上并不拥挤,因为坚持的人不多. 编辑:业余草 zhuanlan.zhihu.com/p/259985388 推荐:https://www.xtt ...

  3. html5走格子游戏,JS/HTML5游戏常用算法之碰撞检测 地图格子算法实例详解

    JS/HTML5游戏常用算法之碰撞检测 地图格子算法实例详解 发布时间:2020-09-26 20:42:24 来源:脚本之家 阅读:112 作者:krapnik 本文实例讲述了JS/HTML5游戏常 ...

  4. 纸牌游戏洗牌发牌排序算法设计

    纸牌游戏洗牌发牌排序算法设计 本文提供纸牌游戏设计制作的基础部分,即洗牌,发牌,牌张排序排列显示的算法. 以及游戏开始时间使用时间的显示.我是用简单的C语言编译器MySpringC在安卓手机上编写的. ...

  5. C++ STL【常用算法】详解

    大家好,欢迎来到STL[常用算法]详解模块,本文将对STL中常见但又高频的算法进行一个详细又简单的入门级教学

  6. 推荐系统整体架构及算法流程详解

    省时查报告-专业.及时.全面的行研报告库 省时查方案-专业.及时.全面的营销策划方案库 知识图谱在美团推荐场景中的应用实践 搜索场景下的智能实体推荐 机器学习在B站推荐系统中的应用实践 小红书推荐系统 ...

  7. TOPSIS(逼近理想解)算法原理详解与代码实现

    写在前面: 个人理解:针对存在多项指标,多个方案的方案评价分析方法,也就是根据已存在的一份数据,判断数据中各个方案的优劣.中心思想是首先确定各项指标的最优理想值(正理想值)和最劣理想值(负理想解),所 ...

  8. [转]数据结构KMP算法配图详解(超详细)

    KMP算法配图详解 前言 KMP算法是我们数据结构串中最难也是最重要的算法.难是因为KMP算法的代码很优美简洁干练,但里面包含着非常深的思维.真正理解代码的人可以说对KMP算法的了解已经相当深入了.而 ...

  9. 机器学习,深度学习基础算法原理详解(图的搜索、交叉验证、PAC框架、VC-维(持续更新))

    机器学习,深度学习基础算法原理详解(图的搜索.交叉验证.PAC框架.VC-维.支持向量机.核方法(持续更新)) 机器学习,深度学习基础算法原理详解(数据结构部分(持续更新)) 文章目录 1. 图的搜索 ...

最新文章

  1. JS传中文到后台需要的处理
  2. 软件工程个人作业01
  3. 分布式动态配置后浪推前浪 -- Nacos
  4. 【观点】开发人员的测试悖论
  5. H264 TS/ES
  6. stl list 删除元素_删除所有出现的元素,并从列表中删除一些特定的元素。 C ++ STL...
  7. Django - - - -视图层之视图函数(views)
  8. C/C++基本数据类型
  9. python书籍推荐:Head First Python(中文版)
  10. php开始页,页码从第四页开始怎么设置
  11. 【更新】Essential Studio for Xamarin更新至2018 v4(二)
  12. 软考高级软件架构师论文——论软件架构风格
  13. Linux面试题总结(2022最新版)
  14. 四层协议和七层协议详解
  15. Flutter TextFiled去掉下划线
  16. 如何从苹果手机中导出视频且保证画面不失真
  17. KT148A语音芯片怎么烧录语音进入芯片里面通过串口和电脑端的工具
  18. markdown 语法大全
  19. S700K道岔故障诊断数据txt及故障诊断算法
  20. 太少的相濡以沫,太多的相忘江湖

热门文章

  1. 英特尔进军晶圆代工,台积电“危”?
  2. 基于vue的高仿网易云音乐网站,实现大多数功能
  3. SAP应收模块基础流程测试
  4. SQL:某公司dba面试题记录
  5. xcode 报Remote object proxy returned error: Error Domain=NSCocoaErrorDomain Code=4099 The connec...
  6. 厦大C语言上机 1379 合并队伍
  7. python之String字符串处理库,文件/目录,日期和时间
  8. 栅栏效应,频谱泄露,旁瓣效应
  9. 11.4. 拒绝服务攻击
  10. 2020年研究生数学建模竞赛优秀论文汇总