[原创]彩票选号后的数学——抽牌算法的实现

talent_marquis 2008-07-04 12:59:24
中国的彩票选号,例如36选7,从36个数字中随机选取7个,这在算法上如何实现呢?

最简单的想法就是,每次都从1~36随机选取一个数,一共选7次,不就可以了吗?
但这样会有一个问题——重复。彩票选号是不能重复的,这也即是说如果你第一次选到的数是10,那么以后再从1~36中选数的时候,10就不能再选了。
有人可能会说了,这还不好办,如果重复了就废掉,重新再选一个呗。
这的确是一种解决方法,但是会有很大的问题,比如说5选4吧,前三个都已经选好了是2,3,4,现在取第4个数,这种情况下,取到1和5的几率要比取到2,3,4的几率还要小,也就是说,最坏的情况下,有可能会取很多次2,3,4,扔掉很多次,才最终能取到1或5,完成4个随机数字的选择。显然,这样效率是有很大问题的。

下面就介绍一种算法:抽牌算法,来实现这种不允许重复的选号,同时不会出现这种效率上的问题。
[separator]
抽牌算法的核心思想如下:
以36选7为例
一副牌,一共36张,抽出其中一张牌,放到一边,再从剩下的牌中抽出第二张,放到一边……以此类推,直到抽完了7张牌为止。
很显然,这样抽牌是绝对不会重复的。而其核心就是抽出的牌要放到一边

用算法如何实现呢?
其实很简单,只要能模拟实现把抽出的牌放到一边这个概念就可以了,而模拟实现的方法是非常简单的:把一个数组模拟成一个牌盒,用数组里存的数模拟牌,而抽出的牌放到一边的动作,只需进行一次数组交换,把它放到数组的末尾即可。

以36选7为例
初始化数组,其结构为[1,2.....35,36]
第一轮,从1~36序号中选取随机序号,抽取到序号7, 把序号7和序号36的值交换,7放到数组的末尾,数组结构变成[1...6,36,8......34,35,7]
第二轮,从1~35序号中选取随机序号,抽取到7(这时位置7所存的数就是36了),把36和35交换,数组结构就变成了[1..6,35,8...34,36,7]
第三轮,从1~34序号中选取随机序号,抽取到5,把5和34交换,数组结构变成了[1...4,34,6,35,8....5,36,7]
...
每一次,都把抽出的“牌”放到数组的最后,然后再抽牌时,就不抽最后那张牌,这样就实现了抽出的牌放到一边这样一个概念。

请看以下Java代码:

//获得不重复的随机数数组,取值范围[min,max),个数size
public static int[] getRandomIntWithoutReduplicate( int min, int max, int size )
{
int[] result = new int[size];//用于存储结果的数组

int arraySize = max - min;//用于放"牌"的数组大小
int[] intArray = new int[arraySize];//用于放"牌"的数组
// 初始化"牌盒",比如取值范围是[3,10)则"牌盒"里放的"牌"就是3,4,5,6,7,8,9
for( int i = 0 ; i < intArray.length ; i++ )
{
intArray[i] = i + min;
}
// 获取不重复的随机数数组
for( int i = 0 ; i < size ; i++ )
{
int c = getRandomInt( min, max - i );//获取到一个随机数
int index = c - min;//这个随机数在"牌盒"里的位置
swap( intArray, index, arraySize - 1 - i );//将这张"牌"放到"牌盒"的最后面
result[i] = intArray[ arraySize - 1 - i ];//把这张"牌"的值扔到存储结果的数组里
}
return result;
}
//获取随机数,随机数取值范围为[min, max)
public static int getRandomInt( int min, int max )
{
// include min, exclude max
int result = min + new Double( Math.random() * ( max - min ) ).intValue();

return result;
}

private static void swap( int[] array, int x, int y )
{//交换数组arry, 序号x与序号y值的顺序
int temp = array[x];
array[x] = array[y];
array[y] = temp;
}

