一个随机数引发的血案

博客探讨了一个面试题:如何使用rand3()和rand5()生成rand7()。作者指出许多答案过于复杂,实际上这是一个组合排列问题。通过多次求和求模可以达到概率均衡,但某些答案忽视了概率分布的均匀性。文中提供了测试程序和分析,强调理解概率分布和平滑概率的作用。
摘要由CSDN通过智能技术生成

一个随机数引发的血案

我也来做一次标题党
原文地址:http://blog.csdn.net/WinsenJiansbomber/article/details/50604653

目录

起因

事情是这样的,刚不久在CSDN上看到有ASK.CSDN.NET,就点了进来,发现CSDN也来搞百度知道这类东东。于是乎就点了一些问题来看,其中有一条,是关于随机数应试题的。

一个编程面试题,只要写出伪代码就可以了。试题内容:假设有如下两个函数 rand3()可以产生随机的0 1 2,rand5()可以产生随机的0 1 2 3 4,现在请你利用它编写一个函数rand7(),产生0~6的随机数。

问题还不是这个应试题,要命的是参考答案。在看到 ask.csdn.net 这个问题之前,我从未觉得随机数是这么好玩的东西。先不说 csdn 玩 ask 有点炒 codeproject 冷饭的感觉,就凭看到这个问题的一些花花绿绿的答案,就觉得国内国外的程序从业人员水平差距真不是一般小。

问题根源

其实,这个问题有点像是代码复用范围的问题,题目已经给出 rand3 和 rand5 这两个程序所依赖的功能。作答者只需根据条件来重组它,得到问题所要求的逻辑。万万没想到的是,出现的答案竟然是 for while 随便用的,两层 while 还不够的境况!有些代码则差不多可以写成一篇独立的文章了。

所以,从这些现象看来,似乎作答者都是忘记了题目,在侃大山的。如果真是这样,倒也还好,起码显露得不是真正的水平。

从原理上来讲,这是高中的组合排列问题。这个问题,我记得 Robert 在《the Art and Science of C》这本书中以骰子举过生动的例子。一个六面的骰子,随机出现任一面的概率都是1/6。抛两次,将会有36种情况,将两个结果相加为0、12的情况只有一种,就是两次都同样跌出0、6。相比相加结果是6这种情况,两次抛出的骰子可以是0+6,6+0,1+5,5+1,2+4,4+2,3+3等6种情况。因此它们结果的概率是程一个单峰山形的分布,中间高,两边低。

特别地,当抛的次数越多,则相邻的两个数的概率差别的变化就会变得相对缓慢。因此,通过多次求和再求模的方式,可以一定程度将概率变得更均匀,甚至是可以认为是均衡的,但从原理上它们是不平均的。这一点对于本题来讲,是比较诡异的一个特点。下面以统计数据的方式来说明,以5次rand3相加为例,一次实测的数据是这样的:

1 ->  2013  2.01%
2 ->  6177  6.18%
3 -> 12325 12.33%
4 -> 18404 18.40%
5 -> 21045 21.05%
6 -> 18641 18.64%
7 -> 12394 12.39%
8 ->  6121  6.12%
9 ->  2070  2.07%
10 ->   411 0.41%

数据可以看到通过直接的相加得到的是概率分布是差异明显的,如果通过求模的方式来求取 0-5 的概率分布,则会出现惊人的效果:

1 ->  2013  2.01% + 7 -> 12394 12.39% = 14.40%
2 ->  6177  6.18% + 8 ->  6121  6.12% = 12.30%
3 -> 12325 12.33% + 9 ->  2070  2.07% = 14.40%
4 -> 18404 18.40% + 10 ->   411 0.41% = 18.81%
5 -> 21045 21.05%                     = 21.05%

结果显示,概率分布已经明显地趋于平均分布,因为求模过程产生了互补效应,只要设置的求模点恰当,概率的分布是可以接近平均分布的。

对应方案

那么,说了这么多,参考答案有些什么内容呢,下面就连同我专门为随机数这个问题写的一个测试程序一同奉上:

<!DOCTYPE>
<html>
 <head>
  <title> random test </title>
  <meta name="link" content="http://ask.csdn.net/questions/234228" />
  <meta name="generator" content="editplus" />
  <meta name="author" content="Jimbowhy" />
 </head>
 <body>
 <div id="canvas"></div>
  <script type="text/javascript">
  //此处放入javascript代码
  </script>
 </body>
