Math.random()的概率分布特性进阶

上一篇文章我们验证了Math.random()生成的随机数等概率。

这一篇先上一个问题:

问题一:

已知一个函数f1()固定返回0和1,返回0的概率为80%,返回1的概率为20%。请基于当前函数f1()实现一个新的函数f2(),使得新的函数等概率返回0和1。

先实现函数f1()

public static int f1() {
    return Math.random() < 0.8 ? 0 : 1;
}

写一个main方法验证一波

public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[2];
    for (int i = 0; i < count; i++) {
        numbers[f1()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "的概率为:" + (double) numbers[i] / (double) count);
    }
}
// 测试结果输出如下,符合题意返回0的概率80%,返回1的概率20%
// 数字0的概率为:0.79987
// 数字1的概率为:0.20013

如果我们连续调用两次f1()函数,我们的结果有以下4种:

第一次调用

第二次调用

概率

0

0

80%*80%=64%

0

1

80%*20%=16%

1

0

20%*80%=16%

1

1

20*20%=4%

结果发现,只有当两次调用的结果不一样的时候概率是一样,反之当两次调用的结果一样的时候,我们让f1()重做,也就是重新执行f1()函数。

public static int f2() {
    int result;
    /**
     * 这里调用了两次随机函数f1()
     * 得到的结果只可能有4种:
     * 0 0
     * 0 1
     * 1 0
     * 0 0
     * 因为0的概率为80%,1的概率为20%
     * 所以得到以上4种情况的概率分别为:
     * 0 0 64%
     * 0 1 16%
     * 1 0 16%
     * 1 1 4%
     *
     * 通过以上概率的计算,我们知道只有当0 1 和 1 0的时候返回的概率是一样的,
     * 所以我们在得到0 0 和 1 1 的时候直接让他重做(重新获取两个新的随机数),直到两次随机得到的数字不一样为止
     */
    do {
        result = f1();
    } while (result == f1());
    return result;
}

测试以上函数

public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[2];
    for (int i = 0; i < count; i++) {
        numbers[f2()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "的概率为:" + (double) numbers[i] / (double) count);
    }
}
// 测试结果输出如下,符合题意0和1返回的概率一致。
// 数字0的概率为:0.499489
// 数字1的概率为:0.500511

问题二:

问题进阶:已知一个函数f11()等概率返回返回1-5,基于当前函数f11()实现一个新的函数,使得新的函数等概率返回1-7。

  • 第一步:实现一个函数f11,等概率返回1-5

基于题意,我们只需要写一个等概率返回0-4的函数,把返回的结果+1就能到的一个等概率返回1-5的函数

/**
 * 这是一个只返回1-5的函数
 */
public static int f11() {
    return (int) (Math.random() * 5) + 1;
}

// 验证
public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[6];
    for (int i = 0; i < count; i++) {
        numbers[f11()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "的概率为:" + (double) numbers[i] / (double) count);
    }
}
// 测试结果如下
// 数字0的概率为:0.0
// 数字1的概率为:0.199801
// 数字2的概率为:0.200004
// 数字3的概率为:0.200584
// 数字4的概率为:0.200184
// 数字5的概率为:0.199427

  • 第二步:基于当前函数f11,转换成一个等概率只生成0和1的函数
/**
 * 把f11函数改成一个等概率只返回0和1的函数【01等概率函数】
 * 如果f11函数等概率返回的是奇数个数字,
 *		比如是12345,那么当随机到12的时候返回0,随机到45的时候返回1,随机到3的时候整个函数重新执行
 * 如果f11函数等概率发挥的是偶数个数字,
 *   	比如是123456,那么当随机到123的时候返回0,随机到456的时候返回1.
 */
public static int f12() {
    int number;
    do {
        number = f11();
    } while (number == 3);

    return number < 3 ? 0 : 1;
}

  • 第三步:我们知道7,对应的二进制为 111,那么我们只需要重复调用3次f2函数,然后就可以组装一个最大值为7的二进制数字。得到000~111 做到等概率 即做到了0~7等概率返回。
public static int f13() {
    // 第一次调用f2(),得到0或者1,左移2位可的000或者100
    // 第二次调用f2(),得到0或者1,左移1位可的00或者10
    // 第二次调用f2(),得到0或者1
    // 三次调用的结果可得返回000~111之间的随机数
    return (f12() << 2) + (f12() << 1) + f12();
}

// 验证
public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[8];
    for (int i = 0; i < count; i++) {
        numbers[f13()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "的概率为:" + (double) numbers[i] / (double) count);
    }
}

// 测试结果如下:
// 数字0的概率为:0.124536
// 数字1的概率为:0.12527
// 数字2的概率为:0.125337
// 数字3的概率为:0.125285
// 数字4的概率为:0.124886
// 数字5的概率为:0.125117
// 数字6的概率为:0.124946
// 数字7的概率为:0.124623
  • 最后:依题意我们是要一个等概率生成1-7的函数,以上函数多了个0,我们只需要返回值为0的时候让函数重做(重新调用f13),或者当返回7的时候让函数重做并把最终的结果+1也能得到我们想要的结果。
