Java實現遊戲抽獎演算法
阿新 • • 發佈:2019-01-30
常用抽獎演算法對比
基礎的遊戲抽獎演算法通常要求實現在指定獎品的集合中,每個獎品根據對對應概率進行抽取。個人瞭解的主要有以下幾中抽獎演算法:
隨機數一一對應
演算法思想
這種演算法思想最為簡單。將n個獎品編號0~n-1,其中各類獎品的概率通過其數量體現,最後程式產生0~n-1之間的隨機數便是抽中的獎品編號。例如:
蘋果手機概率1%,網站會員20%,折扣券20%,很遺憾59%。這樣,編號0是蘋果手機,1~20是會員,21~40是折扣券,41~100是 很遺憾。產生的隨機數落在那個區間,就代表那個獎品被抽中。
存在問題
- 總數N快速膨脹
概率通過數量來體現在各個獎品概率較大的情況下,總數n可以較小。但如果在精度很高的情況下,總數必須按比例成倍擴大。
例如,所有獎品概率都是10%,那麼n只需要取10就可以。但是如果某個獎品概率是0.01%,按照這種演算法,總數要擴大到100*100。 - 平衡性影響
在Java中,Math.random()方法本身基本可以保證大量測試的情況下避免高重複,且概率分佈比較平均。但是需要注意的是,該方法預設返回0-1之間的資料。
在當前演算法中,必須擴大指定倍數並且強制使用int進行型別轉換。在這樣的擴大和轉換過程中,必然會對資料精度進行修改,轉換後的資料也不能保證概率分佈平均。
因此,該演算法實際可能達不到預期的概率要求。 - 演算法複雜度
資料準備階段,為每個獎品確定編號與獎品資訊的關係集合需要O(n);
產生隨機數階段並轉換,O(1);
從集合中查詢,不同的資料結構實現不同,最差需要O(n);
離散法
演算法思想
(高中數學裡幾何概形的思想)
將獎品集合的概率劃分區段放入陣列中。概率區段通過該概率累計相加確定。利用隨機數產生隨機概率,加入陣列並排序,該資料的下標,就是對應獎品集合中獎品的索引。例如,獎品的集合有X1,X2,X3,X4,對應概率為P1=0.2,P2=0.2,P3=0.3,P4=0.3。
那麼,產生的概率區段陣列為[0.2,0.4,0.7,1.0]。
0.2以下代表X1,0.2~0.4代表X2,0.4~0.7代表X3,0.7~1代表X4。
這樣,如果產生一個隨機概率為0.5,加入陣列排序後,0.4~0.7之間,是X3相加所在的概率區間,返回index=2。
由於區間分佈的確定是按照X集合順序的,所以該索引也正是X3在原集合中的索引。
特點
- 利用幾何概形,概率陣列分佈在0到1之間,不再需要擴大倍數和取整操作,基本可以保證概率平均分佈,避免大量重複的情況
- 概率分配的排序過程,可以使用java預設的排序工具類,也可以自己實現。保證時間複雜度最小。
- 複雜度
準備階段,O(m)。m遠小於n,因為概率只有幾個,不會大量膨脹。
產生隨機數,O(1)
排序取下標,根據排序演算法,O(logn)即可實現
取值,根據下標,O(1);
Alias 演算法
這種演算法對數學要求比較高,沒有仔細研究。
感興趣的小夥伴可以自己研究一下和我分享
演算法實現
獎品實體類
/**
* 抽獎獎品實體類
* @author irving
* @since 2017年7月23日 下午9:41:33
* @version MARK 0.0.1
*/
public class Gift {
private int id; //獎品Id
private String name; //獎品名稱
private double prob; //獲獎概率
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getProb() {
return prob;
}
public void setProb(double prob) {
this.prob = prob;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
抽獎實現工具類
/**
* 抽獎工具類<br/>
* 整體思想:
* 獎品集合 + 概率比例集合
* 將獎品按集合中順序概率計算成所佔比例區間,放入比例集合。併產生一個隨機數加入其中,排序。</br>
* 排序後,隨機數落在哪個區間,就表示那個區間的獎品被抽中。</br>
* 返回的隨機數在集合中的索引,該索引就是獎品集合中的索引。</br>
* 比例區間的計算通過概率相加獲得。
* @author irving
* @since 2017年7月23日 下午9:48:23
* @version MARK 0.0.1
*/
public class DrawLotteryUtil {
public static int drawGift(List<Gift> giftList){
if(null != giftList && giftList.size()>0){
List<Double> orgProbList = new ArrayList<Double>(giftList.size());
for(Gift gift:giftList){
//按順序將概率新增到集合中
orgProbList.add(gift.getProb());
}
return draw(orgProbList);
}
return -1;
}
public static int draw(List<Double> giftProbList){
List<Double> sortRateList = new ArrayList<Double>();
// 計算概率總和
Double sumRate = 0D;
for(Double prob : giftProbList){
sumRate += prob;
}
if(sumRate != 0){
double rate = 0D; //概率所佔比例
for(Double prob : giftProbList){
rate += prob;
// 構建一個比例區段組成的集合(避免概率和不為1)
sortRateList.add(rate / sumRate);
}
// 隨機生成一個隨機數,並排序
double random = Math.random();
sortRateList.add(random);
Collections.sort(sortRateList);
// 返回該隨機數在比例集合中的索引
return sortRateList.indexOf(random);
}
return -1;
}
public static void main(String[] args) {
Gift iphone = new Gift();
iphone.setId(101);
iphone.setName("蘋果手機");
iphone.setProb(0.1D);
Gift thanks = new Gift();
thanks.setId(102);
thanks.setName("再接再厲");
thanks.setProb(0.5D);
Gift vip = new Gift();
vip.setId(103);
vip.setName("優酷會員");
vip.setProb(0.4D);
List<Gift> list = new ArrayList<Gift>();
list.add(vip);
list.add(thanks);
list.add(iphone);
for(int i=0;i<100;i++){
int index = drawGift(list);
System.out.println(list.get(index));
}
}
}