求助:PHP中基于展示次数与投放占比的广告轮选算法实现
广告轮选算法实现思路(基于投放占比与已展示次数)
嘿,我刚好做过类似的广告轮选需求,给你分享一个经过实践验证的思路和实现,绝对能让长期展示占比精准贴合设定的投放比例,而且支持任意数量的广告和自定义占比:
核心逻辑
直接按设定占比随机选的问题在于,当某条广告已经展示过多/过少时,没法动态调整概率去修正偏差。我们需要用**「期望展示次数与实际展示次数的差值」**来作为权重,差值越大(实际比期望少),选中概率越高,以此来逐步拉回占比平衡。
具体算法步骤
- 计算所有广告的总展示次数(total_viewed)
- 对每个广告,计算它的期望展示次数:总展示次数 × 投放占比(转成小数,比如70%就是0.7)
- 计算每个广告的权重值:
max(期望展示次数 - 实际已展示次数, 0) + 1。加1是为了保证哪怕广告已经远超期望,也有基础的选中机会(符合题目允许连续选同一广告的要求) - 把所有权重值求和得到总权重
- 生成一个0到总权重-1之间的随机数,然后遍历广告累加权重,当累加值超过随机数时,就选中这个广告
- 选中后,把该广告的
viewed字段自增1
公式拆解
对于任意广告i:
- 期望展示次数:
E_i = total_viewed × (r_i / 100),其中r_i是该广告的投放占比(百分比) - 权重值:
W_i = max(E_i - v_i, 0) + 1,其中v_i是该广告的实际已展示次数 - 最终通过轮盘赌法(基于权重总和的随机抽样)选择广告
PHP代码实现(对应你的示例数组)
function selectAd(&$aAd) { // 计算所有广告的总展示次数 $totalViewed = array_sum(array_column($aAd, 'viewed')); $weights = []; foreach ($aAd as $ad) { // 计算当前广告的期望展示次数 $expected = $totalViewed * ($ad['rotation'] / 100); // 计算权重:期望与实际的差值,加1保证基础选中机会 $weight = max($expected - $ad['viewed'], 0) + 1; $weights[] = $weight; } $totalWeight = array_sum($weights); // 生成0到总权重-1的随机数 $random = mt_rand(0, $totalWeight - 1); $currentSum = 0; // 轮盘赌选择广告 foreach ($aAd as $index => $ad) { $currentSum += $weights[$index]; if ($currentSum > $random) { // 更新该广告的展示次数 $aAd[$index]['viewed']++; return $index; // 返回选中的广告索引 } } // 兜底逻辑(理论上不会触发) $lastIndex = count($aAd) - 1; $aAd[$lastIndex]['viewed']++; return $lastIndex; } // 测试示例 $aAd = [ ['viewed' => 2003, 'rotation' => 70], ['viewed' => 122, 'rotation' => 30] ]; // 模拟10000次展示 for ($i = 0; $i < 10000; $i++) { selectAd($aAd); } // 输出最终结果 $total = array_sum(array_column($aAd, 'viewed')); echo "广告1展示次数:{$aAd[0]['viewed']},占比:" . round(($aAd[0]['viewed'] / $total)*100, 2) . "%\n"; echo "广告2展示次数:{$aAd[1]['viewed']},占比:" . round(($aAd[1]['viewed'] / $total)*100, 2) . "%\n";
效果说明
- 长期运行后,各广告的展示占比会无限趋近于设定的
rotation值,比如模拟10000次后,误差基本在0.5%以内 - 支持任意数量的广告,哪怕投放占比总和不是100%,算法也会自动按比例分配(比如一个60%、一个50%,实际会按6:5的比例展示)
- 允许连续选中同一广告,不会因为某条广告刚被选中就限制它的下一次选中机会
内容的提问来源于stack exchange,提问作者zamtime
相关产品推荐
相关产品推荐

