您的位置:首页 > 其它

抽奖活动(一)-Alias算法

2017-07-15 10:45 791 查看
抽奖活动在项目开发中其实是比较常见的问题,这篇文章主要介绍一下Alias算法解决随机类型概率问题:

对于开发抽奖活动的任务来说,奖品一般放置在数据库中,而概率分为一下两种:

第一种是:所有奖项的概率和为1,也就是说本次活动所有参与人员都会中奖,中奖的等级随奖品的概率而定;

第二种是:所有的奖项的概率和小于1,也就是说存在未中奖的情况,其实这种情况也可以归结为第一种,将剩余的概率归到未中奖事件上,然后再将未中奖看做一个奖项,这种情况就和第一种相似了。

而我今天主要对第二种进行剖析一下,如果掌握了第二种,我想第一种也就迎刃而解了。

首先看一下代码:

package com.thinkive.app.bonus.business.utils;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Random;
import com.thinkive.base.jdbc.DataRow;
public class AliasMethod {

private final double[] probability;
private final int[] alias;
private final int length;
private final Random rand;

public AliasMethod(List<Double> prob) {
this(prob, new Random());
}

public AliasMethod(List<Double> prob, Random rand) {
/* Begin by doing basic structural checks on the inputs. */
if (prob == null || rand == null)
throw new NullPointerException();
if (prob.size() == 0)
throw new IllegalArgumentException("Probability vector must be nonempty.");

this.rand = rand;
this.length = prob.size();
this.probability = new double[length];
this.alias = new int[length];

double[] probtemp = new double[length];
Deque<Integer> small = new ArrayDeque<Integer>();
Deque<Integer> large = new ArrayDeque<Integer>();

/* divide elements into 2 groups by probability */
for (int i = 0; i < length; i++) {
probtemp[i] = prob.get(i) * length; /* initial probtemp */
if (probtemp[i] < 1.0)
small.add(i);
else
large.add(i);
}

while (!small.isEmpty() && !large.isEmpty()) {
int less = small.pop();
int more = large.pop();
probability[less] = probtemp[less];
alias[less] = more;
probtemp[more] = probtemp[more] - (1.0 - probability[less]);
if (probtemp[more] < 1.0)
small.add(more);
else
large.add(more);
}
/*
* At this point, everything is in one list, which means that the
* remaining probabilities should all be 1/n. Based on this, set them
* appropriately.
*/
while (!small.isEmpty())
probability[small.pop()] = 1.0;
while (!large.isEmpty())
probability[large.pop()] = 1.0;
}
/**
* Samples a value from the underlying distribution.
*
*/
public int next() {
/* Generate a fair die roll to determine which column to inspect. */
int column = rand.nextInt(length);

/* Generate a biased coin toss to determine which option to pick. */
boolean coinToss = rand.nextDouble() < probability[column];

/* Based on the outcome, return either the column or its alias. */
return coinToss ? column : alias[column];
}

/* 概率测试 */
public static void main(String[] argv) {
List<Double> prob = new ArrayList<Double>();

//prob.add(0.25); /* 0.01% 几率命中 */
//prob.add(0.25); /* 50% 几率命中 */
//prob.add(0.25); /* 50% 几率命中 */
prob.add(0.001);//一等奖
prob.add(0.05);//二等奖
prob.add(0.1);//三等奖
prob.add(0.3);
prob.add(0.549);
String[] str={"一等奖","二等奖","三等奖","四等奖","未中奖"};
int[] test={0,0,0,0};
AliasMethod am = new AliasMethod(prob);
for(int i=0;i<100;i++){
System.out.println(str[am.next()]);
}
}
}

运行结果大家可以自行测试一下,中奖次数符合概率分布。

下面就介绍一下的Alias算法的基本原理:

我们以一下概率分布为主

奖项一等奖二等奖三等奖未中奖
概率0.10.20.30.4
概率分布如下图:



然后将各个分类的概率乘于4(注意:这个数字是事件的总数,目的是将每种事件的的概率都填补成1),结果为:



下面将要进行的是一个“借”概率的过程,由图可知,第三种第四种的概率分别为12/10和16/10,大于1,而前两种的概率小于一,将第三种多余的2/10拿出来放到第一种上面,将第四种多余的6/10拿出4/10分到第一种上,剩下的2/10分到第二种上,就会使每种概率都为一。结果如下:



暂时将每种事件“借”来的概率默认为这个事件本身自我的概率,由此可见,每种事件的概率都为一,每种事件发生的概率都相同了,到这,有人就会问,第三种第四种明明概率大于一,就算是借出去也应该是自己的,怎么能分给第一种第二种呐?

对,下面我将解答这个疑问:

将每种概率都转化成一,这是一个平均资源的过程,保证资源落到每块的概率相等,比如:向分配好的区域内随机放40个事件,按照概率分布,每个区域都会有10个事件,这个过程就叫平均资源,然后再第一个区域内的10个事件就会有2个事件属于第三种,有4个事件属于第四种,这个过程我称为“内部消化”,这样就能保证概率大于一的事件能“维护”好自己的权益了。

以上便是我对Alias算法通俗的理解,我的这篇文章只是帮助大家理解一下这个算法,至于它的计算过程和专业的计算原理,大家可以百度一下,百度上关于这个算法的原理讲解的文章有很多。

如有不足,欢迎及时提出,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: