Java實現遊戲抽獎演算法
常用抽獎演算法對比
基礎的遊戲抽獎演算法通常要求實現在指定獎品的集合中,每個獎品根據對對應概率進行抽取。個人瞭解的主要有以下幾中抽獎演算法:
隨機數一一對應
演算法思想
這種演算法思想最為簡單。將n個獎品編號0 - N-1,其中各類獎品的概率通過其數量體現,最後程式產生0~n-1之間的隨機數便是抽中的獎品編號。例如:
蘋果手機概率1%,網站會員20%,折扣券20%,很遺憾59%。這樣,編號0是蘋果手機,1-20是會員,21-40是折扣券,41~100是 很遺憾。產生的隨機數落在那個區間,就代表那個獎品被抽中。
存在問題
1、總數N快速膨脹
概率通過數量來體現在各個獎品概率較大的情況下,總數n可以較小。但如果在精度很高的情況下,總數必須按比例成倍擴大。
2、平衡性影響
在Java中,Math.random()方法本身基本可以保證大量測試的情況下避免高重複,且概率分佈比較平均。但是需要注意的是,該方法預設返回0-1之間的資料。
在當前演算法中,必須擴大指定倍數並且強制使用int進行型別轉換。在這樣的擴大和轉換過程中,必然會對資料精度進行修改,轉換後的資料也不能保證概率分佈平均。
因此,該演算法實際可能達不到預期的概率要求。
3、演算法複雜度
資料準備階段,為每個獎品確定編號與獎品資訊的關係集合需要O(n);
產生隨機數階段並轉換,O(1);
離散法
演算法思想
(高中數學裡幾何概形的思想)
將獎品集合的概率劃分區段放入陣列中。概率區段通過該概率累計相加確定。利用隨機數產生隨機概率,加入陣列並排序,該資料的下標,就是對應獎品集合中獎品的索引。例如,獎品的集合有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。
特點
1、利用幾何概形,概率陣列分佈在0到1之間,不再需要擴大倍數和取整操作,基本可以保證概率平均分佈,避免大量重複的情況
2、概率分配的排序過程,可以使用java預設的排序工具類,也可以自己實現。保證時間複雜度最小。
3、複雜度
準備階段,即對準備概率集合,進行歸一化等操作,假設樣本的概率個數為M個,則複雜度為O(m)。m遠小於方法一中的n,因為概率只有幾個,不會大量膨脹。
產生隨機數,O(1)
排序取下標,根據排序演算法,O(logM)即可實現
取值,根據下標,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 */ package com.xxx.xxx.xxx.service; import java.util.*; /** * 抽獎工具類 * @Author yuanyicheng * @Date 7:14 上午 2020/9/9 */ public class DrawUtil { public static Gift draw(List<Gift> gifts) { if (null == gifts || gifts.size() == 0) { return null; } gifts.sort((o1,o2) -> (o1.prob - o2.prob) > 0 ? 1 : -1); List<Double> probLists = new ArrayList<>(gifts.size()); Double sumProb = 0D; for (Gift gift : gifts) { sumProb += gift.getProb(); } if (sumProb <= 0) { return null; } // 歸一化概率端點 Double rate = 0D; for (Gift gift : gifts) { rate += gift.getProb(); probLists.add(rate / sumProb); } double random = Math.random(); probLists.add(random); Collections.sort(probLists); int index = probLists.indexOf(random); if (index >= 0) { return gifts.get(index); } return null; } public static void main(String[] args) { List<Gift> gifts = new ArrayList<>(); Gift nothing = new Gift("謝謝惠顧",0.5D); Gift vip = new Gift("XX會員1個月",0.4D); Gift phone = new Gift("手機",0.1D); gifts.add(nothing); gifts.add(phone); gifts.add(vip); // 抽獎 // Gift g = draw(gifts); // 以下是測試統計 Map<String,Integer> countMap = new HashMap<>(); for (Gift gift: gifts) { countMap.put(gift.getName(),0); } countMap.put("null",0); for (int i=0; i<1000; i++) { // 抽一個 Gift gift = draw(gifts); String name = "null"; if (null != gift) { name = gift.getName(); } int count = countMap.get(name); countMap.put(name,++count); } for (Map.Entry<String,Integer> entry : countMap.entrySet()) { System.out.println("抽到"+entry.getKey()+","+entry.getValue()+"次"); } } } /** * 獎品類 */ class Gift { String name; Double prob; public Gift(String name,Double prob) { this.name = name; this.prob = prob; } 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; } }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。