您当前位于: 首页 » 我爱PHP » 令人纠结的php几率算法问题

令人纠结的php几率算法问题06/24/2010

唉!现在终于发现上学时不好好念书有多少的坏处了,概率几率对于我来说一直是一个很难弄清楚的问题。今天,我又继续让这个问题纠结上了。好吧!来说说我的那点事儿,首先注明一下:这是一篇求助性文字,我的几率算法也许根本就是不对的。如果恰巧有数学系专家学者路过,那希望您能留言说两句。问题是这样子的:在一个物品合成系统中,需要使用几件不同的物品来合成宝石,有不同的几率生成一颗(50%)、两颗(16%)和三颗(2%)宝石,其余的为爆掉,即什么也得不到。

我的PHP代码实现是这样子的:

<?php
//初始化数组
$stone_arr = array( 
		array( 'num' => 1, 'prob' => '50%' ),
		array( 'num' => 2, 'prob' => '16%' ),
		array( 'num' => 3, 'prob' => '2%' )
		 );
//随机获得一个幸运数字
$luck_num = mt_rand( 0, 99 );
//初始化几率区间和最终宝石生产数目
$lucky_range = $made_num = 0;
 
foreach( $stone_arr as $sa ){
	$prob = intval( $sa['prob'] );
	if( $luck_num >= $lucky_range && $luck_num < $lucky_range + $prob ){
		$made_num = $sa['num'];
		break;
	}
	else{
		$lucky_range += $prob;
	}
}
 
for( $i = 0; $i < $made_num; $i++ ){
	//生产宝石的逻辑
}
 
?>

左思右想,我总觉得这个并没有问题,可我的同事坚持认为$luck_num = mt_rand( 0, 99 )这一行应该放到循环体的里面,说的也有道理呀!不过我觉得这样是不是多给了玩家机会?(当然,从玩家的角度来说,机会越多越好。呵呵~),截至记者发稿时止,我还是没有弄清楚是不是该把随机数放到循环里面?或者这两种方法都不对?麻烦路过的明眼人给俺说道说道,不甚感激

| 25条评论 标签:  