...全文
604 31 打赏 收藏 转发到动态 举报
写回复
用AI写文章
31 条回复
切换为时间正序
请发表友善的回复…
发表回复
lovingprince 2008-07-09
  • 打赏
  • 举报
回复
见到作者的帖了,呵呵,写得还是不错。我用集合实现了同样做法,楼主使用数组,的确不错。呵呵
大真 2008-07-09
  • 打赏
  • 举报
回复
用集合Set set = new HashSet();
这样,就不不会重复了吗?
在来一个for循环出7个数。放在set中去。

干嘛,用数组呢。代码多。看着还累。。
haoxiongok 2008-07-09
  • 打赏
  • 举报
回复
import java.util.*;
public class ChouJiang {


public static void main(String[] args) {
Scanner in=new Scanner(System.in);
System.out.print("你需要抽取多少个数?");
int k=in.nextInt();
System.out.print("你的数字最大的是多少?");
int n=in.nextInt();
int [] numbers=new int[n];
for(int i=0;i<numbers.length;i++)
numbers[i]=i+1;
int[] result=new int[k];
for(int i=0;i<result.length;i++)
{
int r=(int)(Math.random()*n);
result[i]=numbers[r];
numbers[r]=numbers[n-1];
n--;
}
Arrays.sort(result);
//System.out.println("赌中接下来的这个号码组合,你就有钱了!");
for(int r=0;r<result.length;r++)
System.out.println(result[r]);
}

}
sunyuchuan86 2008-07-09
  • 打赏
  • 举报
回复
学习。。。。。。。。
talent_marquis 2008-07-09
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 billgacsli 的回复:]
帽子的效率是挺高的(代码风格真好,pfpf),不过不是又回去楼主的“非抽牌”算法了吗??

不知道楼主的“完整改正版效率极差是到什么程度”,我试了100000次35选7也只需235ms啊,应该足够效率了吧,我觉得。

确实List删除元素后会调用System.arraycopy()方法进行重排。
[/Quote]


我说的是下面这个算法效率有问题,不是顶楼的那个抽牌算法。

private static Random random = new Random();
public static int[] getRandomIntWithoutReduplicate2( int min, int max, int size )
{
int[] result = new int[size];
List<Integer> list = new ArrayList< Integer >();
//init list
for( int i = min; i < max; i++ )
{
list.add( i );
}
for( int i = 0; i < size; i++ )
{
int index = random.nextInt( list.size() );
result[i] = list.get( index );
list.remove( index );
}
return result;
}

妄旺 2008-07-08
  • 打赏
  • 举报
回复
帽子的效率是挺高的(代码风格真好,pfpf),不过不是又回去楼主的“非抽牌”算法了吗??

不知道楼主的“完整改正版效率极差是到什么程度”,我试了100000次35选7也只需235ms啊,应该足够效率了吧,我觉得。

确实List删除元素后会调用System.arraycopy()方法进行重排。
hiker_1 2008-07-07
  • 打赏
  • 举报
回复
很好!!学习了.谢谢
zhj92lxs 2008-07-07
  • 打赏
  • 举报
回复
laorer 2008-07-07
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 java2000_net 的回复:]
不错,喜欢你的钻研精神。 我们哪天启动背包系统的竞赛啊?
[/Quote]

似乎蛮有难度的,具体不记得了,运筹学里面有,9X年的高程考试考过
talent_marquis 2008-07-07
  • 打赏
  • 举报
回复
cool~~

[Quote=引用 21 楼 sagezk 的回复:]
Bug 修复:

Java codeif (len == 1) return new int[] {min};


这句去了,修复后:

Java codeimport java.util.Arrays;
import java.util.Random;

