上一篇文章我们验证了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,就是我们要得到的函数。
以上内容感谢左神。