问题介绍与扩展
几天前偶然在网上看到这样一道面试题:
Given a random number generator which can generate the number in range (1,5) uniformly. How can you use it to build a random number generator which can generate the number in range (1,7) uniformly?
当时第一反应是无法承诺在有限步之内做到,只能逼近。今天突然想到这个问题,深入探究后发现收获不少。为了叙述方便,我们构造集合A A A ,对于任何正整数n,若可以得到( 1 , n ) (1,n) ( 1 , n ) 均匀随机数生成方法,则说n n n 在集合A A A 中。
将面试题推广一下,就是给定一个1 ∼ m 1\sim m 1 ∼ m 的均匀随机数生成器,是否能构造一个1 ∼ n 1\sim n 1 ∼ n 的均匀随机数生成器?也就是说,m ∈ A m\in A m ∈ A 是否可以推出n ∈ A n\in A n ∈ A ?
性质与推论
关于集合A A A ,很容易得到的性质有:
1. 由m ∈ A m\in A m ∈ A 和n ∈ A n\in A n ∈ A 可以推出m n ∈ A mn\in A m n ∈ A ,特别的,对于素数p p p ,p ∈ A p\in A p ∈ A 推出p n ∈ A p^n\in A p n ∈ A (n n n 是任一正整数)。
2. 由d ∣ n d\mid n d ∣ n 和n ∈ A n\in A n ∈ A 可以推出d ∈ A d\in A d ∈ A 。
3. 由上面两条性质,对正整数n n n 作素因子分解n = ∏ i p i α i n=\prod_i p_i^{\alpha_i} n = ∏ i p i α i ,则n ∈ A n\in A n ∈ A 等价于对任意i i i ,p i ∈ A p_i\in A p i ∈ A 。
第3条性质非常重要,它引导我们先从素数入手,即假设给定p p p 和q q q 为素数,且p ≠ q p≠q p = q 。直觉说来p ∈ A p\in A p ∈ A 无法导出q ∈ A q\in A q ∈ A ,是否可以证明呢?为此我们需要探究p ∈ A p\in A p ∈ A 时,A A A 中必须有哪些元素。一个非常直接的断言是下面的性质:
4. 若素数p ∈ A p\in A p ∈ A ,则A = { p n ∣ n ∈ N } A=\{p^n\mid n\in N\} A = { p n ∣ n ∈ N } 是符合题意的最小集合。
性质4的证明:设q ∉ A q∉A q ∈ / A ,不妨取n n n 次1 ∼ p 1\sim p 1 ∼ p 均匀随机数(因为有限步之内完成,所以可以限定一个n n n ),若能得到1 ∼ q 1\sim q 1 ∼ q 的均匀随机数,有多元函数f f f 使得f ( x 1 , x 2 , … , x n ) f(x_1,x_2,…,x_n) f ( x 1 , x 2 , … , x n ) 的值域为1 ∼ q 1\sim q 1 ∼ q 之间的整数,其中x i x_i x i 由1 ∼ p 1\sim p 1 ∼ p 随机生成器生成,即取值范围是1 ∼ p 1\sim p 1 ∼ p 。因为定义域实际上为p n p^n p n 个n n n 维空间的点,所以f f f 是这p n p^n p n 个点到1 ∼ q 1\sim q 1 ∼ q 个数的单射。由于1 ∼ q 1\sim q 1 ∼ q 个数每个数的原像无交集,概率相等(因为每个点的取值概率相等,所以概率大小取决于点的个数),所以定义域必然是q q q 的倍数,这就矛盾了。
这个证明还不能说十分完善,因为A A A 中还有不同于p p p 的数(比如p 2 p^2 p 2 ),但是利用这个证明的思想可以得到更强的性质:
4(推论). 若素数p 1 , p 2 , … , p n ∈ A p_1,p_2,…,p_n\in A p 1 , p 2 , … , p n ∈ A ,则A = { ∏ i p i α i ∣ α i ∈ N } A=\{\prod_i p_i^{\alpha_i}\mid α_i\in N\} A = { ∏ i p i α i ∣ α i ∈ N } 是符合题意的最小集合。
甚至还能推广到无穷个素数的情况:
4(推论2). 若素数{ p n } ∈ A \{p_n\}\in A { p n } ∈ A (p n p_n p n 两两不同),则A = { ∏ i q i ∣ q i ∈ { p n } , i ∈ N } A=\{\prod_i q_i\mid q_i\in \{p_n\}, i\in N\} A = { ∏ i q i ∣ q i ∈ { p n } , i ∈ N } 是符合题意的最小集合。
问题解决
现在原题已经很清楚了。如果仅有1 ∼ m 1\sim m 1 ∼ m 均匀随机数生成器,若n n n 的素因子都是m m m 的素因子,那么可以利用素因子分解和上面的提到的性质构造1 ∼ n 1\sim n 1 ∼ n 的均匀随机数生成器,否则就没法构造。
问题并不是到此为止了,对于已经证明无法构造的随机数生成器,在实际情况下我们可能仍然需要考虑写一个功能接近的投入使用,因为总是有误差可以被允许的。
我们再次从素数入手(已经分析过了,解决了素数就解决了一切)把问题描述为:
给定素数p ≠ q p≠q p = q ,现在有外部函数int rand_p()
,它可以等概率返回一个1 ∼ p 1\sim p 1 ∼ p 的整数(但你无法查看函数细节),请你写一个函数int rand_q()
,它可以等概率返回一个1 ∼ q 1\sim q 1 ∼ q 的整数。
首先由费马小定理可知p q − 1 ≡ 1 ( m o d q ) p^{q-1}≡1\pmod q p q − 1 ≡ 1 ( m o d q ) ,进一步得到p n ( q − 1 ) ≡ 1 ( m o d q ) p^{n(q-1)}≡1\pmod q p n ( q − 1 ) ≡ 1 ( m o d q ) 。由于我们有1 ∼ p n ( q − 1 ) 1\sim p^{n(q-1)} 1 ∼ p n ( q − 1 ) 的均匀随机数生成方法(为了简洁下面记N = p n ( q − 1 ) N= p^{n(q-1)} N = p n ( q − 1 ) ),利用函数y = x % q + 1 y=x\%q+1 y = x % q + 1 得到1 ∼ q 1\sim q 1 ∼ q 的随机数(x x x 是1 ∼ N 1\sim N 1 ∼ N 的随机数)。除了取到1的概率为1 / q + ( 1 − 1 / q ) / N 1/q+(1-1/q)/N 1 / q + ( 1 − 1 / q ) / N 外,其余值被取到的概率为1 / q − 1 / ( q N ) 1/q-1/(qN) 1 / q − 1 / ( q N ) ,当n取得充分大的时候可以让误差充分小。这种方法就是我们平时用<stdlib.h>
中的rand()
函数来取整数a ∼ b a\sim b a ∼ b 的随机数的方法。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> #include <stdlib.h> #include <time.h> #define p (2) #define q (5) extern int rand_p () ;int rand_p_n (int n) { int sum=rand_p()-1 ; for (int i=0 ;i<n-1 ; i++) { sum*=p; sum+=rand_p()-1 ; } return sum+1 ; } int rand_q () { return rand_p_n(3 *(q-1 ))%q+1 ; } int main () { int n=100000 ; srand((unsigned )time(NULL )); int count[q]={0 }; while (n--) count[rand_q()-1 ]++; for (int i=0 ;i<q;i++) printf ("%d " ,count[i]); }
此外我还想到另一种方法,看起来更完美一些,牺牲一点点时间可以换取完全的等概率。前面提到N ≡ 1 ( m o d q ) N≡1\pmod q N ≡ 1 ( m o d q ) (还是记N = p n ( q − 1 ) N=p^{n(q-1)} N = p n ( q − 1 ) ),现在取1 ∼ N 1\sim N 1 ∼ N 随机数,取到N N N 时丢弃此次结果,重新选取。虽然不能预先确定几步之内可以完成选取,但是有限步之内完成的几率为1。即:
1 2 3 4 5 6 7 8 9 int rand_q () { int k=rand_p_n(3 *(q-1 )); while (k==0 ) k=rand_p_n(3 *(q-1 )); return k%q+1 ; }
至此已经可以完全地回答前面的面试提问——
5和7是不相等的素数,仅依靠1~5随机数生成器无法在确定的有限步内得到1~7的随机数生成器。由于5^(7-1)=15625,且我们可以构造1~15625之间的随机数,可以用如下方法来构造1~7之间的随机数:
取1~15625的随机数x,若x为15625,重复上述步骤,否则y=x%7+1就是要求的1~7的随机数。(15625-1=2232*7)
但其实不需要构造1~15625之间的随机数,而是应该构造1~25之间的随机数更快,见下文。
复杂度补充
假设从1 ∼ m 1\sim m 1 ∼ m 的均匀随机数生成器中取出一个数字的用时为1,相比之下一般的加减乘除和取余数运算可以忽略用时。
我们想要获得1 ∼ k 1\sim k 1 ∼ k 的均匀随机数(k < m k<m k < m )的话(取到大于k的数则重取),期望用时为
1 + m − k m + ( m − k m ) 2 + … = 1 1 − ( m − k ) / m = m k 1+\frac{m-k}{m}+(\frac{m-k}{m})^2+\ldots\,=\frac{1}{1-(m-k)/m} = \frac{m}{k}
1 + m m − k + ( m m − k ) 2 + … = 1 − ( m − k ) / m 1 = k m
通过调整可以使k ≥ [ ( m + 1 ) / 2 ] k\geq [(m+1)/2] k ≥ [ ( m + 1 ) / 2 ] ,从而上式至多为2。
假设利用1 ∼ m 1\sim m 1 ∼ m 的均匀随机数生成器构造1 ∼ m a 1\sim m^a 1 ∼ m a 的均匀随机数生成器,则取一次随机数的用时变为a a a ,假设我们想要获得1 ∼ k ′ 1\sim k' 1 ∼ k ′ 的均匀随机数(k ′ < m a k'<m^a k ′ < m a ),期望用时为a m a / k ′ > a am^a/k'>a a m a / k ′ > a 。
若a ≥ 2 a\geq 2 a ≥ 2 ,则此式必然超过前式,换言之,平方扩展不如不扩展。
但不是说扩展的越少(a a a 越小)越好。特殊情形举例:
给定1~61的均匀随机数生成器,要构造1~1861的均匀随机数生成器。假设取1~61的随机数用时为1,则取1~61^2的随机数用时为2,在其中构造取1~1861的随机数用时约为4,但若取1~61^3的随机数用时为3,在其中构造取1~1861的随机数用时约为3。在这个例子中可以认为a = 1.5 a=1.5 a = 1 . 5 。