public static int f14() {
    int number;
    do {
        // number 返回的值是0-7,当返回7的时候,重新执行f13函数,直到返回一个不是7的数字
        // 也就是说f13返回的值是0-6
        number = f13();
    } while (number == 7);
    return number;
}

最终测试:

public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[7];
    for (int i = 0; i < count; i++) {
        numbers[f14()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "的概率为:" + (double) numbers[i] / (double) count);
    }
}
// 测试结果如下:
// 数字1的概率为:0.143519
// 数字2的概率为:0.142237
// 数字3的概率为:0.142667
// 数字4的概率为:0.142529
// 数字5的概率为:0.142925
// 数字6的概率为:0.142656
// 数字7的概率为:0.143467

问题三:

我们有1个函数f111,等概率随机返回一个4-20的随机数,现在我们需要实现一个函数,等概率返回一个30-66的随机函数

  • 第一步:写一个函数f111等概率返回4-20,并验证
public static int f111() {
        return (int) (Math.random() * 17) + 4;
}

// 验证
public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[17];
    for (int i = 0; i < count; i++) {
        numbers[f111() - 4]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + (i + 4) + "次数为:" + numbers[i] + ",概率为:" + (double) numbers[i] / (double) count);
    }
}

// 测试结果如下:
// 数字4次数为:58852,概率为:0.058852
// 数字5次数为:58881,概率为:0.058881
// 数字6次数为:58687,概率为:0.058687
// 数字7次数为:59019,概率为:0.059019
// 数字8次数为:58306,概率为:0.058306
// 数字9次数为:59064,概率为:0.059064
// 数字10次数为:58865,概率为:0.058865
// 数字11次数为:58636,概率为:0.058636
// 数字12次数为:58658,概率为:0.058658
// 数字13次数为:58757,概率为:0.058757
// 数字14次数为:58922,概率为:0.058922
// 数字15次数为:59262,概率为:0.059262
// 数字16次数为:58887,概率为:0.058887
// 数字17次数为:58766,概率为:0.058766
// 数字18次数为:59013,概率为:0.059013
// 数字19次数为:58676,概率为:0.058676
// 数字20次数为:58749,概率为:0.058749
  • 第二步:我们把函数f111改造成一个等概率返回0、1的函数f112,并验证
/**
 * 当f111生成小于12的数字的时候返回0
 * 当f111生成大于12的数字的时候返回1
 * 当f111生成12的时候f111重做(重新调用f111)
 */
public static int f112() {
    int num;
    do {
        num = f111();
    } while (num == 12);
    return num < 12 ? 0 : 1;
}

// 验证
public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[2];
    for (int i = 0; i < count; i++) {
        numbers[f112()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "次数为:" + numbers[i] + ",概率为:" + (double) numbers[i] / (double) count);
    }
}

// 测试结果如下:
// 数字0次数为:500043,概率为:0.500043
// 数字1次数为:499957,概率为:0.499957

  • 我们想要一个30-66的随机函数,先写一个可以随机生成0-36的函数;我们知道36的二进制为:100100,那么我们先写一个可以等概率生成0-111111的函数f113,也就是一个可以等概率生成0-63的函数,并验证
public static int f113() {
    return (f112() << 5) + (f112() << 4) + (f112() << 3)
        + (f112() << 2) + (f112() << 1) + f112();
}

// 验证
public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[64];
    for (int i = 0; i < count; i++) {
        numbers[f113()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "次数为:" + numbers[i] + ",概率为:" + (double) numbers[i] / (double) count);
    }
}

