代码改变世界

给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数(均匀概率问题)

2013-10-04 03:38  youxin  阅读(5277)  评论(0编辑  收藏  举报

google面试题:给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数。

问题分析:现在给了一个能随机生成1~5的随机函数,怎样利用这个已知条件生成一个1~7的随机函数呢?既然要生成的是随机数那么生成1,2,3,4,5,6,7的概率就应该是一样的。显然现在光生成1~5之间的数就不够了,我们想到应该要加大生成数的范围,并且加大范围的同时还要保证每个数产生的概率一样,于是有这样一种方法用这个表达式来扩大生成数范围:rand5()*5+rand5(),新的数据范围变成: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.并且可以看出来这个25个数出现的可能性是一样的,于是我们可以只用6~26之间的21个数变成1~7这7个数,于是就是要每3个数对应一个数,即:

6,7,8对应1

9,10,11对应2

…………

24,25,26对应7

这种变化对应的方式是(6 - 3)/ 3 = 1,(7 - 3) / 3 = 1,(8-3) / 3 = 1.

int rand7()
{
    int i;
    //直到产生6~26之间的数跳出循环
    while((i=rand5()*5+rand5()) > 26) ;
    return (i-3)/3;
}

总感觉这种方式不好,虽然1-7概率均匀,但相加不等于1。现在深入分析:

假设我们要等概率生成一个3位的10进制数(000 - 999),我们可以在 随机生成整数0到9的函数 基础上,随机生成3个数字组成三位数就得到了结果。

这里类似,我们首先必须认识到:任何一个数都可以用5进制的数来表示,如12 = 5进制(22) = 2*5 + 2。因此假设我们要随机生成[0,444]范围的数,我们只要随机生成3个5进制的数字组合就可以。

这里的主要问题是:7不是5的幂次方。

但是我们可以将某一个5的幂次方均分成 7 段(分别为0 - 6,等概率的落到每一段),利用5进制随机成一个数,看这个数在哪一个段,就代表我们要生成哪一个数字,这样就保证了等概率的生成0 - 6.

有一个小的问题就是:5的幂次方不能整除7,会遗漏最高的几个数。但是我们这里只要数字足够大,则遗漏的概率相当的小

下面是简单的代码:

const int K = 10;//[0, 5^K - 1]
int Base[K],Rnd[K],Step; //Step表每个区间的长度
int Rand15() //已有的随机生成1 - 5的随机函数
{
    return rand()%5 + 1;
}
void Init()
{
    Base[0] = 1;
    for(int i=1 ;i<K; ++i)
        Base[i] = Base[i-1]*5;
    Step = Base[K-1]*5/7;
    //cout<<"Step = "<<Step<<"\tStep*7 = "<<Step*7<<endl;
    srand(time(0));
}
int Rand17() //我们需要写的随机生成1 - 7的随机函数
{
    int Sum = 0;
    for(int i=0 ;i<K; ++i) //随机生成K个1到5的随机数
    {
        Rnd[i] = Rand15();
        Sum += (Rnd[i]-1)*Base[i];
    }
    return Sum/Step + 1;
}

这里K = 10的时候,保证遗漏了2个数,即落到余数里面的概率是:2/9765625.

利用rand[1,5】得到rand[1,7]可以抽象成利用rand[0,1]得到rand[0,n],看下面的就清楚了。

-----------------------------------------

题目:已知rand7() 可以产生 1~7 的7个数(均匀概率),利用rand7() 产生rand10() 1~10(均匀概率)

记住这道题重点是:均匀概率。

rand7 产生的数概率是一样的,即1~7出现概率一样,由于我们对结果做了一定的筛选只能通过 1~5,而1~5出现的概率也是一样的,又由于范围为1~5 所以 temp1 出现 1~5的概率 为1/5 ,同理 后面的 出现 temp2 的概率为 1/2。

首先temp1出现在1~5的概率为1/5,而temp2出现 1~2 的概率为1/2,也就是说 5*(temp2-1) 出现5或0的概率为1/2,所以假如你要得到1~5的数的话 那么 5*(temp2-1) 必须0,所以因为你要保证 5*(temp2-1)=0,这个概率只有1/2,再加上 你前面指定1~5 的概率 为1/5 ,所以结果为 1/5*1/2=1/10

int rand10() 
02
{ 
03
    int temp1; 
04
    int temp2; 
05
    do 
06
    { 
07
        temp1 = rand7(); 
08
    }while(temp1>5); 
09
    do 
10
    { 
11
        temp2 = rand7(); 
12
    }while(temp2>2); 
13
    return temp1+5*(temp2-1); 
14
} 

已知一个函数f可以得到1-5间的随机数,问怎么得到1-7的随机数

对不对?

这个其实和算法导论上的一个题很像么:已知random等概率返回0或者1,那么试写一个函数等概率返回[a,b]之间的整数。思路就是2进制表示[0, b-a]之间的数,先计算出至少需要多少位,按位生成一个二进制数,一旦大于b-a就重新生成。放到这里的话,表示成5进制就可以了~

推广一下:对于等概率可以生成k个连续整数函数的函数randomk,设计生成[a,b]之间的整数的算法:
令n = b - a;则等概率生成[0,n]上的一个整数即可。于是用k进制表示生成的整数,设m=ceiling(logk(n)),(这个不对)

k^m-1=n; 求解m。m=ceil(logk(n+1)); 向上取整,如4,应该用3个二进制表示. log2(5) 向上取整. (m还可以写成:

m= 1 +log2n)

  1. int randN() {
  2.   while (res > n) {
  3.      for (i = 0; i < m; i++)  
  4.          gen bit i for res with randomk
  5.   }
  6.   return res;
  7. }
  8. 期望的运行时间为t*m* i * (1 - (n+1)/k^m)^(i-1) * ((n+1)/k^m),i从1加到无穷,t表示randomk的运行时间,那么计算这个级数的值为t*m*k^m/(n+1).
    带入到这个题目,期望运行时间为50*t/7,还是很快的。
 利用rand()得到rand[0,n]的代码:
void set(int &a,int i) { a |= (1<<i);}//一定要是引用
int randN(int n)
{
  
    int res=n+1;//让第一次进入while循环,res只要》n即可.
    //c++只有log(默认以e为底)和log10,如果自己设定底数用换底公式
    float floatN=n;
    float tmp=log(floatN+1)/log(2.0);
    int m=ceil(tmp);
    //cout<<"m:"<<m<<endl;
    while(res>n)
    {
        res=0;
        for(int i=0;i<m;i++)
        {
            Sleep(20);
            srand(time(NULL));
            
            int bit=rand()%(2-0)+0;//[0,1]
            if(bit==1)
            {
                set(res,i);
            }
        }
    }
    return res;
}

参考:http://blog.csdn.net/pkueecser/article/details/7425994