[算法 笔记]用小范围随机函数编写大范围随机函数
最近笔试出现的一道概率问题:给定一个随机函数rand_m()能按1/m的概率生成数字z(0 ≤ z < m),基于rand_m()随机函数编写一个新的随机函数rand_n()按1/n的概率生成数字o(0 ≤ o < n)。
在分析前,首先说明有关事件独立的概念:如果两个事件A和B满足Pr{A∩B} = Pr{A}Pr{B},则称两个时间独立。例如,假设抛两个均匀硬币的结果是独立的。那么两个都是正面的概率就是(1/2)(1/2)=(1/4)。
简化问题:现给定一个随机函数rand3()能按1/3的概率生成数字{0, 1, 2},根据给定的函数写出随机函数rand7(),要求按照1/7的概率生成{0, 1, 2, 3, 4, 5, 6}。
那么,如何来实现这个函数呢?
(答案来自:http://blog.csdn.net/column/details/ms100.html)
1 int rand_c_opt( int limits ) 2 { 3 int tmp0 = 0; 4 int tmp1 = 0; 5 6 do { 7 tmp0 = rand3() * 3; 8 tmp1 = rand3(); 9 tmp1 += tmp0; 10 } while( tmp1 > limits ); 11 12 return tmp1; 13 }
我以前的时候百思不得其解,为什么这么写?而不是这样写:
1 int rand_c( int limits ) 2 { 3 int tmp0 = 0; 4 int tmp1 = 0; 5 int tmp2 = 0; 6 7 do { 8 tmp0 = rand3(); 9 tmp1 = rand3(); 10 tmp2 = rand3(); 11 12 tmp0 += ( tmp1 + tmp2 ); 13 } while( tmp0 > limits ); 14 15 return tmp0; 16 }
因为tmp0,tmp1,tmp2的取值范围均在{0, 1, 2}之间,那么三者之和就在[0, 6]范围值内,但是运行结果给我当头一棒。(运行次数为10000000次)
从图中可以看出,这个运行结果不均匀。那么为什么计算结果是这样呢?让我们从概率学的角度来看一下(以前学习的概率知识都让我还给老师了,让我偷偷的在网上找一找):
表1 rand3()生成数值的概率
数值 |
0 |
1 |
2 |
概率 |
1/3 |
1/3 |
1/3 |
那么在tmp1+tmp2的计算结果的概率为:
表2 中间计算结果的概率
|
0(1/3) |
1(1/3) |
2(1/3) |
0(1/3) |
0(1/9) |
1(1/9) |
2(1/9) |
1(1/3) |
1(1/9) |
2(1/9) |
3(1/9) |
2(1/3) |
2(1/9) |
3(1/9) |
4(1/9) |
则从表2可以计算出0~4的概率是:
表3 中间计算结果概率总结
数值 |
0 |
1 |
2 |
3 |
4 |
概率 |
1/9 |
2/9 |
1/3 |
2/9 |
1/9 |
从表3中就发现,在计算的中间过程中,概率就开始不均匀。那么在最后的计算结果tmp0+=(tmp1 + tmp2)的概率是:
表4 最后结果的概率
|
0(1/9) |
1(2/9) |
2(1/3) |
3(2/9) |
4(1/9) |
0(1/3) |
0(1/27) |
1(2/27) |
2(1/9) |
3(2/27) |
4(1/27) |
1(1/3) |
1(1/27) |
2(2/27) |
3(1/9) |
4(2/27) |
5(1/27) |
2(1/3) |
2(1/27) |
3(2/27) |
4(1/9) |
5(2/27) |
6(1/27) |
从表4中计算出0~6的概率是:
表5 最后结果的概率总结
数值 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
概率 |
1/27 |
1/9 |
2/9 |
7/27 |
2/9 |
1/9 |
1/27 |
通过表5,大家可以发现这样的方式计算出的结果是不均匀的(忘记概率知识的结果真是可怕)。
函数rand_c_opt()的生成数值过程中,使用3来将第一次随机数值进行了提升,在这样的情况下不改变概率,只是改变生成的结果为{0, 3, 6}。这里有的同学就会问了,那么我不是乘以3而是乘以其他数值也可以。但是,朋友们请注意算式:rand3()*3+rand3()。下面在计算概率的过程中,大家就会发现这个算式的高明之处。至于为什么不能乘以其他数值,在看过之后,大家也可能会想到。
表6 算式rand3()*3+rand3()的概率
|
0(1/3) |
3(1/3) |
6(1/3) |
0(1/3) |
0(1/9) |
3(1/9) |
6(1/9) |
1(1/3) |
1(1/9) |
4(1/9) |
7(1/9) |
2(1/3) |
2(1/9) |
5(1/9) |
8(1/9) |
通过表6,大家会发现算式rand3()*3+rand3()能够有效以1/9的概率生成0~8之间的数值。程序的运算结果也能说明:
所有源码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #include <string.h> 5 6 int rand3() 7 { 8 return rand() % 3; 9 } 10 11 int rand_c_opt( int limits ) 12 { 13 int tmp0 = 0; 14 int tmp1 = 0; 15 16 do { 17 tmp0 = rand3() * 3; 18 tmp1 = rand3(); 19 tmp1 += tmp0; 20 } while( tmp1 > limits ); 21 22 return tmp1; 23 } 24 25 int rand_c( int limits ) 26 { 27 int tmp0 = 0; 28 int tmp1 = 0; 29 int tmp2 = 0; 30 31 do { 32 tmp0 = rand3(); 33 tmp1 = rand3(); 34 tmp2 = rand3(); 35 36 tmp0 += ( tmp1 + tmp2 ); 37 } while( tmp0 > limits ); 38 39 return tmp0; 40 } 41 42 int main() 43 { 44 enum{ limits = 9 }; 45 // enum{ limits = 7 }; 46 int cnt[limits]; 47 int counter = 10000000; 48 srand( (unsigned) time(0) ); 49 50 memset( cnt, 0, sizeof(cnt) ); 51 52 while ( counter-- > 0 ) 53 { 54 int tmp = rand_c_opt( limits - 1 ); 55 // int tmp = rand_c( limits - 1 ); 56 cnt[tmp]++; 57 } 58 59 for ( counter = 0; counter < limits; ++counter ) 60 { 61 printf( " %d : %d\n", counter, cnt[counter] ); 62 } 63 64 return 0; 65 }
欢迎大家共同来讨论!欢迎指导!!