Java 如何真正洗牌一副牌
当我需要在Java/Android中洗牌时,我会使用Java 如何真正洗牌一副牌,java,android,random,shuffle,playing-cards,Java,Android,Random,Shuffle,Playing Cards,当我需要在Java/Android中洗牌时,我会使用Collections.shuffle(List)。我曾经这样做过,结果似乎可以接受。但事实并非如此 如中所述,共有52个!52张扑克牌组中可能出现的独特洗牌。总数约为2^226 但是Collections.shuffle(列表列表)默认情况下使用new Random(),因此只能创建2^48个唯一的shuffle-这只占所有可能的shuffle的3.49*10^(-52)百分比 那么我如何正确洗牌呢 我已经开始使用了SecureRandom,
Collections.shuffle(List)
。我曾经这样做过,结果似乎可以接受。但事实并非如此
如中所述,共有52个!52张扑克牌组中可能出现的独特洗牌。总数约为2^226
但是Collections.shuffle(列表列表)
默认情况下使用new Random()
,因此只能创建2^48个唯一的shuffle-这只占所有可能的shuffle的3.49*10^(-52)
百分比
那么我如何正确洗牌呢
我已经开始使用了SecureRandom
,但最终这足够了吗
List<Card> cards = new ArrayList<Card>();
...
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstance("SHA1PRNG");
}
catch (NoSuchAlgorithmException e) {
secureRandom = new SecureRandom();
}
secureRandom.nextBytes(new byte[20]); // force SecureRandom to seed itself
Collections.shuffle(cards, secureRandom);
List cards=new ArrayList();
...
安全随机安全随机;
试一试{
secureRandom=secureRandom.getInstance(“SHA1PRNG”);
}
捕获(无算法异常){
secureRandom=新的secureRandom();
}
secureRandom.nextBytes(新字节[20]);//强迫自己播种
收集。洗牌(卡片,随机);
尽管您使用的是SecureRandom
,但它的状态仍然有限。只要输入种子的范围小于52!它不可能是完全随机的
事实上,这意味着它还不够随机。接下来,几年前它就有了一个解决方案,通过使用名为
UnCommons Math
的第三方库,您可能只能从特定的起始安排中获得248只不同的手,但不要求您每次都以相同的安排开始
据推测,在牌组完成(扑克手、21点等等)后,它将以不确定的顺序排列,任何一种重新排列都是合适的
而且,如果您担心每次启动程序时都是从固定的安排开始,请在退出时保持顺序,然后下次重新加载
在任何情况下,248仍然是大量的可能性(大约280000000000000),对于纸牌游戏来说已经足够了,当你意识到它限制了洗牌而不是安排时就更是如此。除非你是一个认真的统计学家或密码学家,否则你所拥有的应该是好的。从你链接的文章中窃取答案:
START WITH FRESH DECK
GET RANDOM SEED
FOR CT = 1, WHILE CT <= 52, DO
X = RANDOM NUMBER BETWEEN CT AND 52 INCLUSIVE
SWAP DECK[CT] WITH DECK[X]
从新甲板开始
获得随机种子
对于CT=1,而CT如果你想要真正的随机性,你可以跳过伪随机生成器,选择一些更好的方法,比如从大气噪声中生成随机数
提供了一种将以这种方式生成的随机数集成到您自己的软件中的方法。如何真正洗牌
有几个
任一(剥离/上移):
或(即兴):
除此之外还有更多内容,详见我上面的链接
无论如何,有如此多的组合,即使是完美的洗牌算法也需要机器每秒探索2*10^50
唯一的排列,才能完成宇宙存在时的每个排列。到2019年,现代计算机预计只会达到1Exaflops(1*10^18
floating point operations/sec)
没有人类洗牌者会探索这种可能性范围,我相信你(在最基本的层面上)是在模拟人类洗牌,对吗?你会不会发现一个蹲位者可能会在一次洗牌中将递增顺序的牌组洗牌成递减顺序的牌组?在一次洗牌中,在奇数之前,以偶数等级分开牌组
我不认为在每次洗牌时将自己限制在相空间的一小部分(尽管是非常小的部分)(2^48
可能的随机数)是不可接受的,只要你不以同样的方式连续播种等等
在52张牌组中,有52张可能的牌序(简写为52!)。这大约是8×1067个可能的订单,或者具体来说:80658175170943878571660636856403766975289505440883277824000000000000
这个数字的大小意味着,即使在宇宙历史上,两张随机选择的、真正随机的牌组也不太可能是相同的。然而,尽管随机牌组中所有牌的确切顺序是不可预测的,可能会对未充分随机的甲板进行一些概率预测。
~
另外,值得注意的是,拜耳和迪亚科尼斯公司在1992年证明了一副牌只需要7次良好的随机洗牌就可以正确地随机排列,这是维基百科上关于这一点的章节,其中有很多讨论这一点的文章链接。来自该论文:首先要认识到的是,一个算法能够产生52个牌中的每一个!洗牌并不是必须的。@Philipp Sander:重复洗牌并不会增加随机性,是吗?这可能会有所帮助,根据这篇文章,我还更新了我的答案,使之不完全是胡说八道。@MarcoW.mersene Twister产生32位或64位的结果,但它基于一个更大的内部状态-它的周期长度是2^19937-1。@MarcoW。我相信是的。在任何单种子生成器(如LCG)中,一旦第二次看到给定值,整个序列将以相同的方式重复。当较大的状态被折叠/投影到较小的输出状态时,看到相同值的重复并不意味着您将得到相同序列的重复。至于播种,那只是选择进入大循环的一个切入点。64位种子表示从长度为2^19937-1的循环的10^18个入口点中选择一个。洗牌算法不是问题所在。Java的Collections.shuffle(…)
应该使用Fisher-Yates shuffle,这已经足够了。但PRNG及其种子是关键点。同意,关键在于随机数的生成。(尽管本文描述了人们是如何把洗牌弄错的。)大概是在牌组完成后
Cut the deck in two
Add a small (pseudorandom) amount of one half to the front of the front of the other
Add a small (pseudorandom) amount of one half to the front of the back of the other
Do this until one hand is empty
Repeat
Cut the deck in two
Set down a small (pseudorandom) portion of one half
Set down a small (pseudorandom) portion of the other
Do this until both hands are empty, and you have a new deck
Repeat