首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 概率算法實作:抽獎程式、隨機廣告與加權隨機選擇完整指南

     編輯:PHP 相關知識

網站活動常需按機率發放獎品(如 1% 中 iPad、50% 中 Q幣)。本文詳細解析兩種經典 PHP 概率算法,並提供完整的獎品配置與抽獎邏輯實作,含大獎保護機制與前端整合建議。

核心算法對比

算法一:陣列展開法 (空間換時間)

<?php
/**
 * 全機率計算 - 陣列展開法
 * @param array $ps 機率陣列 ['獎項A' => 0.5, '獎項B' => 0.3, '獎項C' => 0.2]
 * @return string 中獎鍵名
 */
function random($ps) {
    static $cache = [];
    
    // 建立快取鍵 (序列化陣列產生 MD5)
    $key = md5(serialize($ps));
    
    if (!isset($cache[$key])) {
        $max = array_sum($ps);  // 總權重
        $pool = [];
        
        // 按權重比例填充池子 (放大 10000 倍精度)
        foreach ($ps as $k => $v) {
            $count = intval($v / $max * 10000);
            for ($i = 0; $i < $count; $i++) {
                $pool[] = $k;
            }
        }
        $cache[$key] = $pool;
    }
    
    // 隨機取一個
    return $cache[$key][mt_rand(0, count($cache[$key]) - 1)];
}

優點:取樣極快 O(1)、適合固定機率重複抽樣
缺點:記憶體佔用大 (權重精度高時)、機率變動需重建快取

算法二:累積機率法 (經典、高效、記憶體友善) ✅ 推薦

<?php
/**
 * 經典加權隨機選擇 - 累積機率法
 * @param array $proArr 權重陣列 ['獎項A' => 100, '獎項B' => 200, '獎項C' => 300]
 * @return string 中獎鍵名
 */
function get_rand($proArr) {
    $result = '';
    $proSum = array_sum($proArr);  // 總權重
    
    foreach ($proArr as $key => $proCur) {
        $randNum = mt_rand(1, $proSum);  // 1 到總權重間隨機
        
        if ($randNum <= $proCur) {
            $result = $key;
            break;  // 命中,結束循環
        } else {
            $proSum -= $proCur;  // 未命中,縮減權重範圍
        }
    }
    
    unset($proArr);
    return $result;
}

原理:將權重視為連續數軸區間,隨機落點落在哪個區間即中該獎項
優點:O(n) 時間、O(1) 空間、支援動態權重、大數據量極高效
缺點:權重項目極多 (萬級) 時循環較慢,可優化為二分搜尋

完整抽獎系統實作

<?php
/**
 * 抽獎核心類
 */
class Lottery {
    private $prizes = [];
    private $remaining = [];
    
    public function __construct(array $prizes) {
        // 獎品格式: ['id'=>1, 'name'=>'iPad', 'weight'=>1, 'total'=>10, 'won'=>0]
        $this->prizes = $prizes;
        $this->reset();
    }
    
    public function reset() {
        $this->remaining = [];
        foreach ($this->prizes as $p) {
            $this->remaining[$p['id']] = [
                'name' => $p['name'],
                'weight' => $p['weight'],
                'left' => $p['total'] - $p['won']
            ];
        }
    }
    
    /**
     * 執行抽獎
     * @return array ['won'=>中獎獎品, 'remaining'=>未中獎列表]
     */
    public function draw() {
        // 建構當前可用獎品權重陣列
        $weights = [];
        foreach ($this->remaining as $id => $info) {
            if ($info['left'] > 0) {
                $weights[$id] = $info['weight'];
            }
        }
        
        if (empty($weights)) {
            return ['won' => '獎品已發完', 'remaining' => []];
        }
        
        // 執行機率抽選
        $won_id = get_rand($weights);
        $won_prize = $this->remaining[$won_id]['name'];
        
        // 更新庫存
        $this->remaining[$won_id]['left']--;
        
        // 整理未中獎列表 (打亂順序)
        $remaining_names = [];
        foreach ($this->remaining as $id => $info) {
            if ($id != $won_id && $info['left'] > 0) {
                $remaining_names[] = $info['name'];
            }
        }
        shuffle($remaining_names);
        
        return [
            'won' => $won_prize,
            'remaining' => $remaining_names
        ];
    }
    
