随机数的扩展

几天前偶然在网上看到这样一道面试题:

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 ,对于任何正整数n,若可以得到(1,n)均匀随机数生成方法,则说 n 在集合A中。

将面试题推广一下,就是给定一个1~ m 的均匀随机数生成器,是否能构造一个1~n的均匀随机数生成器?也就是说, mA 是否可以推出 nA

关于集合 A ,很容易得到的性质有:

  • 1. 由mA nA 可以推出 mnA ,特别的,对于素数 p pA推出 pnA n 是任一正整数)。
  • 2. 由d|n nA 可以推出 dA

    • 3. 由上面两条性质,对正整数 n 作素因子分解n=ipαii,则 nA 等价于对任意 i piA
    • 第3条性质非常重要,它引导我们先从素数入手,即假设给定 p q为素数,且 pq 。直觉说来 pA 无法导出 qA ,是否可以证明呢?为此我们需要探究 pA 时, A 中必须有哪些元素。一个非常直接的断言是下面的性质:

      • 4. 若素数pA,则 A={pn|nN} 是符合题意的最小集合。

        性质4的证明:设 qA ,不妨取 n 次1~p均匀随机数(因为有限步之内完成,所以可以限定一个 n ),若能得到1~q的均匀随机数,有多元函数 f 使得f(x1,x2,,xn)的值域为1~ q 之间的整数,其中xi由1~ p 随机生成器生成,即取值范围是1~p。因为定义域实际上为 pn n 维空间的点,所以f是这 pn 个点到1~ q 个数的单射。由于1~q个数每个数的原像无交集,概率相等(因为每个点的取值概率相等,所以概率大小取决于点的个数),所以定义域必然是 q 的倍数,这就矛盾了。

        这个证明还不能说十分完善,因为A中还有不同于 p 的数(比如p2),但是利用这个证明的思想可以得到更强的性质:

        • 4(推论). 若素数 p1,p2,,pnA ,则 A={ipαii|αiN} 是符合题意的最小集合。
          甚至还能推广到无穷个素数的情况:

        • 4(推论2). 若素数 {pn}A pn 两两不同),则 A={iqi|qi{pn},iN} 是符合题意的最小集合。

        现在原题已经很清楚了。如果仅有1~ m 均匀随机数生成器,若n的素因子都是 m 的素因子,那么可以利用素因子分解和上面的提到的性质构造1~n的均匀随机数生成器,否则就没法构造。

        问题并不是到此为止了,对于已经证明无法构造的随机数生成器,在实际情况下我们可能仍然需要考虑写一个功能接近的投入使用,因为总是有误差可以被允许的。

        我们再次从素数入手(已经分析过了,解决了素数就解决了一切)把问题描述为:

        给定素数 pq ,现在有外部函数int rand_p(),它可以等概率返回一个1~ p 的整数(但你无法查看函数细节),请你写一个函数int rand_q(),它可以等概率返回一个1~q的整数。

        首先由费马小定理可知 pq11(modq) ,进一步得到 pn(q1)1(modq) 。由于我们有1~ pn(q1) 的均匀随机数生成方法(为了简洁下面记 N=pn(q1) ),利用函数 y=x%q+1 得到1~ q 的随机数(x是1~ N 的随机数)。除了取到1的概率为1/q+(11/q)/N外,其余值被取到的概率为 1/q1/(qN) ,当n取得充分大的时候可以让误差充分小。这种方法就是我们平时用<stdlib.h>中的rand()函数来取整数 a ~b的随机数的方法。代码如下:

        #include<stdio.h>
        #include<stdlib.h>
        #include<time.h>
        
        #define p (2)
        #define q (5)
        
        extern int rand_p();
        /* return rand()%p+1; */
        
        //求1~p^n随机数,要求n是正整数
        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;
            //这里可以对p^(q-1)的值进行评估
            //若太小,采用p^(k(q-1)),k为大小合适的正整数
            //若太大,适当减小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]);
        }

        此外我还想到另一种方法,看起来更完美一些,牺牲一点点时间可以换取完全的等概率。前面提到 N1(modq) (还是记 N=pn(q1) ),现在取1~ N 随机数,取到N时丢弃此次结果,重新选取。虽然不能预先确定几步之内可以完成选取,但是有限步之内完成的几率为1。即:

        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;
            //若q太大,同样可以适当减小q-1的值
            //但p^k最好还是能与较小的数同余(mod q)
        }

        至此已经可以完全地回答前面的面试提问——

        5和7是不相等的素数,仅依靠1~5随机数生成器无法在确定的有限步内得到1~7的随机数生成器。由于 571=15625 ,且我们可以构造1~15625之间的随机数,可以用如下方法来构造1~7之间的随机数:

        取1~15625的随机数x,若x为15625,重复上述步骤,否则y=x%7+1就是要求的1~7的随机数。(15625-1=2232*7)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值