随机函数Java中的 Math.random()

1.验证Math.random()的等概率性

我们知道Math.random()是等概率返回[0,1)随机数,来一起验证下。

 public static void getRandom(){
    /**
         * todo 测试random方法是不是等概率随机的
         * 结果测定,是等概率的
         */
        int testTimes = 10000000;
        int count = 0;
        double ans;
        for(int i = 0; i < testTimes; i++){
            //[0,1)
            ans = Math.random();
            if(ans < 0.5){
                count ++;
            }
        }
        System.out.println(count);
        System.out.println("测试随机数是不是等概率出现的,测一下小于0.5得数出现的概率是多少: " + (double)count / (double)testTimes);
}

//输出结果
5000803
测试随机数是不是等概率出现的,测一下小于0.5得数出现的概率是多少: 0.5000803

我们可以看到大量测试后,发现[0,0.5)的概率大约是0.5,当然这个数值可以随便修改进行测试,都是一样的。

2. 随机数Math.random()的进阶

我们都知道Math.random()可以返回[0,1)中的任意数,那我想随机返回[0,10)的数据呢?没错,乘以10。

int result = Math.random()*10;

那我们如何验证[0,10)之间的等概率性呢,我们验证一下[0,10) 的int类型的数据是不是等概率返回的。

   /**
         * todo 测定修改为任意随机数,暂定修改为[0,10)
         *
         */
        int[] counts = new int[10];
        int ans1;
        for(int i = 0; i < testTimes; i++){
            //[0,10)
            ans1 = (int)(Math.random() * 10);
            counts[ans1] ++;
        }

        for(int i = 0; i< 10; i++){
            System.out.println("测试数据ans1  "+ i + " 出现了: " +  counts[i] + " 次");
        }

//输出结果
测试数据ans1  0 出现了: 999458 次
测试数据ans1  1 出现了: 999601 次
测试数据ans1  2 出现了: 999948 次
测试数据ans1  3 出现了: 1000671 次
测试数据ans1  4 出现了: 1001815 次
测试数据ans1  5 出现了: 998365 次
测试数据ans1  6 出现了: 999731 次
测试数据ans1  7 出现了: 999684 次
测试数据ans1  8 出现了: 999788 次
测试数据ans1  9 出现了: 1000939 次

可以看出来各个数字出现的次数相差无几,还是等概率的。
[0,10)是 Math.random()*10得到的,举一反三,加一个数还可得,[1, 11),[2,12)等等。


假如我在[0,1)上有一个x,我想得到x平方该怎么做呢?
思路:我们可以有两次随机取值,这两次随机取值都是x, 是不是就是x平方

   //任意的x,x属于[0,1),概率由原来的x 得到x平方
    public static double getXToTwo(){
        /**
         * @ 只有max才能求得x方
         */
         return Math.max(Math.random(), Math.random());

        //求得不是x方,而是最大落在[0,x)的概率,并不是x方
//        return Math.min(Math.random(), Math.random());
    }

第一次的Math.random()得到x, 第二次Math.random()得到x,取最大值,是不是就是x平方呢。我们可以验证一下。

        count = 0;
        double x = 0.4;
        for(int i = 0; i < testTimes; i++){
            double xToTwo = getXToTwo();
            if(xToTwo < x){
                count ++;
            }
        }
        System.out.println((double) count/ (double) testTimes);
        System.out.println("测试随机数是不是等概率出现的,测一下小于0.4的平方的数出现的概率是多少: " + Math.pow(x, 2));

//输出结果
0.1600108
测试随机数是不是等概率出现的,测一下小于0.4的平方的数出现的概率是多少: 0.16000000000000003

可以看到,当我调用了上述的求x平方的函数时,我假设x是0.4,求得x的平方大约是0.16多,和我们调用函数Math.pow(x,2)求平方是差不多的。以此类推,我想求得x的3次方,调用三次Math.random()方法,求得最大值小于x,即可得到x的3次方。

return Math.max(Math.random(), Math.max(Math.random(),Math.random()));

3.关于随机函数的经典面试题

A. 函数f()等概率返回1~5,在不改变f()函数内部的前提下,如何获得1~7的等概率返回函数g()?