/**
* <code>RandomUtil</code> - Random Tool Class.
* @author SageZk
* @version 1.0
*/
public class RandomUtil {

private RandomUtil() {}

private static Random rnd = null;

/**
* 初始化随机数发生器。
*/

[/Quote]
sagezk 2008-07-07
  • 打赏
  • 举报
回复
Bug 修复
if (len == 1) return new int[] {min};

这句去了,修复后
import java.util.Arrays;
import java.util.Random;

/**
* <code>RandomUtil</code> - Random Tool Class.
* @author SageZk
* @version 1.0
*/
public class RandomUtil {

private RandomUtil() {}

private static Random rnd = null;

/**
* 初始化随机数发生器。
*/
private static void initRnd() {
if (rnd == null) rnd = new Random();
}

/**
* 计算并返回无重复值的以 <code>min</code> 为下限 <code>max</code> 为上限的随机整数数组。
* @param min 随机整数下限(包含)
* @param max 随机整数上限(包含)
* @param len 结果数组长度
* @return 结果数组
*/
public static int[] getLotteryArray(int min, int max, int len) {
//参数校验及性能优化
if (len < 0) return null; //长度小于 0 的数组不存在
if (len == 0) return new int[0]; //返回长度为 0 的数组
if (min > max) { //校正参数 min max
int t = min;
min = max;
max = t;
}
final int LEN = max - min + 1; //种子个数
if (len > LEN) return null; //如果出现 35 选 36 的情况就返回 null
//计算无重复值随机数组
initRnd(); //初始化随机数发生器
int[] seed = new int[LEN]; //种子数组
for (int i = 0, n = min; i < LEN;) seed[i++] = n++; //初始化种子数组
for (int i = 0, j = 0, t = 0; i < len; ++i) {
j = rnd.nextInt(LEN - i) + i;
t = seed[i];
seed[i] = seed[j];
seed[j] = t;
}
return Arrays.copyOf(seed, len); //注意:copyOf 需要 JRE1.6
}

//Unit Testing
public static void main(String[] args) {
final int N = 10000; //测试次数
for (int i = 0; i < N; ++i) {
int[] la = RandomUtil.getLotteryArray(1, 35, 7);
if (la == null) continue;
for (int v : la) System.out.printf("%0$02d ", v);
System.out.println();
}
}

}
sagezk 2008-07-07
  • 打赏
  • 举报
回复
写个效率高速度快的,如下:

import java.util.Arrays;
import java.util.Random;

/**
* <code>RandomUtil</code> - Random Tool Class.
* @author SageZk
* @version 1.0
*/
public class RandomUtil {

private RandomUtil() {}

private static Random rnd = null;

/**
* 初始化随机数发生器。
*/
private static void initRnd() {
if (rnd == null) rnd = new Random();
}

/**
* 计算并返回无重复值的以 <code>min</code> 为下限 <code>max</code> 为上限的随机整数数组。
* @param min 随机整数下限(包含)
* @param max 随机整数上限(包含)
* @param len 结果数组长度
* @return 结果数组
*/
public static int[] getLotteryArray(int min, int max, int len) {
//参数校验及性能优化
if (len < 0) return null; //长度小于 0 的数组不存在
if (len == 0) return new int[0]; //返回长度为 0 的数组
if (min > max) { //校正参数 min max
int t = min;
min = max;
max = t;
}
final int LEN = max - min + 1; //种子个数
if (len > LEN) return null; //如果出现 35 选 36 的情况就返回 null
if (len == 1) return new int[] {min}; //即 min == max 的情况
//计算无重复值随机数组
initRnd(); //初始化随机数发生器
int[] seed = new int[LEN]; //种子数组
for (int i = 0, n = min; i < LEN;) seed[i++] = n++; //初始化种子数组
for (int i = 0, j = 0, t = 0; i < len; ++i) {
j = rnd.nextInt(LEN - i) + i;
t = seed[i];
seed[i] = seed[j];
seed[j] = t;
}
return Arrays.copyOf(seed, len); //注意:copyOf 需要 JRE1.6
}

//Unit Testing
public static void main(String[] args) {
final int N = 10000; //测试次数
for (int i = 0; i < N; ++i) {
int[] la = RandomUtil.getLotteryArray(1, 35, 7);
if (la == null) continue;
for (int v : la) System.out.printf("%0$02d ", v);
System.out.println();
}
}

}
talent_marquis 2008-07-06
  • 打赏
  • 举报
回复
刚进行了测试,这种方法效率极差,可能和list的数据结构有关,进行remove操作后数据有一个重排的过程。

[Quote=引用 17 楼 talent_marquis 的回复:]
完整改进版如下:


Java code
private static Random random = new Random();
public static int[] getRandomIntWithoutReduplicate2( int min, int max, int size )
{
int[] result = new int[size];
List<Integer> list = new ArrayList< Integer >();
//init list
for( int i = min; i < max; i++ )
{
list.add( i );
}
fo…
[/Quote]
talent_marquis 2008-07-06
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 java2000_net 的回复:]
不错,喜欢你的钻研精神。 我们哪天启动背包系统的竞赛啊?
[/Quote]

这先把背包系统背后所需要的东西整理出来才行

我现在能想起来的就是这些:

物品类:包含属性:大小,价值
背包类:包含属性:大小,放东西的算法就应该放在这个背包类里面
物品生成类:用来随机生成指定数量的物品,能够随机生成物品大小,价值
显示背包类:可以直观地用二维数组的方式来显示背包里东西放置的样式,例如
[3x3,15],*,*,[1x1,5], 0
* ,*,*,[2x2,10],*
* ,*,*, *, *

最后还要有一个统计背包价值的类,计算谁放入背包内的东西价值最高

不过感觉这个背包竞赛如果真正想实施的话,还有很多细节需要完善,现在还只能说是一个初步构想,有漏洞。
talent_marquis 2008-07-06
  • 打赏
  • 举报
回复
完整改进版如下:


private static Random random = new Random();
public static int[] getRandomIntWithoutReduplicate2( int min, int max, int size )
{
int[] result = new int[size];
List<Integer> list = new ArrayList< Integer >();
//init list
for( int i = min; i < max; i++ )
{
list.add( i );
}
for( int i = 0; i < size; i++ )
{
int index = random.nextInt( list.size() );
result[i] = list.get( index );
list.remove( index );
}
return result;
}
老紫竹 2008-07-06
  • 打赏
  • 举报
回复
不错,喜欢你的钻研精神。 我们哪天启动背包系统的竞赛啊?
talent_marquis 2008-07-06
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 java2000_net 的回复:]
1 彩票这种业务要求,不需要高效算法,所以效率不是主要的问题。
2 如果5选4,根据逻辑,应该改成5选1
[/Quote]

