经典面试问题:用 randX() 实现 randY()
在牛牛空间上看面经的时候经常会看到这样一个问题,就是现在有 randX()
可以等概率给出 范围内的随机数,现在要根据这个来实现 randY()
1. 当 时
其实很容易想到,当用大随机数生成小随机数时可以直接舍弃掉超出范围内的结果,只保留 范围内的结果,显然生成结果是等概率的,为了使算法更快,我们可以只舍弃掉最后的 个数
int randY(int Y) { int res; do { res = randX(); } while(res > X - X % Y); return (res % Y) + 1; }
期望次数为()
2. 当 时
现在忘掉原问题,改为: 现在有一个 randX()
和 randY()
如何等概率地生成 内的数呢?其实很简单,可以把它想象为一个有 和 列的矩阵,如何将这个二维矩阵映射到一维的 范围内,对于位置 (从1开始) ,只需要构造映射函数为 即可。自然地: 可以使用 (randX() - 1) * Y + randY()
来等概率地生成 内的数了。
接下来,考虑 3 维矩阵映射到 1 维,即现在有 randX()
、randY()
和 randZ()
要映射到 ,可以通过 (randX - 1) * (Y * Z) + (randY - 1) * Z + randZ()
来实现
得出一般性的结论,一个 维矩阵,矩阵维度分别为 ,而 表示元素的 维坐标,则一维映射公式可以表示为(令 )
映射函数只需要将公式里的 分别替换为对应的 Rand()
函数即可。
现在回到问题,怎么通过 randX()
来实现 randY()
呢?
很简单,我们只需要进行多次采样,假设每次采样的结果为 ,然后通过 的组合
通过之前构造的映射函数,我们实现了 的 rand()
方法,获取 randY()
就只需要像情况1一样把多余的部分舍弃掉就好了。至于怎么通过 randX()
来获取 randd_i
呢?还是和情况1一样。
理论上来说,我们可以随意的按照想要的方式对采样方式进行组合,只要满足 就行了,但是不同的组合方式在性能上却不同,我们总是想要找出一种最优的组合方式。
其实现在的问题就转化成为了,需要找一个最大质因子不超过 的数 ,然后对 进行质因数分解,如此就能知道需要进行多少组采样,而在具体操作中,可以将一些较小的 进行合并,以减少采样次数,比如 , , ,就不需要进行两次采样,而是进行 一次采样。
例: 用 rand7()
构造 rand100()
100 分解质因数后为 ,其中 可以合并为 ,所以可以按照 进行 3 次采样,映射函数如上可以写为
代码如下
// Big to Small, randX->randY int randB2S(int Y) { int res; do { res = randX(); } while(res > X - X % Y); return (res % Y) + 1; } int rand100() { int first = randB2S(4); int second = randB2S(5); int third = randB2S(5); return (first - 1) * 25 + (second - 1) * 5 + third; }
最后: 关于上面的最少采样次数是很难通过一个统一的方法去找到它对应的解,只能根据具体的情况去找,所以是无法设计一个统一的 rand(int Y)
算法并且满足对 最优的。