25条评论
  1. wynn说道:

    你贴的这个是对的,放进循环内是错误的。
    这段代码本质上是实现了阶梯表查找,即先生成了一个随机数,随后通过逐区间查找的方式确定这个随机数落在哪个区间内。
    从逻辑上判断很容易,单次合成是“一个”动作,并不涉及多步逻辑,显然只生成一次随机数才是合理的。

    把随机数生成挪到循环内的错误性很容易证明,举一个反例就可以了。考虑这样一种生成概率:60%机会生成1颗,40%机会生成2颗。那么显然,这样一种生产概率下,合成有100%的几率得到宝石,只是数量的问题而已。假如随机数生成是在循环内,却有可能每一轮区间比较均失败,最后什么都没有。例如,在第一次执行循环体时,生成了70,于是第一次循环比较失败(需要的是在0与60之间),进入第二次循环,而这时候又运起不好随机生成了30,于是第二次又比较失败,最后的结果是0个宝石。这个案例就可以作为反例证明其错误性。

    在你这个程序中,假如其他不变动,只把随机数生成挪到循环内部,那么实际概率也是可以计算的:
    1颗: 50%
    2颗: (1-50%) * 16% = 8%
    3颗:(1-50%) * (1-16%) * 2% = 0.84%
    无产品:(1-50%) * (1-16%) * (1-2%) = 41.16%

    其实如果只是单纯要比较2个方案,最简单的方案就是让他们分别跑上几万次,统计结果概率,和需求概率做一下比较马上就可以看出来了。

    ps. 博主是福州人?我们还是老乡耶……

    • gently说道:

      啊!太感谢你能给出如此详尽的回复,受教了。谢谢!概率这个东西着实一直困扰的我头疼,看了您的解答,我又明白了一些,还得修炼。。。呵呵!我算半个福州人吧,现在福州工作

  2. wynn说道:

    补充:正文中的代码其实有一个bug,我以上的分析是无视了这个bug的。
    就是这行: $luck_num = mt_rand( 0, 100 );
    PHP中的mt_rand后面的参数指定的是一个闭区间,也就是说mt_rand( 0, 100 )实际上可能结果是101个数(包括了0和100),因此按照需求,这里应该写mt_rand( 0, 99 )才是正确的。

  3. ray说道:

    放外面吧?放里面几率变大了…

  4. wynn说道:

    汗,我是福州人,但是现在不在福州工作>_<

  5. wynn说道:

    回ray:
    计算表明,放里面实际上是概率变小了……

  6. cos.x说道:

    看不懂…
    我的做法是:
    <?php
    $a = array_fill(0,50, 1);
    $b = array_fill(0,16, 2);
    $c = array_fill(0,2, 3);
    $d = array_fill(0,32, 0);
    $arr = array_merge($a, $b, $c);
    //var_dump($arr);
    $d = mt_rand(0,99);
    echo $arr[$d];

  7. cos.x说道:

    顺便说一下 我也是福州的 在福州工作

  8. Gery说道:

    这个东西实际上很简单,不需要考虑那么深:
    function a(){
    $n = mt_rand(0,99);
    $result = 0;
    if($n >= 0 && $n = 0 && $n = 2 && $n = 50 && $n <= 99){
    $result = 1;
    }
    return $result;
    }

  9. Gery说道:

    function a(){
    $n = mt_rand(0,99);
    $result = 0;
    if($n >= 0 && $n = 1){
    $result = 1;
    }
    if($n >=2 && $n =50 && $n <= 99){
    $result = 2;
    }
    return $result;
    }

  10. Gery说道:

    function a(){
    $n = mt_rand(0,99);
    $result = 0;
    if($n >= 0 && $n = 1){
    $result = 1;
    }
    if($n >=2 && $n = 50 && $n <=99){
    $result = 3;
    }

    return $result;
    }

  11. Gery说道:

    晕,这个页面程序有BUG,我写程序到上面都帮我改了!

  12. 宇风说道:

    博主福州那上班?

  13. hemon说道:

    看楼主的zendstudio设置xdebug,分享一下我的一段概率执行代码:
    http://www.hemono.com/?p=268

    1. /**
    2. * 以$x/$y的概率返回true
    3. *
    4. * @param int $x
    5. * @param int $y
    6. * @return bool
    7. *
    8. * prob(1,2) = 1/2
    9. * prob(1,100) = 1/100
    10. *
    11. */
    12. function prob($x, $y){
    13. return ( mt_rand(0, $y-1) < $x );
    14. }

  14. hemon说道:

    博主,你的flash录屏工具叫什么名字?

  15. tqjs说道:

    如果是我会这么做
    $stone_arr = array(
    array( ‘num’ => 1, ‘prob’ => array(1,…50) ),
    array( ‘num’ => 2, ‘prob’ => array(51,…66) ),
    array( ‘num’ => 3, ‘prob’ => array(67,68) )
    );
    $seed = mt_rand(1,100);
    $num = 0;
    接下来,就是循环数组 $stone_arr,看看$seed 有没有元素的’prob’的数组内,如果有,就取得对应的num,如果循环结束num == 0那就是爆掉了,明白意思了吗,我觉得这样是最简单的,但是效率和算法优越性什么的没去考虑,希望改进或批评意见

  16. mengxianfeng说道:

    $arr=array();
    for($i=0;$i0 and $seed50 and $seed$value){
    echo $key.’====>’.$value.”;
    }

  17. mengxianfeng说道:

    不需要循环的吧
    $seed = mt_rand(1,100);
    if($seed>0 and $seed50 and $seed<67){
    $re =2;
    }else if($seed==67 or $seed == 68){
    $re =3;
    }else{
    $re =0;
    }

  18. mengxianfeng说道:

    应该不需要循环,也不需要初始化概率生成区间,从概率论上讲,指定区间代表概率为1,这样不会影响最终的结果,所以只要指定生成宝石的区间范围即可,比如可以指定[1,50]->1,[51,66]->2,[67,68]->3,[69,100]->0,只要你指定的四个区间能完全覆盖你随机生成数的范围即可,比如你可以指定生成3个宝石的区间为39和20这两个数,但是要保证这2个数不能在其他的区间范围内,而后根据随机生成的数字落在那个区间就可以得到宝石的数量,这样应该不需要循环就可以得到结果。希望能对你有帮助。

  19. xiaoba说道:

    第一次来到你们的网站,感觉里面很多文章都很多意思,跑题了
    毫无疑问,随机数肯定放到循环外面的,wynn说的比较详细了,反证法很有说服力

  20. 说道:

    $rand = rand(1, 100);
    if($rand <= 50) {
    echo 1;
    } elseif($rand <= 66) {
    echo 2;
    } elseif($rand <= 68) {
    echo 3;
    } else {
    echo 0;
    }

发表评论