1. 程式人生 > 程式設計 >Java實現遊戲抽獎演算法

Java實現遊戲抽獎演算法

常用抽獎演算法對比

基礎的遊戲抽獎演算法通常要求實現在指定獎品的集合中,每個獎品根據對對應概率進行抽取。個人瞭解的主要有以下幾中抽獎演算法:

隨機數一一對應

演算法思想

這種演算法思想最為簡單。將n個獎品編號0 - N-1,其中各類獎品的概率通過其數量體現,最後程式產生0~n-1之間的隨機數便是抽中的獎品編號。例如:

蘋果手機概率1%,網站會員20%,折扣券20%,很遺憾59%。這樣,編號0是蘋果手機,1-20是會員,21-40是折扣券,41~100是 很遺憾。產生的隨機數落在那個區間,就代表那個獎品被抽中。

存在問題

1、總數N快速膨脹

概率通過數量來體現在各個獎品概率較大的情況下,總數n可以較小。但如果在精度很高的情況下,總數必須按比例成倍擴大。

例如,所有獎品概率都是10%,那麼n只需要取10就可以。但是如果某個獎品概率是0.01%,按照這種演算法,總數要擴大到100*100。

2、平衡性影響

在Java中,Math.random()方法本身基本可以保證大量測試的情況下避免高重複,且概率分佈比較平均。但是需要注意的是,該方法預設返回0-1之間的資料。

在當前演算法中,必須擴大指定倍數並且強制使用int進行型別轉換。在這樣的擴大和轉換過程中,必然會對資料精度進行修改,轉換後的資料也不能保證概率分佈平均。

因此,該演算法實際可能達不到預期的概率要求。

3、演算法複雜度

資料準備階段,為每個獎品確定編號與獎品資訊的關係集合需要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在原集合中的索引。

Java實現遊戲抽獎演算法

特點

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;
 }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。