1 对。所以这个只是一种算法上的探讨。不过抽牌算法用于选取不能有重复值的随机数上,还是有一定意义的,不一定只局限于彩票选号上。
2 5选4这个例子我举的不好,只是想说明极端情况下选取不重复的随机数会有重复值的问题,如果用20选10会更好些吧。
talent_marquis 2008-07-06
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 billgacsli 的回复:]
其实楼主算法算不上是“抽牌算法”啊??!!
关键点是:抽出一张牌放一边,原来的顺序是不变的。
按楼主的例子,第一次抽到7,那么抽出7这张牌后,顺序应该为[1...6,8......34,35,36,7] 才对啊

直接用List很容易实现,removeItem就好了
[/Quote]

确实如此!
大致代码如下,简化了很多很多啊。


ArrayList list = new ArrayList( Data );
int[] result = new int[size];
for( int i =0 ; i < size ; i++ )
{
int index = Rondom.nextInt( list.size() );
resut[i] = list.get( index );
list.remove( index );
}
老紫竹 2008-07-05
  • 打赏
  • 举报
回复
1 彩票这种业务要求,不需要高效算法,所以效率不是主要的问题。
2 如果5选4,根据逻辑,应该改成5选1
妄旺 2008-07-04
  • 打赏
  • 举报
回复
其实楼主算法算不上是“抽牌算法”啊??!!
关键点是:抽出一张牌放一边,原来的顺序是不变的
按楼主的例子,第一次抽到7,那么抽出7这张牌后,顺序应该为[1...6,8......34,35,36,7] 才对啊

直接用List很容易实现,removeItem就好了
加载更多回复(10)

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