抽奖活动(一)-Alias算法

来源:互联网 发布:关闭端口的命令 编辑:程序博客网 时间:2024/09/21 09:21

抽奖活动在项目开发中其实是比较常见的问题,这篇文章主要介绍一下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);    elselarge.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);    elselarge.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算法通俗的理解,我的这篇文章只是帮助大家理解一下这个算法,至于它的计算过程和专业的计算原理,大家可以百度一下,百度上关于这个算法的原理讲解的文章有很多。

如有不足,欢迎及时提出,谢谢!

原创粉丝点击