    /**
     * 獲取當前獎品狀態
     */
    public function getStatus() {
        return $this->remaining;
    }
}

// ===== 使用範例 =====
$prize_config = [
    ['id' => 1, 'name' => '平板電腦', 'weight' => 1,   'total' => 2,  'won' => 0],
    ['id' => 2, 'name' => '數碼相機', 'weight' => 5,   'total' => 5,  'won' => 0],
    ['id' => 3, 'name' => '音箱設備', 'weight' => 10,  'total' => 20, 'won' => 0],
    ['id' => 4, 'name' => '4G 優盤',  'weight' => 12,  'total' => 50, 'won' => 0],
    ['id' => 5, 'name' => '10 Q幣',   'weight' => 22,  'total' => 200,'won' => 0],
    ['id' => 6, 'name' => '下次沒准就能中哦', 'weight' => 50, 'total' => 9999, 'won' => 0],
];

$lottery = new Lottery($prize_config);

// 模擬 1000 次抽獎統計
$stats = [];
for ($i = 0; $i < 1000; $i++) {
    $result = $lottery->draw();
    $stats[$result['won']] = ($stats[$result['won']] ?? 0) + 1;
}

echo "統計結果:\n";
foreach ($stats as $prize => $count) {
    echo "$prize: $count 次 (" . ($count/10) . "%)\n";
}

機率設定對照表

權重總和權重值 1 的機率適用場景
1001%百分比直觀設定
10000.1%千分位精度
100000.01%萬分位精度 (大型活動)
10000000.0001%極低機率大獎

大獎保護機制 (防止超發)

<?php
// 在 draw() 方法前加入庫存檢查
public function drawWithProtection() {
    // 1. 檢查大獎是否達上限
    foreach ($this->remaining as $id => $info) {
        if ($info['left'] <= 0) {
            unset($this->remaining[$id]);  // 直接移除已發完獎品
        }
    }
    
    // 2. 如果只剩安慰獎,直接返回
    if (count($this->remaining) === 1) {
        $only = array_values($this->remaining)[0];
        $only['left']--;
        return ['won' => $only['name'], 'remaining' => []];
    }
    
    return $this->draw();
}

前端 AJAX 整合範例

<!-- HTML -->

<script> function startDraw() { const btn = document.getElementById('draw-btn'); btn.disabled = true; btn.textContent = '抽獎中...'; fetch('/api/lottery/draw.php', {method: 'POST'}) .then(res => res.json()) .then(data => { document.getElementById('result').innerHTML = `恭喜獲得:${data.won}`; btn.disabled = false; btn.textContent = '再試一次'; }) .catch(err => { alert('網路錯誤'); btn.disabled = false; btn.textContent = '開始抽獎'; }); } </script>

為什麼你抽不到大獎?(揭秘)

若主辦方將大獎權重設為 0,則該獎項 永遠無法中獎。某些活動甚至在前端顯示「大獎就在旁邊」的煙霧彈獎項,實際程式邏輯早已決定結果。理解算法原理,才能看清遊戲規則。

算法優化:二分搜尋版 (適用萬級獎項)

<?php
function get_rand_binary($proArr) {
    // 建構累積權重陣列
    $prefix = [];
    $sum = 0;
    foreach ($proArr as $k => $v) {
        $sum += $v;
        $prefix[$k] = $sum;
    }
    
    $rand = mt_rand(1, $sum);
    
    // 二分搜尋
    $keys = array_keys($prefix);
    $low = 0;
    $high = count($keys) - 1;
    
    while ($low <= $high) {
        $mid = (($low + $high) >> 1);
        $key = $keys[$mid];
        
        if ($prefix[$key] >= $rand) {
            $high = $mid - 1;
        } else {
            $low = $mid + 1;
        }
    }
    
    return $keys[$low] ?? array_pop($keys);
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved