先抛出问题:一个公司的员工有100位,如何在这100位的员工里面抽取出10个幸运奖得主?通过分析,这个问题可以总结为“如何在列表中随机等概率地选择其中某些元素”。
针对这个问题,我先给出代码,然后在给出解释(通过问答的方式)。
package art.programming.simpling;
import java.util.Random;
public class Simpling {
public static Object[] simple(Object[] objects, int num) {
//The length of the array
int len = objects.length;
Object[] selectedArray = new Object[num];
int select = num;
for (int i = 0; i < len; i++) {
if(select < 1) break;
int random = new Random().nextInt(len -i);
//随机数小于select的概率是select/len
if (random < select) {
selectedArray[num - select] = objects[i];
select --;
}
}
return selectedArray;
}
}
Q:以上这段代码如何保证等概率呢?
A:等概率是通过这段代码的保证的
int random = new Random().nextInt(len -i);
if (random < select) {...}
因为从概率上来说,在集合S中,随机选择M个元素,任何一个元素被选中的概率是M/S。这样说可能有些拗口,换个说法是抽奖箱里有100个球,其中奖品球10个,100个人抽奖,任何一个人抽中奖的概率是1/10.
上面这段代码的意思就是随机产生0到len-i的数,这个数小于select的概率就是select/(len-i). 比如随机产生一个0到100的数,这个数小于10的概率是1/10.
Q: 这个i是递增的, 是不是i=1,i=2..的时候没有被选中了就没有机会被选中了?
A:是的,就像100个人排队抽奖,第一个人没有抽中,就把机会留给剩下的99个人了。需要注意的是,在第一个人抽的时候,大家的机会都是1/10;如果抽中了,剩下的人的机会是9/99;如果没有抽中,剩下的人的机会是10/99. 但不论如何都是等概率的。代码
int random = new Random().nextInt(len -i);
if (random < select) {...}
就是为了保证这一点。
测试用例
package art.programming.simpling;
import org.junit.Test;
public class SimplingTest {
@Test
public void test() {
Integer[] array = new Integer[]{1,2,3,4,5,6,7,8,9,10};
//在以上数组中随机选择3个元素
Object[] objects = Simpling.simple(array, 3);
for (Object o : objects){
System.out.println((Integer) o);
}
}
}