// 测试结果如下:
// 数字0次数为:15566,概率为:0.015566
// 数字1次数为:15659,概率为:0.015659
// 数字2次数为:15535,概率为:0.015535
// 数字3次数为:15613,概率为:0.015613
// 数字4次数为:15613,概率为:0.015613
// 数字5次数为:15638,概率为:0.015638
// 数字6次数为:15544,概率为:0.015544
// 数字7次数为:15612,概率为:0.015612
// 数字8次数为:15534,概率为:0.015534
// 数字9次数为:15588,概率为:0.015588
// 数字10次数为:15758,概率为:0.015758
// 数字11次数为:15525,概率为:0.015525
// 数字12次数为:15632,概率为:0.015632
// 数字13次数为:15577,概率为:0.015577
// 数字14次数为:15666,概率为:0.015666
// 数字15次数为:15414,概率为:0.015414
// 数字16次数为:15691,概率为:0.015691
// 数字17次数为:15559,概率为:0.015559
// 数字18次数为:15636,概率为:0.015636
// 数字19次数为:15579,概率为:0.015579
// 数字20次数为:15697,概率为:0.015697
// 数字21次数为:15566,概率为:0.015566
// 数字22次数为:15685,概率为:0.015685
// 数字23次数为:15761,概率为:0.015761
// 数字24次数为:15620,概率为:0.01562
// 数字25次数为:15619,概率为:0.015619
// 数字26次数为:15861,概率为:0.015861
// 数字27次数为:15718,概率为:0.015718
// 数字28次数为:15582,概率为:0.015582
// 数字29次数为:15563,概率为:0.015563
// 数字30次数为:15753,概率为:0.015753
// 数字31次数为:15863,概率为:0.015863
// 数字32次数为:15475,概率为:0.015475
// 数字33次数为:15847,概率为:0.015847
// 数字34次数为:15665,概率为:0.015665
// 数字35次数为:15334,概率为:0.015334
// 数字36次数为:15592,概率为:0.015592
// 数字37次数为:15534,概率为:0.015534
// 数字38次数为:15730,概率为:0.01573
// 数字39次数为:15493,概率为:0.015493
// 数字40次数为:15751,概率为:0.015751
// 数字41次数为:15746,概率为:0.015746
// 数字42次数为:15724,概率为:0.015724
// 数字43次数为:15615,概率为:0.015615
// 数字44次数为:15609,概率为:0.015609
// 数字45次数为:15586,概率为:0.015586
// 数字46次数为:15761,概率为:0.015761
// 数字47次数为:15695,概率为:0.015695
// 数字48次数为:15806,概率为:0.015806
// 数字49次数为:15753,概率为:0.015753
// 数字50次数为:15956,概率为:0.015956
// 数字51次数为:15408,概率为:0.015408
// 数字52次数为:15479,概率为:0.015479
// 数字53次数为:15430,概率为:0.01543
// 数字54次数为:15491,概率为:0.015491
// 数字55次数为:15525,概率为:0.015525
// 数字56次数为:15554,概率为:0.015554
// 数字57次数为:15411,概率为:0.015411
// 数字58次数为:15563,概率为:0.015563
// 数字59次数为:15473,概率为:0.015473
// 数字60次数为:15469,概率为:0.015469
// 数字61次数为:15823,概率为:0.015823
// 数字62次数为:15743,概率为:0.015743
// 数字63次数为:15732,概率为:0.015732
  • 第四步:如果获取到大于36的值,让函数重做,并验证验证函数f114等概率生成0-36的数,并验证
public static int f114() {
    int num;
    do {
        num = f113();
    } while (num > 36);
    return num;
}

// 验证
public static void main(String[] args) {
    int count = 100_0000;
    int[] numbers = new int[37];
    for (int i = 0; i < count; i++) {
        numbers[f114()]++;
    }
    for (int i = 0; i < numbers.length; i++) {
        System.out.println("数字" + i + "次数为:" + numbers[i] + ",概率为:" + (double) numbers[i] / (double) count);
    }
}

// 测试结果如下:
// 数字0次数为:27284,概率为:0.027284
// 数字1次数为:26808,概率为:0.026808
// 数字2次数为:27109,概率为:0.027109
// 数字3次数为:27260,概率为:0.02726
// 数字4次数为:26893,概率为:0.026893
// 数字5次数为:27009,概率为:0.027009
// 数字6次数为:26846,概率为:0.026846
// 数字7次数为:26914,概率为:0.026914
// 数字8次数为:27329,概率为:0.027329
// 数字9次数为:26957,概率为:0.026957
// 数字10次数为:27158,概率为:0.027158
// 数字11次数为:27160,概率为:0.02716
// 数字12次数为:26874,概率为:0.026874
// 数字13次数为:26892,概率为:0.026892
// 数字14次数为:27228,概率为:0.027228
// 数字15次数为:26996,概率为:0.026996
// 数字16次数为:27019,概率为:0.027019
// 数字17次数为:26878,概率为:0.026878
// 数字18次数为:27013,概率为:0.027013
// 数字19次数为:26889,概率为:0.026889
// 数字20次数为:26769,概率为:0.026769
// 数字21次数为:27077,概率为:0.027077
// 数字22次数为:27318,概率为:0.027318
// 数字23次数为:27012,概率为:0.027012
// 数字24次数为:27077,概率为:0.027077
// 数字25次数为:26986,概率为:0.026986
// 数字26次数为:27040,概率为:0.02704
// 数字27次数为:27018,概率为:0.027018
// 数字28次数为:27254,概率为:0.027254
// 数字29次数为:27062,概率为:0.027062
// 数字30次数为:27207,概率为:0.027207
// 数字31次数为:27107,概率为:0.027107
// 数字32次数为:26875,概率为:0.026875
// 数字33次数为:26882,概率为:0.026882
// 数字34次数为:26911,概率为:0.026911
// 数字35次数为:26896,概率为:0.026896
// 数字36次数为:26993,概率为:0.026993

  • 最后:把f114返回的结果+30,就是我们要得到的函数。

以上内容感谢左神。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值