随机洗牌算法(随机洗牌函数)
Knuth 洗牌算法
思考:洗牌的结果是所有元素的一个排列。一副牌如果有 n 个元素,最终排列的可能性一共有 n! 个。 公平的洗牌算法,应该能等概率地给出这 n! 个结果中的任意一个 。 如思考虑到这一点,我们就能设计出一个简单的暴力算法了:对于 n 个元素,生成所有的 n! 个排列,然后,随机抽一个。 这个算法绝对是公平的。但问题是,复杂度太高。复杂度是多少呢?O(n!)。因为,n 个元素一共有 n! 种排列,我们求出所有 n! 种排列,至少需要 n! 的时间。所以,这个算法确实是公平的,但是,时间不可容忍。
我们再换一个角度思考“公平”这个话题。其实,我们也可以认为,公平是指, 对于生成的排列,每一个元素都能独立等概率地出现在每一个位置。 或者反过来, 每一个位置都能独立等概率地放置每个元素。
基于这个定义,我们就可以给出一个简单的算法了。说这个算法简单,是因为他的逻辑太容易了,就一个循环:
这么简单的一个算法,可以保证上面我所说的,对于生成的排列,每一个元素都能独立等概率的出现在每一个位置。或者反过来,每一个位置都能独立等概率的放置每个元素。 大家可以先简单的理解一下这个循环在做什么。其实非常简单,i 从后向前,每次随机一个 [0...i] 之间的下标,然后将 arr[i] 和这个随机的下标元素,也就是 arr[rand(0, i)] 交换位置。 大家注意,由于每次是随机一个 [0...i] 之间的下标,所以,在每一轮,是可以自己和自己交换的。 这个算法就是大名鼎鼎的 Knuth-Shuffle,即 Knuth 洗牌算法。
随机洗牌:哪一种算法是正确的
几乎所有的程序员都写过类似于“洗牌”的算法,也就是将一个数组随机打乱后输出,虽然很简单,但是深入研究起来,这个小小的算法也是大有讲究。我在面试程序员的时候,就会经常让他们当场写一个洗牌的函数,从中可以观察到他们对于这个问题的理解和写程序的基本功。 在深入讨论之前,必须先定义出一个基本概念:究竟洗牌算法的本质是什么?也就是说,什么样的洗牌结果是“正确”的? 云风曾经有一篇博文,专门讨论了这个问题,他也给出了一个比较确切的定义,在经过洗牌函数后,如果能够保证每一个数据出现在所有位置的概率是相等的,那么这种算法是符合要求的。在这个前提下,尽量降低时间复杂度和空间复杂度就能得到好的算法。 第一个洗牌算法:随机抽出一张牌,检查这张牌是否被抽取过,如果已经被抽取过,则重新抽取,直到找到没被抽出过的牌,然后把这张牌放入洗好的队列中,重复该过程,直到所有的牌被抽出。 大概是比较符合大脑对于洗牌的直观思维,这个算法经常出现在我遇到的面试结果中,虽然它符合我们对于洗牌算法的基本要求,但这个算法并不好,首先它的复杂度为O(N2),而且需要额外的内存空间保存已经被抽出的牌的索引。所以当数据量比较大时,会极大降低效率。
C语言 洗牌算法
/*洗牌程序:用任何语言,随机分配52张扑克牌到52个位置上,每个位置只容许放一张牌
用1-13表示红心A--K
14-26表示黑桃A,2,3-,Q,K
27-39表示方块A,2,3-,Q,K
40-52表示黑桃A,2,3-,Q,K
也就是生成1-52不重复的随机数,放到数组中*/
#includeiomanip.h
#includestdlib.h
#includetime.h
const int N=52;
static int a[N];
int create(int n)
{
return (1+rand()%52);
}
int main()
{
int i,j;
srand(time(0));
for(i=0;iN;++i)
{
a[i]=create(N);
for(j=0;ji;++j)
{
if(a[j]==a[i])
{
a[i]=(a[i]+1)%52;
}
}
coutsetw(5)a[i];
}
coutendl;
return 0;
}
Fisher Yates 洗牌算法
Fisher–Yates shuffle是一种基于有限序列产生随机排列的算法。因为每次抽取每个元素都是等概率的,所以该算法产生一个无偏排列:每个排列的可能性相等。
Fisher–Yates shuffle最开始描述在 Ronald Fisher and Frank Yates 的 Statistical tables for biological, agricultural and medical research 一书中。
A到H的排列
抽取1-8之间的随机数
保存对应位置的元素到结果,并删除原始排列中的元素
原始方法会花费不必要的时间来计算上面第3步中剩余的数字;新方法做出改进,就是将挑选的元素和最后一个元素进行互换。
相反方向的排列
A到H的排列
抽取1-8之间的随机数
保存对应位置的元素到结果,并删除原始排列中的元素
# 原文:
Fisher–Yates shuffle
Shuffle a given array using Fisher–Yates shuffle Algorithm
几种扑克牌洗牌算法
洗牌的
几种话先设定好洗牌方式几种比方对分上下交l以及交织洗牌然扑克牌后用随机数生成函数确定单步洗牌作牌的数量多反复几遍即可。
的一个合理的定义就是算法
一副扑克张牌有种陈列方式。
这样做的好处:
给出的洗牌算算法应该可以等概率地生成这种结果中的一种
关于牌类游戏洗牌算法一问:怎样才算把牌洗
1. 嵌入式洗牌法
把部分的塔罗牌拿在手中,使牌面朝下,将剩下的牌随意插入手里的牌,再自手中拿出一些牌,再插进去。重复这个步骤直到你觉得牌洗干净了为止。不过这种洗牌方式非常容易折损牌的边缘,要小心喔(有时还会刮伤手…)
2. 推摆洗牌法
将塔罗牌牌面朝下,在桌面上弄混,之后用左手的拇指将最上方的一叠牌推回左手,再用右手拇指推下方的一叠牌到右手,持续重复这个动作,直到所有的牌都被分开,之后重叠再一起并重复这些动作,直到你觉得已经洗干净为止。不过这个方法不是很容易,你必须常常练习才不会打到手(嘿嘿嘿…)
3. 一般正常洗牌法
将塔罗牌牌面朝下,双手以顺时针或逆时针方向将牌均匀混和即可。以上介绍的三种方式均为一般常见的洗牌方法(尤其是第三项,一般市面上的中文塔罗书籍均是以此法为主,故简略带过),在洗牌时一般是建议受占者心中专心默念要问的问题,而占卜师择是专心洗牌。至于是否要由受占者洗牌则是见仁见智。不过殿主向来是由自己洗牌,有时总会遇到『朋友』想帮『他的朋友』占卜,而『他的朋友』并不在现场的情况,若是非当事者洗牌不可,大概这牌也就不需要算了…
至于是否一定要使用这几种方法来洗牌,答案是不一定。只要能将牌充分混和均匀,也不致伤到牌面就好了。
此外,在使用第三种洗牌方式时,殿主提供一些经验分享给大家:
1.转动时要利用指腹与手腕的力量
2.像写书法一样,手腕抬高
3.尽量轻柔地转动每一张牌
4.每一张牌建议尽量转三圈半以上(要公转也要自转喔)
5.注意上层的牌要与下层的牌混合均匀