</html>
function rand3(){
      return ~~(Math.random()*3);
  }
  function rand5(){
      return ~~(Math.random()*5);
  }
  function output(name, a, k){
      var canvas = document.getElementById("canvas");
      canvas.innerHTML += "<h3>"+name+ " " + k +"</h3>";
      for(var i in a){
        var v = a[i];
        canvas.innerHTML += i + " -&gt; " + v + " " + (v/k*100).toFixed(2) + "%<br>";
      }
  }
  function test(target, k){
      if(!target) return;
      var a = [];
      for(var i=0; i<k; i++){
          var j = target();
          !a[j]? a[j] = 1 : a[j]++;
      }
      output("Test "+target.name,a,k);
  }

  function rand7(){
    var sum=0;
    sum+=rand3();//0,1,2
    sum+=rand3()+3;//3,4,5
    sum+=rand3()+6;//6,7,8
    sum+=rand5()+9;//9,10,11,12,13
    sum%=7;//0,1,2,3,4,5,6
    return sum;
  }//代码很具有迷惑性,但是将代码化简一下就是:
   //sum=3*rand3()+rand5()+18!这已经面目全非,再求模也是枉然啊!
  function rand7f(){
    return rand3()+rand3()+rand3();
  }//单峰形,一眼看去,好象三个 rand3 各自独立互不影响,
   //其实出现0、6的概率是远低于出现3的概率的。
   //一眼看上去好像很正确,其实它是典型的单峰形,中间的数出现概率最高,
   //两边的就最低。无数据,根本想不到它们有多离谱
  function rand7e(){
    var sum = 0;
    for(var i=0;i<7;i++) {
        sum += rand5() + i*5;
    }
    return sum%7;
  }//0-34等概率数对7求模,近似等概率OK,只是 i*5 没有存在的意义,
  //因为5+10+...+30=105,105%7=0
  function rand7d(){
    var sum = 0;
    for (var i=0;i<7;i++) {
        sum+= rand3()+i*3;
    }
    return sum%7;
  } // 双峰形
  function rand7c(){
    var sum;
    switch(rand3())
    {
    case 0:
        sum=rand5();//0,1,2,3,4
        break;
    case 1:
        sum=rand5()+5;//5,6,7,8,9
        break;
    case 2:
        do
        {
            sum=rand5()+10;//10,11,12,13,14
        }while(sum!=14);
        break;
    }
    sum%=7;//0,1,2,3,4,5,6
    return sum;
  }//递减形
  function rand7b(){
      return rand3() + rand5()
  }//中峰形
  function rand7a(){
    var x5 = rand5();
    var x3 = rand3();
    while (x5 == x3 ) {
        x3 = rand3();
    }
    return x5+x3;
  }//高低起伏形
  test(rand5, 9999);
  test(rand7, 99999);
  test(rand7a, 99999);
  test(rand7b, 99999);
  test(rand7c, 99999);
  test(rand7d, 99999);
  test(rand7e, 99999);
  test(rand7f, 99999);

将以上内容保存为HTML文件,并用浏览器如CHROME打开它,就可以看到运行结果了。

更新

关于rand7c,ysuwood修改了while条件,我在分析TA的答案时没有注意到,因此要重新分析:

回复ysuwood:
这回看到了,要再分析一下。如果while==14,就直接将0的概率截掉一大块了。代码近乎实用,但是还有个问题,大家可能没留意到:case 0 - 2 的概率分布是平均,也就是说,出现0-4和5-9,还有10-14的概率是一样的,那么求模后的结果好像就是概率平均分布的!其实不然,当while条件中把14废了之后,rand5出现14的概率还在,此时只好重新抽,这,就是这里,意味着出现14的概率转接到了10-13这几个数上,所以这几个数不明显地比0-9这几个数的概率要高。之所以说不明显,是因为经过了求模的运算,它有平滑概率的作用。@caozhy 好像也没注意到这个情况。

为了验证这个分析,可以将rand7c中的求模运算注解掉,再运行,这样结果就会显示0-13这几个数的概率分布了。

再有,在评论TA的另一个答案时,硬是生生地把几行代码错误地简化成了一条语句,原代码是这样的:

sum+=rand3();//0,1,2
sum+=rand3()+3;//3,4,5
sum+=rand3()+6;//6,7,8
sum+=rand5()+9;//9,10,11,12,13

化简过程在这里:

Jimbo 回复ysuwood: 哦,还真的是,我掉坑里了,SORRY,SORRY,SORRY,误导看官。但是三个相加也不是正确的做法。

ysuwood 回复Jimbo: 随机数能相加和乘3等价吗?糊涂了吧。

Jimbo: 代码很具有迷惑性,但是将代码化简一下就是:sum=3*rand3()+rand5()+18!这已经面目全非,再求模也是枉然啊!

另外 @lovingning 提供了一个解答问题的正确思路:

lovingning: 第一枚骰子有三种可能,第二枚骰子有五种可能,排列组合有十五种可能,取其中的七种或十四种,剩下的抛弃不就行了,只要保证七分之一或者十四分之一,反正是电脑运行,有什么资源浪费的

电脑运行是其次的,主要问题是概率要平均,这是重点。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值