大致思路:

  • 1~5等概率返回,那可以利用f()函数改造一个等概率返回0和1的函数f0();
  • f0()函数等概率返回0和1,利用二进制转换的逻辑,使f0()函数完成一个等概率返回000~111的函数f1();
  • 二进制转换000~111即0~7,f1()函数等概率返回0~7,要想获得等概率返回1~7的函数g(),此时有两种思路均可;
  1. 直接获得,f1()等概率返回0~7时,如果是0,就重新返回,即可得到等概率返回1~7的函数g();
  2. 间接获得,f1()等概率返回0~7时,如果是7,就重新返回,即可得到等概率返回的0~6的函数f2(); 对f2()函数进行加1 的操作就可得到g()函数。

以上就是等概率获取1~7的所有思路,接下来,详细解释和代码编写。


第一步

  • 利用f()函数改造一个等概率返回0和1的函数f0();
    f()函数等概率返回1,2,3,4,5;假定
    f()函数返回1,2 => f0()函数返回0
    f()函数返回3 => 重来
    f()函数返回4,5 => f0()函数返回1
    这样的话,fo()函数可等概率的返回0和1。看看代码:
  //f函数,表示等概率的返回1~5
    public static int  f(){
       return (int)(Math.random()* 5 + 1);
    }

 //使用f进行封装处理,得一个等概率获取0,1的函数f0
    public static int f0(){
        int ans;
        do{
            ans = f();
        }while (ans == 3);
        //方法是1,2-> 0, 4,5-> 1, 3重新进入循环,那么该方法就会等概率得到0,1
        return ans < 3 ? 0 : 1;
    }

第二步

  • f0()函数等概率返回0和1,利用二进制转换的逻辑,使f0()函数完成一个等概率返回000~111的函数f1();
    (f0()<< 2) + (f0() << 1) + (f0() << 0) 可等概率返回000~111之间的数,代码如下:
   //使用f0方法得到等概率返回0~7的方法f1
    //使用的是二进制表示,000~111 表示0~7
    public static int f1(){
        int ans = (f0() << 2) + (f0() << 1) + f0();
        return ans;
    }

第三步

  • 二进制转换000~111即0~7,f1()函数等概率返回0~7,要想获得等概率返回1~7的函数g(),此时有两种思路均可;
    1. 直接获得,f1()等概率返回0~7时,如果是0,就重新返回,即可得到等概率返回1~7的函数g();
    //使用f1方法得到等概率返回1~7的方法g()
    //f1等概率返回0~7 ,那么遇到0重来,就会得到1~7
    public static int g(){
        int ans;
        do {
            ans = f1();
        }while(ans == 0);
        return ans;
    }
  1. 间接获得,f1()等概率返回0~7时,如果是7,就重新返回,即可得到等概率返回的0~6的函数f2(); 对f2()函数进行加1 的操作就可得到g()函数。
     //第二种方法,先根据f1方法得到等概率0~6的方法f2()
    //f1等概率返回0~7,那么遇到7重来,就可以得到0~6
    public static int f2(){
        int ans;
        do{
            ans = f1();
        }while (ans == 7);
        return ans;
    }

    //基于f2()方法得到1~7 的方法g()
    public static int g(){
        return f2() + 1;
    }

至此,这道题已经解决了,我们测试一下g()方法。

        //测试第一个g()方法是否会等概率返回1~7
        int[] count4 = new int[8];
        for(int i = 0; i < testTimes; i++){
            int ans2 = g();
            count4[ans2] ++;
        }
        for(int i = 1; i< 8; i++){
            System.out.println("测试数据ans1  "+ i + " 出现了: " +  count4[i] + " 次");
        }

//输出结果
测试数据ans1  1 出现了: 1428448 次
测试数据ans1  2 出现了: 1427468 次
测试数据ans1  3 出现了: 1427696 次
测试数据ans1  4 出现了: 1429870 次
测试数据ans1  5 出现了: 1426945 次
测试数据ans1  6 出现了: 1429856 次
测试数据ans1  7 出现了: 1429717 次

第一个g()方法测试结果可以看出来1~7是等概率返回的。
现在调第二个g()方法测试一下结果是否等概率返回1~7

        //测试g是否会等概率返回1~7
        int[] count6 = new int[8];
        for(int i = 0; i < testTimes; i++){
            int ans2 = g();
            count6[ans2] ++;
        }
        for(int i = 1; i< 8; i++){
            System.out.println("测试数据ans1  第二个方法 "+ i + " 出现了: " +  count6[i] + " 次");
        }

//输出结果
测试数据ans1   第二个方法 1 出现了: 1427601 次
测试数据ans1   第二个方法 2 出现了: 1429933 次
测试数据ans1   第二个方法 3 出现了: 1428051 次
测试数据ans1   第二个方法 4 出现了: 1428761 次
测试数据ans1   第二个方法 5 出现了: 1430342 次
测试数据ans1   第二个方法 6 出现了: 1428077 次
测试数据ans1   第二个方法 7 出现了: 1427235 次

