算法问题:转盘抽奖概率算法业务实现

第一类是常见的有等级的抽奖活动,如一等、二等、三等奖等等
&&&&private&static&final&Integer[]&lotteryList&=&{5,&10,&20,&40,&100};&&
&&&&private&int&getSum()&{&&
&&&&&&&&int&sum&=&0;&&
&&&&&&&&for&(int&v&:&lotteryList)&{&&
&&&&&&&&&&&&sum&+=&v;&&
&&&&&&&&}&&
&&&&&&&&return&&&
&&&&private&int&getLotteryLevel()&{&&
&&&&&&&&Random&random&=&new&Random(System.nanoTime());&&
&&&&&&&&int&sum&=&getSum();&&
&&&&&&&&for&(int&i&=&0;&i&&&lotteryList.&++i)&{&&
&&&&&&&&&&&&int&randNum&=&Math.abs(random.nextInt())&%&&&
&&&&&&&&&&&&if&(randNum&&=&lotteryList[i])&{&&
&&&&&&&&&&&&&&&&return&i;&&
&&&&&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&sum&-=&lotteryList[i];&&
&&&&&&&&&&&&}&&
&&&&&&&&}&&
&&&&&&&&return&-1;&&
第二类是不分等级的抽奖活动,仅需要参与人数与奖品总数,各奖品中奖概率相等。
&&&&private&static&final&int&lotteryNum&=&75;&&
&&&&private&static&final&int&total&=&175;&&
&&&&private&static&Set&Integer&&lotterySet&=&new&HashSet&Integer&();&&
&&&&static&{&&
&&&&&&&&for&(int&i=1;&i&&=&lotteryN&++i)&{&&
&&&&&&&&&&&&lotterySet.add(total*i/lotteryNum);&&
&&&&&&&&}&&
&&&&private&int&getLotteryNum2()&{&&
&&&&&&&&Random&rand&=&new&Random(System.nanoTime());&&
&&&&&&&&int&randNum&=&Math.abs(rand.nextInt())&%&&&
&&&&&&&&if&(lotterySet.contains(randNum))&{&&
&&&&&&&&&&&&return&randNum*lotteryNum/&&
&&&&&&&&}&&
&&&&&&&&return&-1;&&
第三类 &一个商场进行一场抽奖活动,其中有两个奖项,第一个奖项A抽中的概率是1/6,第二个奖项B抽中的概率是5/6;编码实现这个抽奖程序。
这题考察队随机函数的应用。
由于rand()函数产生的随机数的范围是0-65535,那么将该随机数对6求余,得到的数在0-5之间,且每个数出现概率相等。
第四类 : 不同概率抽奖
VO类:Gift.java& & 具体对应就是上面表格里的内容
public class Gift {
private int
private String gitfId;
private String giftN
private double
public Gift(int index, String gitfId, String giftName, double probability) {
this.index =
this.gitfId = gitfId;
this.giftName = giftN
this.probability =
public int getIndex() {
public void setIndex(int index) {
this.index =
public String getGitfId() {
return gitfId;
public void setGitfId(String gitfId) {
this.gitfId = gitfId;
public String getGiftName() {
return giftN
public void setGiftName(String giftName) {
this.giftName = giftN
public double getProbability() {
public void setProbability(double probability) {
this.probability =
public String toString() {
return &Gift [index=& + index + &, gitfId=& + gitfId + &, giftName=& + giftName + &, probability=& + probability + &]&;
Util类:LotteryUtil.java& & 真正的不同概率的抽奖工具类
import java.util.ArrayL
import java.util.C
import java.util.L
public class LotteryUtil {
public static int lottery(List&Double& orignalRates) {
if (orignalRates == null || orignalRates.isEmpty()) {
return -1;
int size = orignalRates.size();
double sumRate = 0d;
for (double rate : orignalRates) {
sumRate +=
List&Double& sortOrignalRates = new ArrayList&Double&(size);
Double tempSumRate = 0d;
for (double rate : orignalRates) {
tempSumRate +=
sortOrignalRates.add(tempSumRate / sumRate);
double nextDouble = Math.random();
sortOrignalRates.add(nextDouble);
Collections.sort(sortOrignalRates);
return sortOrignalRates.indexOf(nextDouble);
Test类:Test.java
import java.util.ArrayL
import java.util.HashM
import java.util.L
import java.util.M
import java.util.Map.E
public class LotteryTest {
public static void main(String[] args) {
List&Gift& gifts = new ArrayList&Gift&();
gifts.add(new Gift(1, &P1&, &物品1&, 0.2d));
gifts.add(new Gift(2, &P2&, &物品2&, 0.2d));
gifts.add(new Gift(3, &P3&, &物品3&, 0.4d));
gifts.add(new Gift(4, &P4&, &物品4&, 0.3d));
gifts.add(new Gift(5, &P5&, &物品5&, 0d));
gifts.add(new Gift(6, &P6&, &物品6&, -0.1d));
gifts.add(new Gift(7, &P7&, &物品7&, 0.008d));
List&Double& orignalRates = new ArrayList&Double&(gifts.size());
for (Gift gift : gifts) {
double probability = gift.getProbability();
if (probability & 0) {
probability = 0;
orignalRates.add(probability);
Map&Integer, Integer& count = new HashMap&Integer, Integer&();
double num = 1000000;
for (int i = 0; i & i++) {
int orignalIndex = LotteryUtil.lottery(orignalRates);
Integer value = count.get(orignalIndex);
count.put(orignalIndex, value == null ? 1 : value + 1);
for (Entry&Integer, Integer& entry : count.entrySet()) {
System.out.println(gifts.get(entry.getKey()) + &, count=& + entry.getValue() + &, probability=& + entry.getValue() / num);
不同概率的抽奖原理很简单&
就是把0到1的区间分块,而分块的依据就是物品占整个的比重,再根据随机数种子来产生0-1中间的某个数,来判断这个数是落在哪个区间上,而对应的就是抽到了那个物品。随机数理论上是概率均等的,产生的每个数理论上也应该概率均等,那么相应的区间所含数的多少就体现了抽奖物品概率的不同。(p.s. 当然数目是数不清楚的,具体抽象话了点)
这个实例的数据可以说明&
1. 概率可以是负数和0,当然实际上中应该不会(p.s. 正常情况下可能真的有0,比如抽个iphone5,当然是抽不到的了,这个时候,构建礼物(List&gifts)的时候最好就不要加这个进去),还有可以把负数的处理放到抽奖工具类(LotteryUtil)中;&
2. 所有礼物加起来的概率可以不是1,可以认为这里的概率是一个权重;
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:554次
排名:千里之外2013年5月 总版技术专家分月排行榜第一
2016年7月 总版技术专家分月排行榜第二2016年3月 总版技术专家分月排行榜第二2015年12月 总版技术专家分月排行榜第二2014年8月 总版技术专家分月排行榜第二2014年7月 总版技术专家分月排行榜第二2013年6月 总版技术专家分月排行榜第二
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。1544人阅读
抽奖概率(7)
一个简单抽奖算法的实现以及如何预防超中
每个用户每天有3次抽奖机会;
抽奖奖池一共分为6档内容:现金红包1元,2元,3元,5元,iphone6s,谢谢参与;
支持每天调整和配置抽奖的获奖概率;
每种奖品都有一个权重 对应一个区间 若落入该区间就表示中奖 调整区间大小就可改变获奖概率 即调整权重值即可
假设设定抽10次中一次, 未中奖权重 = 抽检概率导数奖品数-奖品数 = 10 = 59409
抽奖的时候 先生成一个随机值
randNum =&new&Random().nextInt(totalWeight);&
判断该随机值在哪一个区间 如
randNum = 8944 落在未中奖区间 未中奖
randNum = 944 落在1元区间 中了一元
如果想增大中iphone6s的概率 调整权重值即可 如将权重改为1000, 则区间变为[)
同时会为每种奖品设置库存 如
中奖后 会减库存 但假如库存只剩1个了 有10个用户同时落入一元区间 如何避免1-10=-9的情况呢?
update&award_stock&set&stock = stock -&1&where&award_id = ?&and&stock &&0;
即是否中奖除了落入区间外 还需判断减库存是否成功
如果减库存失败 仍当做未中奖
一旦一种奖品库存为0 下次计算区间的时候 将它排除 如一元奖品库存已为0 这时各奖品的区间变化为
=38 此时中奖概率变小了 相当于抽38次中一次
验证上述算法
看是否能抽完所有奖品 如某天的奖品配置如下 (权重默认等于库存)
假设日活用户数为3万 每个用户可抽3次
final Map&String, Integer& awardStockMap = new ConcurrentHashMap&&();
awardStockMap.put(&1&, 5000);
awardStockMap.put(&2&, 1000);
awardStockMap.put(&3&, 500);
awardStockMap.put(&5&, 100);
awardStockMap.put(&iphone&, 1);
awardStockMap.put(&未中奖&, 59409);
final Map&String, Integer& awardWeightMap = new ConcurrentHashMap&&(awardStockMap);
int userNum = 30000;
int drawNum = userNum * 3;
Map&String, Integer& dailyWinCountMap = new ConcurrentHashMap&&();
for(int j=0; j&drawN j++){
Map&String, Integer& awardWeightHaveStockMap = awardWeightMap.entrySet().stream().filter(e-&awardStockMap.get(e.getKey())&0).collect(Collectors.toMap(e-&e.getKey(), e-&e.getValue()));
int totalWeight = (int) awardWeightHaveStockMap.values().stream().collect(Collectors.summarizingInt(i-&i)).getSum();
int randNum = new Random().nextInt(totalWeight);
int prev = 0;
String choosedAward = null;
for(Entry&String,Integer& e : awardWeightHaveStockMap.entrySet() ){
if(randNum&=prev && randNum&prev+e.getValue()){
choosedAward = e.getKey();
prev = prev+e.getValue();
pute(choosedAward, (k,v)-&v==null?1:v+1);
if(!&未中奖&.equals(choosedAward)){
pute(choosedAward, (k,v)-&v-1);
if(awardStockMap.get(choosedAward)==0){
System.out.printf(&奖品:%s 库存为空%n&,choosedAward);
System.out.println(&各奖品中奖计数: &+dailyWinCountMap);
奖品:iphone 库存为空
奖品:5 库存为空
奖品:1 库存为空
奖品:2 库存为空
奖品:3 库存为空
每日各奖品中奖计数: {1=0, 3=500, 5=100, iphone=1, 未中奖=83399}
可知 假如该天抽奖次数能有9万次的话 可以抽完所有的奖品 另外因是单线程未考虑减库存
失败的情况 即并发减库存的情况
抽奖算法2 存在奖品库存的前提下 保证每次中奖的概率恒定 如15% 抽100次有15次中奖
final Map&String, Integer& awardStockMap = new ConcurrentHashMap&&();
awardStockMap.put(&1&, 3000);
awardStockMap.put(&2&, 2000);
awardStockMap.put(&3&, 1500);
awardStockMap.put(&5&, 1000);
awardStockMap.put(&10&, 100);
awardStockMap.put(&20&, 10);
awardStockMap.put(&50&, 5);
awardStockMap.put(&100&, 2);
final Map&String, Integer& awardWeightMap = new ConcurrentHashMap&&(awardStockMap);
final Map&String, Integer& initAwardStockMap = new ConcurrentHashMap&&(awardStockMap);
int drawNum = 50780;
final int threshold = 15;
Map&String, Integer& dailyWinCountMap = new ConcurrentHashMap&&();
for (int j = 0; j & drawN j++) {
int randNum = new Random().nextInt(100);
if(randNum&threshold){
pute(&未中奖&, (k,v)-&v==null?1:v+1);
Map&String, Integer& awardWeightHaveStockMap = awardWeightMap.entrySet().stream().filter(e-&awardStockMap.get(e.getKey())&0).collect(Collectors.toMap(e-&e.getKey(), e-&e.getValue()));
if(awardWeightHaveStockMap.isEmpty()){
System.out.printf(&第%d次抽奖 奖品已被抽完%n&,j);
int totalWeight = (int) awardWeightHaveStockMap.values().stream().collect(Collectors.summarizingInt(i-&i)).getSum();
randNum = new Random().nextInt(totalWeight);
int prev=0;
String choosedAward = null;
for(Entry&String,Integer& e : awardWeightHaveStockMap.entrySet() ){
if(randNum&=prev && randNum&prev+e.getValue()){
choosedAward = e.getKey();
pute(choosedAward, (k,v)-&v==null?1:v+1);
prev = prev+e.getValue();
pute(choosedAward, (k,v)-&v-1);
System.out.println(&每日各奖品中奖计数: &);
dailyWinCountMap.entrySet().stream().sorted((e1,e2)-&e2.getValue()-e1.getValue()).forEach(System.out::println);
awardStockMap.forEach((k,v)-&{if(v&0){
System.out.printf(&奖品:%s, 总库存: %d, 剩余库存: %d%n&,k,initAwardStockMap.get(k),v);
第47495次抽奖 奖品已被抽完
每日各奖品中奖计数:
未中奖=39878
可见 实际不用到理论抽奖次数 即可抽完所有奖品
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:6802182次
积分:60073
积分:60073
排名:第38名
原创:248篇
转载:2602篇
评论:645条
(17)(92)(17)(25)(37)(63)(7)(74)(67)(95)(177)(114)(86)(40)(43)(71)(14)(10)(17)(12)(6)(20)(27)(54)(71)(97)(74)(32)(2)(24)(21)(62)(60)(36)(23)(27)(46)(34)(76)(63)(121)(141)(74)(54)(120)(77)(42)(4)(12)(19)(1)(9)(15)(19)(18)(16)(31)(79)(68)抽奖概率-三种算法 - 挽星 - 博客园
一、逢&几&中奖
逢&几&中奖,即通过预估抽奖人数和奖品数来判断,&几&=(抽奖人数/奖品数)*N。这是一种最简单抽奖算法,适合抽奖人数众多,而且互相无联系的情况。如今大为流行的微博转发得奖就常常使用这种算法,即根据转发次数来决定奖品归属,透明而且具有激励性。
当然这个&几&也不单只次数,还可能是时间,逢某个时间点就可以抽中,不过这种方案可能产生无人中奖和很多人中奖的情况,时间点的安排很关键!这个时间点一旦公布出去,那就是秒杀,霍霍。。
逢&几&中奖有很多弊端,但是非常简单,很容易实现,被很多抽奖活动所采用,有些会公布抽奖规则,激励抽奖,有些则不会公布,其实后台运行的可能也是这个算法,简单高效又不失公平。在信息不透明的情况下,鬼知道你是第几个抽奖的,哈哈。。
二、概率抽奖
所谓概率抽奖是最容易想到的抽奖算法了,这个概率可以是一成不变的,也可以是一直在变化调整的,最难的是采用多大的概率,何种情况下采用何种概率。这个也没有什么通用的方案,不同的应用场景,所用的概率算法不同。下面介绍一种算法,根据奖品的过期日期来计算它当前时间的中奖率,当时间逐渐接近奖品过期时间时,中奖概率会逐渐发生变化,如果设为1表示线性衰减,2为平方衰减,以此类推。
importjava.util.D
importjava.util.R
publicclass LotteryTool {
private double
private double
private LotteryTool(double probability, long expireTime, int reduce){
this.factor = (double) System.currentTimeMillis() / expireT
this.probability = probability * Math.pow(factor, reduce);
this.rand = new Random(System.currentTimeMillis());
public static LotteryTool getInstance(double probability, longexpireTime,
int reduce) {
return new LotteryTool(probability, expireTime, reduce);
public boolean isLucky(long expected) {
long token = generateLong();
expected = expected % (int) (1 / probability);
if (expected == token) {
return true;
return false;
三、依赖不可控的物理随机数&
什么意思呢,先看个图,看完你就知道了
明白了吧,呵呵,这就是现如今灰常流行的一种抽奖算法,绝对公平、绝对透明、绝对木有暗箱(除非偷偷给你换了抽奖号码)!但是这种方法唯一的缺点是无法实时抽奖,只能事后抽奖。也就是只能拿个抽奖号等着上帝的眷顾,阿门。。。
例如游戏中打败一个boss,会掉落下面其中一个物品,而每个物品都有一定概率: 1. 靴子 20% 2. 披风 25% 3. 饰品 10% 4. 双手剑 5% 5. 金币袋 40% 现在的问题就是如何根据概率掉落一个物品给玩家。
一. 一般算法:生成一个列表,分成几个区间,例如列表长度100,1-20是靴子的区间,21-45是披风的区间等,然后随机从100取出一个数,看落在哪个区间。算法时间复杂度:预处理O(MN),随机数生成O(1),空间复杂度O(MN),其中N代表物品种类,M则由最低概率决定。
二、离散算法:也就是上面的改进,竟然1-20都是靴子,21-45都是披风,那抽象成小于等于20的是靴子,大于20且小于等于45是披风,就变成几个点[20,45,55,60,100],然后也是从1到99随机取一个数R,按顺序在这些点进行比较,知道找到第一个比R大的数的下标,比一般算法减少占用空间,还可以采用二分法找出R,这样,预处理O(N),随机数生成O(logN),空间复杂度O(N)。 请点击查看详细:
三、Alias Method Alias Method就不太好理解,实现很巧妙,推荐先看看这篇文章: 大致意思:把N种可能性拼装成一个方形(整体),分成N列,每列高度为1且最多两种可能性,可能性抽象为某种颜色,即每列最多有两种颜色,且第n列中必有第n种可能性,这里将第n种可能性称为原色。 想象抛出一个硬币,会落在其中一列,并且是落在列上的一种颜色。这样就得到两个数组:一个记录落在原色的概率是多少,记为Prob数组,另一个记录列上非原色的颜色名称,记为Alias数组,若该列只有原色则记为null。
之前的例子,为了便于演示换成分数 1. 靴子 20% -& 1/4 2. 披风 25% -& 1/5 3. 饰品 10% -& 1/10 4. 双手剑 5% -& 1/20 5. 金币袋 40% -& 2/5 然后每个都乘以5(使每列高度为1),再拼凑成方形 拼凑原则:每次都从大于等于1的方块分出一小块,与小于1的方块合成高度为1
由上图方形可得到两个数组: Prob: [3/4, 1/4, 1/2, 1/4, 1] Alias: [4, 4, 0, 1, null] (记录非原色的下标)
之后就根据Prob和Alias获取其中一个物品 随机产生一列C,再随机产生一个数R,通过与Prob[C]比较,R较大则返回C,反之返回Alias[C]。
Alias Method 复杂度:预处理O(NlogN),随机数生成O(1),空间复杂度O(2N)}

我要回帖

更多关于 java 抽奖算法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信