洗牌算法
Ⅰ 随机洗牌:哪一种算法是正确的
几乎所有的程序员都写过类似于“洗牌”的算法,也就是将一个数组随机打乱后输出,虽然很简单,但是深入研究起来,这个小小的算法也是大有讲究。我在面试程序员的时候,就会经常让他们当场写一个洗牌的函数,从中可以观察到他们对于这个问题的理解和写程序的基本功。 在深入讨论之前,必须先定义出一个基本概念:究竟洗牌算法的本质是什么?也就是说,什么样的洗牌结果是“正确”的? 云风曾经有一篇博文,专门讨论了这个问题,他也给出了一个比较确切的定义,在经过洗牌函数后,如果能够保证每一个数据出现在所有位置的概率是相等的,那么这种算法是符合要求的。在这个前提下,尽量降低时间复杂度和空间复杂度就能得到好的算法。 第一个洗牌算法:随机抽出一张牌,检查这张牌是否被抽取过,如果已经被抽取过,则重新抽取,直到找到没被抽出过的牌,然后把这张牌放入洗好的队列中,重复该过程,直到所有的牌被抽出。 大概是比较符合大脑对于洗牌的直观思维,这个算法经常出现在我遇到的面试结果中,虽然它符合我们对于洗牌算法的基本要求,但这个算法并不好,首先它的复杂度为O(N2),而且需要额外的内存空间保存已经被抽出的牌的索引。所以当数据量比较大时,会极大降低效率。
Ⅱ 关于洗牌算法,请用java编写,定义一个数组,储存1-52以内的数,打乱顺序输出!
import java.util.Enumeration;
import java.util.Hashtable;/**
* 7. * 乱序扑克牌 洗牌方法 8. * 9. *
*
* @author virture 10. * 11.
*/
public class Cards { Hashtable htMember = new Hashtable();// 放置扑克牌的Hash表 public Cards() { } public void put(String card) {
htMember.put(card, card);
} public void get() {
System.out.println("你拿到的牌是:");
Enumeration RLKey = htMember.keys();
while (RLKey.hasMoreElements()) {
String accKey = RLKey.nextElement().toString();// 取HashTable中的关键字词
System.out.print((String) htMember.get(accKey) + ",");
}
} public static void main(String[] args) {
String[] cards = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10",
"J", "Q", "K" };
String[] kinds = { "黑桃", "红桃", "梅花", "方块" }; Cards cardList = new Cards(); String suit;// 当前选中牌的类型
String face;// 当前选中牌
int randomCardNum = 52;// 当前随机取牌的个数,记住不能大于全部牌52张 while (true) {
suit = kinds[Math.round((float) Math.random() * (kinds.length - 1))];
face = cards[Math.round((float) Math.random() * (cards.length - 1))]; cardList.put(suit + face);
if (cardList.htMember.size() >= randomCardNum
&& cardList.htMember.size() <= 52) {
break;
}
}
cardList.get();
}
}
Ⅲ 用C++编写一个洗牌发牌的函数,玩家可能有两个、三个和四个
几乎所有的程序员都写过类似于“洗牌”的算法,也就是将一个数组随机打乱后输出,虽然很简单,但是深入研究起来,这个小小的算法也是大有讲究。我在面试程序员的时候,就会经常让他们当场写一个洗牌的函数,从中可以观察到他们对于这个问题的理解和写程序的基本功。
在深入讨论之前,必须先定义出一个基本概念:究竟洗牌算法的本质是什么?也就是说,什么样的洗牌结果是“正确”的?
云风曾经有一篇博文,专门讨论了这个问题,他也给出了一个比较确切的定义,在经过洗牌函数后,如果能够保证每一个数据出现在所有位置的概率是相等的,那么这种算法是符合要求的。在这个前提下,尽量降低时间复杂度和空间复杂度就能得到好的算法。
第一个洗牌算法:
随机抽出一张牌,检查这张牌是否被抽取过,如果已经被抽取过,则重新抽取,直到找到没被抽出过的牌,然后把这张牌放入洗好的队列中,重复该过程,直到所有的牌被抽出。
大概是比较符合大脑对于洗牌的直观思维,这个算法经常出现在我遇到的面试结果中,虽然它符合我们对于洗牌算法的基本要求,但这个算法并不好,首先它的复杂度为O(N2),而且需要额外的内存空间保存已经被抽出的牌的索引。所以当数据量比较大时,会极大降低效率。
第二个算法:
设牌的张数为n,首先准备n个不容易碰撞的随机数,然后进行排序,通过排序可以得到一个打乱次序的序列,按照这个序列将牌打乱。
这也是一个符合要求的算法,但是同样需要额外的存储空间,在复杂度上也会取决于所采用的排序算法,所以仍然不是一个好的算法。
第三个算法:
每次随机抽出两张牌交换,重复交换一定次数次后结束
void shuffle(int* data, int length)
{
for(int i=0; i<SWAP_COUNTS; i++)
{
//Rand(min, max)返回[min, max)区间内的随机数
int index1 = Rand(0, length);
int index2 = Rand(0, length);
std::swap(data[index1], data[index2]);
}
}
这又是一个常见的洗牌方法,比较有意思的问题是其中的“交换次数”,我们该如何确定一个合适的交换次数?简单的计算,交换m次后,具体某张牌始终没有被抽到的概率为((n-2)/n)^m,如果我们要求这个概率小于1/1000,那么 m>-3*ln(10)/ln(1-2/n),对于52张牌,这个数大约是176次,需要注意的是,这是满足“具体某张牌”始终没有被抽到的概率,如果需要满足“任意一张牌”没被抽到的概率小于1/1000,需要的次数还要大一些,但这个概率计算起来比较复杂,有兴趣的朋友可以试一下。
Update: 这个概率是,推算过程可以参考这里,根据这个概率,需要交换280次才能符合要求
第四个算法:
从第一张牌开始,将每张牌和随机的一张牌进行交换
void shuffle(int* data, int length)
{
for(int i=0; i<length; i++)
{
int index = Rand(0, length);
std::swap(data[i], data[index]);
}
}
很明显,这个算法是符合我们先前的要求的,时间复杂度为O(N),而且也不需要额外的临时空间,似乎我们找到了最优的算法,然而事实并非如此,看下一个算法。
第五个算法:
void shuffle(int* data, int length)
{
for(int i=1; i<length; i++)
{
int index = Rand(0, i);
std::swap(data[i], data[index]);
}
}
一个有意思的情况出现了,这个算法和第三种算法非常相似,从直觉来说,似乎使数据“杂乱”的能力还要弱于第三种,但事实上,这种算法要强于第三种。要想严格的证明这一点并不容易,需要一些数学功底,有兴趣的朋友可以参照一下这篇论文,或者matrix67大牛的博文,也可以这样简单理解一下,对于n张牌的数据,实际排列的可能情况为n! 种,但第四种算法能够产生n^n种排列,远远大于实际的排列情况,而且n^n不能被n!整除,所以经过算法四所定义的牌与牌之间的交换程序,很可能一张牌被换来换去又被换回到原来的位置,所以这个算法不是最优的。而算法五输出的可能组合恰好是n!种,所以这个算法才是完美的。
事情并没有结束,如果真的要找一个最优的算法,还是请出最终的冠军吧!
第六个算法:
void shuffle(int* data, int length)
{
std::random_shuffle(data, data+length);
}
没错,用c++的标准库函数才是最优方案,事实上,std::random_shuffle在实现上也是采取了第四种方法,看来还是那句话,“不要重复制造轮子”
不想写 - -
Ⅳ 高分求一个除去大小王52张牌的洗牌算法
洗牌的方法很多,我喜欢用随机选择法,基本方法是:先把52张牌顺序存放到一个数组里面,然后产生一个1~52的随机数,把随机数的那张牌抽出来放到新数组的第一个位置,在原数组里面把抽取出的牌之后的牌前移动一个位置,然后产生1~51的随机数类似处理,直到只剩一张排。
描述如下:
VAR A1,A2:[1..52] OF POKE;
A2[1..52]=POKE1..POKE52;
FOR I=1 TO 51 DO
BEGIN
N=RANDOM(1,52-I+1)
A1[N]=A2[N]
FOR J=I TO 52 A2[J]=A2[J+1];
END
A1[52]=A2[1]
Ⅳ 几种扑克牌洗牌算法
洗牌的
几种话先设定好洗牌方式几种比方对分上下交l以及交织洗牌然扑克牌后用随机数生成函数确定单步洗牌作牌的数量多反复几遍即可。
的一个合理的定义就是算法
一副扑克张牌有种陈列方式。
这样做的好处:
给出的洗牌算算法应该可以等概率地生成这种结果中的一种
Ⅵ java洗牌算法为什么改成从1到54就出错了
for(int i=1;i<55;i+=3){
p1.add(cards.get(i));
p2.add(cards.get(i+1));
p3.add(cards.get(i+2));你的cards有54个,这个循环中当i=53,时,i+2=55,越界,所以报错
}
Ⅶ 怎么证明这个洗牌算法是随机的
有一副牌假设有N张,请设计一个随机洗牌算法。
解决方案:
这里只给出一个可以使用数学证明每张牌出现在任何位置概率为1/N的算法。
Poker[N]
for (i = 0; i < N; ++i)
{
k = rand() % ( i + 1)
if (i != k)
{
switch(Poker[k], Poker[i]);
}
Ⅷ java洗牌算法问题
你用System.out.print方法打印一个对象时,控制台会默认调用其toString方法,java就这么设计的
Ⅸ 关于牌类游戏洗牌算法一问:怎样才算把牌洗
1. 嵌入式洗牌法
把部分的塔罗牌拿在手中,使牌面朝下,将剩下的牌随意插入手里的牌,再自手中拿出一些牌,再插进去。重复这个步骤直到你觉得牌洗干净了为止。不过这种洗牌方式非常容易折损牌的边缘,要小心喔(有时还会刮伤手…)
2. 推摆洗牌法
将塔罗牌牌面朝下,在桌面上弄混,之后用左手的拇指将最上方的一叠牌推回左手,再用右手拇指推下方的一叠牌到右手,持续重复这个动作,直到所有的牌都被分开,之后重叠再一起并重复这些动作,直到你觉得已经洗干净为止。不过这个方法不是很容易,你必须常常练习才不会打到手(嘿嘿嘿…)
3. 一般正常洗牌法
将塔罗牌牌面朝下,双手以顺时针或逆时针方向将牌均匀混和即可。以上介绍的三种方式均为一般常见的洗牌方法(尤其是第三项,一般市面上的中文塔罗书籍均是以此法为主,故简略带过),在洗牌时一般是建议受占者心中专心默念要问的问题,而占卜师择是专心洗牌。至于是否要由受占者洗牌则是见仁见智。不过殿主向来是由自己洗牌,有时总会遇到‘朋友’想帮‘他的朋友’占卜,而‘他的朋友’并不在现场的情况,若是非当事者洗牌不可,大概这牌也就不需要算了…
至于是否一定要使用这几种方法来洗牌,答案是不一定。只要能将牌充分混和均匀,也不致伤到牌面就好了。
此外,在使用第三种洗牌方式时,殿主提供一些经验分享给大家:
1.转动时要利用指腹与手腕的力量
2.像写书法一样,手腕抬高
3.尽量轻柔地转动每一张牌
4.每一张牌建议尽量转三圈半以上(要公转也要自转喔)
5.注意上层的牌要与下层的牌混合均匀