洗牌随机算法的多样性不言而喻,算法的关键取决于随机性——即每张牌经多次洗牌后在牌堆中分布顺序的均匀性。洗牌活动是人类手工能够完成的一项动作,方法多为左右手双手洗结合单手切牌,因此,本算法结合实际操作进行程序加工进行实现,核心代码如下:
Card类,作为一张卡牌的JavaBean:
public class Card {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Cards类,作为卡组,即一套牌的JavaBean:
import java.util.Vector;
public class Cards {
private Vector<Card> cardSet = null;
public Cards(Vector<Card> cardSet){
this.cardSet = cardSet;
}
public Vector<Card> getCardSet() {
return cardSet;
}
public void setCardSet(Vector<Card> cardSet) {
this.cardSet = cardSet;
}
}
CardsMethod类实现洗牌方法:
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;
public class CardsMethod {
public Cards shuffleCardsMethod(Cards cards)//洗牌
{
Vector<Card> cardSet = cards.getCardSet();
int num = cardSet.size();//获取卡牌的总数
Random rand = new Random();
for(int i = 0;i < 1000;i++)
{
Vector<Card> tmpSet_1 = new Vector<Card>(num);//双手洗牌临时卡牌组
Vector<Card> tmpSet_2 = new Vector<Card>(num);//单手切牌临时卡牌组
//双手洗牌
int left = (rand.nextInt(2) == 0) ? num / 2 + rand.nextInt(3) : num / 2 - rand.nextInt(3);
//左手起牌,从卡牌组中间分开,上下浮动4张牌
int sl = 0;//左手开始卡牌ID
int sr = left;//右手开始卡牌ID
int el = 0;//左手结束卡牌ID
int er = 0;//右手结束卡牌ID
while(true)//开始双手洗牌
{
el = sl + rand.nextInt(2) + 1;//左手每次下牌1~2张
er = sr + rand.nextInt(2) + 1;//右手每次下牌1~2张
if(el >= left - 1)//左手的牌先落完
{
for(;sl < left;sl++)
{
tmpSet_1.add(cardSet.get(sl));
}
for(;sr < num;sr++)//右手剩余的全部牌落到牌组底部
{
tmpSet_1.add(cardSet.get(sr));
}
break;
}
else if(er >= num)//右手的牌先落完
{
for(;sr < num;sr++)
{
tmpSet_1.add(cardSet.get(sr));
}
for(;sl < left;sl++)//左手剩余的全部牌落到牌组底部
{
tmpSet_1.add(cardSet.get(sl));
}
break;
}
else//正常洗牌过程
{
if(i % 2 == 0)//双数洗牌回合,
{
for(;sl < el;sl++)
{
tmpSet_1.add(cardSet.get(sl));
}
for(;sr < er;sr++)
{
tmpSet_1.add(cardSet.get(sr));
}
}
else//单数洗牌回合
{
for(;sr < er;sr++)
{
tmpSet_1.add(cardSet.get(sr));
}
for(;sl < el;sl++)
{
tmpSet_1.add(cardSet.get(sl));
}
}
}
}
cardSet = tmpSet_1;
//单手切牌
int sp = rand.nextInt(num/4) + num/10;//切牌的开始卡牌ID
int ep = sp + rand.nextInt(num/4) + num/10;//切牌的结束卡牌ID
int p = sp;
while(true)
{
boolean btw = (sp <= ep) ? true : false;//交换卡牌
if(btw)//如果没有交换完毕
{
tmpSet_2.add(cardSet.get(p));
cardSet.remove(p);
sp++;
}
else//交换结束,将其他所有剩余牌按原顺序放入到切出卡牌的下方
{
Iterator<Card> intator = cardSet.iterator();
while(intator.hasNext())
{
tmpSet_2.add(intator.next());
}
break;
}
}
cardSet = tmpSet_2;
}
System.out.println("shuffle");
cards.setCardSet(cardSet);
return cards;
}
}
PlayCards类用于测试,统计id为0的卡牌在100000次洗牌中分布在牌堆中的数量情况:
import java.util.Vector;
public class PlayCards {
public static void main(String[] args){
int cardNum = 54;
int[] sum = new int[cardNum];
for(int i = 0;i < cardNum; i++){
sum[i] = 0;
}
Vector<Card> cardSet = new Vector<Card>(cardNum);
CardsMethod cm = new CardsMethod();
for(int i = 0;i < cardNum; i++){
Card card = new Card();
card.setId(i);
cardSet.add(card);
}
Cards cards = new Cards(cardSet);
for(int i = 0;i < 54000; i++){
cm.shuffleCardsMethod(cards);
for(int j = 0;j < cardNum; j++){
if(cards.getCardSet().get(j).getId() == 0){
sum[j] += 1;
}
}
}
//id为0的卡在循环54000次后的分布情况
for(int i = 0;i < cardNum;i++){
System.out.println(sum[i]);
}
}
}
统计结果如下:
牌堆id:0 993
牌堆id:1 985
牌堆id:2 982
牌堆id:3 965
牌堆id:4 994
牌堆id:5 1054
牌堆id:6 978
牌堆id:7 1010
牌堆id:8 986
牌堆id:9 954
牌堆id:10 1047
牌堆id:11 1008
牌堆id:12 1003
牌堆id:13 1000
牌堆id:14 978
牌堆id:15 1083
牌堆id:16 1012
牌堆id:17 1029
牌堆id:18 955
牌堆id:19 1019
牌堆id:20 992
牌堆id:21 1060
牌堆id:22 1037
牌堆id:23 986
牌堆id:24 1022
牌堆id:25 1100
牌堆id:26 944
牌堆id:27 1011
牌堆id:28 978
牌堆id:29 1001
牌堆id:30 1039
牌堆id:31 1010
牌堆id:32 1005
牌堆id:33 948
牌堆id:34 954
牌堆id:35 994
牌堆id:36 1056
牌堆id:37 1001
牌堆id:38 941
牌堆id:39 963
牌堆id:40 997
牌堆id:41 970
牌堆id:42 1042
牌堆id:43 1009
牌堆id:44 969
牌堆id:45 983
牌堆id:46 997
牌堆id:47 993
牌堆id:48 981
牌堆id:49 1024
牌堆id:50 974
牌堆id:51 970
牌堆id:52 1003
牌堆id:53 1011
可以通过贝塞尔公式计算标准差方式,评估分布均匀性,计算值越趋近于0说明分布越均匀:
(1)最理想的情况下,在54000次洗牌后,每个牌堆次序id中id为0的卡牌应出现1000次,即其算数平均值;
(2)使用贝塞尔公式计算标准差
其中n取54,Xi为牌堆54个id中id为0的卡牌出现次数的值,计算结果为:34.0216
(3)计算标准差平均值
计算结果为:4.63,即可理解为误差为4.63