C语言中可以使用rand()函数来生成一个从0到RAND_MAX的uniform分布。而rand()函数一般是用线性同余法来实现伪随机。
线性同余法
线性同余方法(LCG)是个产生伪随机数的方法。
它是根据递归公式:
其中是产生器设定的常数。
LCG的周期最大为,但大部分情况都会少于M。要令LCG达到最大周期,应符合以下条件:
1. 互质;
2. 的所有质因数都能整除;
3. 若是4的倍数,也是;
4. 都比小;
5. 是正整数。
C语言中rand()就是利用这个公式,由于这个公式每次产生的数值是有规律的,因此需要一个不同的随机值。鉴于此,srand()根据当前时间可以给定一个起始值,也就是种子,也就是N0。
我的重点不在于探讨rand()产生的历史和方法,而是使用这个rand来看看一些随机函数怎么实现。
0到1的uniform分布
//generate a random number in the range of[0,1] double uniform_zero_to_one(){ return (double)rand()/RAND_MAX; }
任意实数区间的uniform分布
//generate a random real number in [start,end] double uniform_real(double start,double end){ double rate=(double)rand()/RAND_MAX; return start+(end-start)*rate; }
任意整数区间的uniform分布
//generate a random integer number in [start,end) int uniform_integer(int start,int end){ int base=rand(); if(base==RAND_MAX) return uniform_integer(start,end); int range=end-start; int remainder=RAND_MAX%range; int bucket=RAND_MAX/range; if(base<RAND_MAX-remainder) return start+base/bucket; else return uniform_integer(start,end); }
32bits的随机数
//generate a random 32 bits integer number int rand32(){ return ((rand()<<16)+(rand()<<1)+rand()%2); }
有了32bits的随机数生成方法,就可以构造32bits范围内的随机整数区间了,方法和之前16bits的情况一样。
32bits范围内的随机整数区间
//generate a random 32bits integer number in [start,end) int uniform_integer_32(int start,int end){ int base=rand32(); if(base==RAND32_MAX) return uniform_integer_32(start,end); int range=end-start; int remainder=RAND32_MAX%range; int bucket=RAND32_MAX/range; if(base<RAND32_MAX-remainder) return start+base/bucket; else return uniform_integer_32(start,end); }
面试常见问题
此外,在实际的情况中,常常出现一些变形问题。
1、 已知随机数函数rand5(),可以均匀随机生成1~5,编写随机函数rand7(),可以随机生成1~7,并且保持均匀性。
解答:
利用rand5()等概率随机生成1~25,然后去掉22~25,最后将结果%7+1,就等到等概率分布的1~7。
</pre><pre>
这很需要注意的是对于randN(1,N),rst = (randN - 1)*N + randN。这可以用数学归纳法轻松证明。对于randN(0,N),rst = randN*(N+1) + randN+1。
int rand5(); int rand7(){ int rst = 0; do{ rst = (rand5()-1)*5 + rand5(); }while(rst > 21); return rst %7 + 1; }
2、 已知随机数函数rand1(),p的概率等于0,(1-p)的概率等于1,编写随机函数rand01(),可以等概率生成0和1。
解答:
这有个巧妙方法。具体见代码吧。
Int rand01() { Int r1 = rand1(); Int r2 = rand1(); If(r1 && !r2) return 0; If(!r1 && r2) return 1; Else return rand01(); }
哈哈,上面0和1就这样等概率产生。利用产生的01等概率随机函数,可以实现更多的随机函数。
3、 洗牌算法。有一副牌假设有N张,请设计一个随机洗牌算法。
解答:这个题很经典也很简单。但他的思想代表了很多随机函数的应用。所谓随机,即在每一个位置,任何一张牌出现概率一样。由于有N张牌,显然在每个位置每张出现的概率都为1/N。对于第1个位置,随机取一张m,概率为1/N。m可能是任意一张牌,因此对第一个位置满足了。对于第二个位置,由于已经有一张牌出局,只剩下N-1张牌,随机选一张,概率为1/N-1。似乎不大对??其实是由于忽视了取第一张牌时的概率。对于第二个位置,可选择的牌第此都没取到,概率为(N-1)/N.因此对于第二个位置,概率为(N-1)/N
* 1/(N-1) = 1/N。得证。
代码如下:
void suffle(int ar[], int n) { while(n>1){ swap(ar[n-1],ar[rand()%n]); n--; } }
4、 快速生成10亿个不重复的18位随机数的算法(从n个数中生成m个不重复的随机数)
解答:这个题很和洗牌算法异曲同工。相当于随机洗牌后,取出前m个牌。代码就不给了。
补充
C++ 11中终于有了随机函数。见链接:http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution