前言
这是面试的时候遇到的一个问题,问题的关键就是如何利用 r a n d 5 rand5 rand5 生成均匀分布的 r a n d 7 rand7 rand7,另外补充到, r a n d rand rand 只能产生整数。
思考
首先我们来看看另外一个问题,如何利用 r a n d [ 0 , 1 ] rand[0,1] rand[0,1] 生成 r a n d [ 0 , 2 k ) rand[0,2^k) rand[0,2k)?这个可以通过组成2进制数的方式完成这个任务,即将 r a n d [ 0 , 1 ] rand[0,1] rand[0,1] 执行 k k k 次。其中由于产生的是整数,因此在这里 r a n d [ 0 , 2 k ) rand[0,2^k) rand[0,2k) 亦可表示为 r a n d [ 0 , 2 k − 1 ] rand[0,2^k-1] rand[0,2k−1]
再进一步,我们现在有了 r a n d [ 0 , 2 k − 1 ] rand[0,2^k-1] rand[0,2k−1] 如何产生 r a n d [ a , b ] rand[a,b] rand[a,b]?我们来思考两种情况:
- 如果 2 k − 1 = b − a 2^k -1 = b-a 2k−1=b−a,只需要将数字整体偏移就可以了。例如 r a n d [ 3 , 6 ] = r a n d [ 0 , 2 2 − 1 ] + 3 rand[3,6] = rand[0,2^2-1] + 3 rand[3,6]=rand[0,22−1]+3
- 如果 2 k − 1 ≠ b − a 2^k -1 \neq b-a 2k−1=b−a,那先假设 b − a b-a b−a 这个区间更小,那么我只需要截取 2 k − 1 2^k-1 2k−1 的某个与之相同长度的区间偏移映射到 b − a b-a b−a 上即可,当超出这个范围时,只需要重新执行 r a n d rand rand 即可;如果 b − a b-a b−a 区间更大,按照同样的思路,我们可以通过扩大区间来回到前一种情况,事实上只要维持 2 k − 1 > = b − a 2^k -1 >= b-a 2k−1>=b−a 即可,所以 b − a b-a b−a 区间更大的情况是可以排除的。
但是如果硬要通过小区间映射到更大的 b − a b-a b−a 区间,我们可以截取一个区间,这个区间可以整除 b − a b-a b−a 然后将区间分成 N 段,通过多级映射也可以完成,有点类似操作系统里面的多级索引。
int rand(int a, int b) { //generate rand[a,b]
int tmp = 1, k = 0;
int len = b - a;
while (tmp - 1 < len) { //计算最小的正整数k,使2^k - 1 >= len
tmp *= 2;
k++;
}
tmp = 0;
for (int i = 0; i < k; i++) { //产生 rand[0, 2^k-1]
tmp <<= 1;
tmp += rand01();
}
if (tmp > len) { //超出范围,截断
return rand(a,b);
}
else {
return tmp + a;
}
}
实现
方法一
扩大区间,限制整数区间,映射区间
int rand7() {
int i;
do {
i = 5 * (rand5() - 1) + rand5(); //产生[1,25]的整数区间
} while (i > 21); //将[1,25]整数区间控制于[1,21]
return i % 7 + 1; //将[1,21]映射到[1,7]
}
参考
[1] 【算法题】rand5()产生rand7() https://www.cnblogs.com/dwdxdy/archive/2012/07/28/2613135.html