测试结果与预期结果相同,两种获取等概率1~7的方法g()都没有问题。


B.f()函数等概率返回3~19,如何等概率返回20~56

这个题目是A题目的进阶版,不过通过A题目可能总结不出来这套题目的规律,B题目做完就可以掌握这套题目的规律变化。

大致思路同A题目相差不大。

  • f()函数等概率返回3~19,通过使用f()函数得到一个等概率返回0和1的函数f0() 做法是:[3, 10] 返回0;11 重来;[12,19]返回1;
  • f0()函数等概率返回0和1,我们粗略估计一下20~56需要6个二进制位,000000~111111 即 0~63;
  • 使用f1()函数表示等概率返回0~63,我们用f2()函数表示等概率返回0~36,做法:当f1() > 36时,重来,我们只需要f1返回0~36;
  • f2()函数等概率返回0~36,那f2() + 20就可以等概率返回20~56.

因为思路同A题目,所以不详细解释了,此处列出代码:

    //f等概率返回3~19
    public static int f(){
       return  (int)(Math.random() * 17 + 3);
    }

    //根据f函数得到等概率返回0,1的函数f0
    public static int f0(){
        int ans;
        do{
            ans = f();
        }while(ans == 11);
        return ans < 11 ? 0 : 1;
    }

    //根据f0函数得到0~63等概率发生的函数 f1()
    public static int f1(){
        return (f0() << 5) + (f0() << 4) + (f0() << 3) + (f0() << 2) + (f0() << 1) + f0();
    }

    //根据f1函数得到0~36等概率发生的函数f2()
    public static int f2(){
        int ans;
        do{
            ans = f1();
        }while (ans > 36);
        return ans;
    }

    //根据f2函数得到20~56等概率发生的函数g()
    public static int g(){
        return f2() + 20;
    }

到这里呢,这类题目已经完结了,B题目大致思路就是这类题目的规律,万变不离其宗,不管数据怎么变化,思路就是先等概率返回0和1,然后利用二进制表示需要等概率返回的数值范围,先获得0~b-a ,然后加上a,就可以获得了a~b等概率返回的函数了。

C. 假如函数f()不等概率返回0和1,那如何等概率返回0,1随机呢?

题目大致表示的是:f()函数只返回0和1,且不等,假如0返回概率为P,那么1返回的概率就是1-P,如何能让0,1 等概率随机。

解题思路:

  • f()函数 {0:P 1: 1-P},用两位二进制表示{00,01,10,11} 分别概率为{P2, P(1-P), (1-P)P, (1-P)2},可以看出来01和10是等概率的,两外两种不等概率;
  • 那我们可以调两次f()函数,两次不等即可得到01和10,其余的抛弃重来即可。

代码如下:

    //这里的0.8只是假设0和1不等概率返回
    public static int f(){
        return Math.random() > 0.8 ? 0 : 1;
    }
  
    //根据二进制得到随机到0,1的等概率
    public static int g(){
        int ans;
        do {
            ans = f();
        }while(ans == f());
        return ans;
    }

代码f()假设0的概率是0.8,1的概率是0.2,当我调用g()方法时,如果相等则重来,那就排除了00和11两种可能,如果不等则返回,01 或者10概率均为P(1-P)。

4.总结

随机函数Math.random()等概率返回[0,1),对Math.random()进行相关运算可变化的得一些随机数,这些随机数的用处是非常大的。A,B,C题目中就是对这些随机函数的广泛应用。而随机函数最大的用处就是对数器,我们在做大部分题时都需要做校验,而测试的数据为了避免没考虑到边界值,随机数就是最好的校验数据。
给一个简单的对数器的代码看看即可。

  //生成一个随机大小,随机数据的数组
  public static int[] generateArray(int maxlen, int maxValue){
        int len = (int)(Math.random()* maxlen + 1);
        int [] array = new int[len];
        for (int i = 0; i< len; i ++){
            array[i] =(int) (Math.random() * maxValue);
        }
        return array;
   }

这段代码生成一个随机大小(小于maxlen)的随机数据的数组,这个随机数组可用于校验排序后的数组是否是正确的,也可以校验各个排序方法是否一致,亦可成为前缀和数组的输入,或者数组上某段和的输入,用处可见一斑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容