随机数问题——基础知识必备

来源:互联网 发布:外文五大数据库 编辑:程序博客网 时间:2024/04/30 05:21

C++标准模板库规范每次插入操作都在O(log m)时间内完成,而遍历集合则需要O(m)时间,所以每次查找并插入一个元素的时间是O(mlog m).
一些随机数的库函数:(以下来自百度百科)
C库函数 rand():rand()函数是产生随机数的一个随机函数。C语言里还有srand()函数等。
  (1)使用该函数首先应在开头包含头文件stdlib.h

  #include<stdlib.h>

  (2)在标准的C库中函数rand()可以生成0~RAND_MAX之间的一个随机数,其中RAND_MAX是stdlib.h 中定义的一个整数,它与系统有关。

  (3)rand()函数没有输入参数,直接通过表达式rand()来引用;例如可以用下面的语句来打印两个随机数:

  printf("Random numbers are: %i%i\n",rand(),rand());

  (4)因为rand()函数是按指定的顺序来产生整数,因此每次执行上面的语句都打印相同的两个值,所以说C语言的随机并不是真正意义上的随机,有时候也叫伪随机数。

  (5)为了使程序在每次执行时都能生成一个新序列的随机值,我们通常通过为随机数生成器提供一粒新的随机种子。函数 srand()(来自stdlib.h)可以为随机数生成器播散种子。只要种子不同rand()函数就会产生不同的随机数序列。srand()称为随机数生成器的初始化器。

rand()产生伪随机数,srand函数提供种子,种子不同产生的随机数序列也不同,所以通常先调用srand函数 time(0)返回的是系统的时间(从1970.1.1午夜算起),单位:秒,种子不同当然产生的随机数相同几率就很小了。

unsigned int seed;

srand(seed);// 我自己常这样写: srand((unsigned int) time(NULL));

rand();//以这三条语句为基础,如果seed = time(0) 的话,每次种子不同,就可以产生随机数了。

注意一点:

  RAND_MAX是VC中stdlib.h中宏定义的一个字符常量:

  #define RAND_MAX 0x7FFF

  其值最小为32767(2^15 -1, 即0x7FFF-1 ),最大为2147483647,

  通常在产生随机小数时可以使用RAND_MAX。

以上是随机数问题的基础知识。下面讨论几种方法。

 

题目:给定n个数,存放在数组中,抽取其中的m个数输出,要求每个数被选中的概率相等。

设数组为 a[n]

输出a[0]  的概率为: m/n;

输出a[1]  的概率为: m/n  * (m-1)/(n-1) + (1-m/n)  *  m/(n-1) = m/n;  其中m/n为算则a[0]的概率,(1-m/n)为不选择a[0]的概率,(m-1)/(n-1) 和  m/(n-1)分别表示对于a[0]的两种情况中选择a[1] 的概率,最后a[1]被选择的总概率为m/n.

以此类推。

以上证明来自概率论的条件概率知识,很容易理解的。

 

解法一:

i=0:rad = rand(), 如果rad%(n-i)=rad%n<m,则输出a[i],另m=m-1; 如果 如果rad%n>=m,则不输出a[i], 从剩余的n-1个数中选出m个随机数

i=1:rad= rand(), 如果rad%(n-i)=rad%(n-1) <m,则输出a[i],另m=m-1;如果 如果rad%n>=m,则不输出a[i], 从剩余的n-1个数中选出m个随机数

以此类推。这种方法到底正确不,可以考虑一个极限的情况,就是前 n-m 个数都没输出,就剩最后的m个数,理论上讲着m个数就必须全部输出才正确。

到数第m个数时为

i= n-(m-1) = n-m+1:rad%(n-i) = rad% (n-(n-m+1)=rad%(m-1),这个等式的最后结果肯定属于[0,m-1]区间内,该区间内的所有数都小于m,所以a[i] 肯定输出,最后另M=m-1了,M表示要输出的剩余数的个数;  

这就是 i 指向a[n]中倒数第m个数的情况,i=i+1,后,i 指向 a[n] 中的倒数m-1个数, 而上面已经输出一个数了,m已经减掉1了,所以就变成了, i 指向 a[n] 中的倒数m-1个数, 需要输出的数为M=m-1个。 又回到了刚才的状态。

 

重复以上过程,可知,对于假设的极限情况,就是前 n-m 个数都没输出,剩余的m个数会全部输出的!OK啦!!

 

代码:

void rad_m_from_n(int m, int n){        for(i=0; i<n; i++)        {               if(rand() %(n-i) <m)               {                       cout<<a[i]<<endl;                       m--;               }        }}


 

解法二:

将原始数组打乱,然后将数组的前m个数输出。

打乱的方法就是对于每一个a[i] ,从a[i] ~ a[n-1]中随机挑选一个a[j],交换a[i] a[j] 的值,由于只输出前m个,所以执行m次循环就可以了。

void rad_m_from_n2(int m, int n){        int i,j;        for(i=0; i<m; i++)        {               j = rand(i,n-1);//从区间[i],n-1]内随机抽取一个数,函数实现在下面有讲解               int t = a[i];               a[i] = a[j];               a[j] = t;        }        //sort(a,m)//如果需要按顺序输出的话,就将数组的前m个数排序        for(i=0; i<m ;i++)               cout << a[i]<<endl;} 


题目变形

变形一:《编程珠玑》第12章中的习题,rand()通常返回约15个随机位,使用该函数实现函数bigrand()和randint(l,u),要求前者至少返回30个随即位,后者返回[l,u]范围内的一个随机整数。

先来分析bigrand():初始是15位,现在是30位,自然想到了倍数的关系,由于2^30 = 2^15 * 2^15 ,可见bigrand()的返回值最大应当是RAND_MAX *RAND_MAX 了。答案最后为

int bigrand(){       return RAND_MAX*rand() + rand();}

 

金子分析:这个至少30个随机位,说实话我还没有搞懂,应该是最大值至少为30bits表示的最大数吧,如果按照答案的办法,产生的随即数集中在(2^15)~(2^30 -1)吧,确切的边界我没有比较,当然前提是rand()不等于0,到底经过随机后rand()会不会结果为0,我没实验过,上述的办法导致 1 ~ (2^15 -1) 无法产生阿。

我能想到的一种改进方法是 RAND_MAX*(rand() %2)+rand(),利用第一个rand()产生的数的奇偶性来指定输出前半段还是后半段的数。

 

randint 就好实现多了

int randint (int l,int u){     return l + bigrand() % ( u-l + 1);//注意边界不要弄错了。边界应该理解为u - (l -1).}

 

变形二:(百度的一道题目)

为了分析用户的行为,系统往往需要存储用户的一些query,但是因为query非常多,所以系统不能够存下每一条。假设我们的系统每天只能够存储mquery,现在需要设计一个算法,对用户时时请求的query进行随机选择m个,请给出一个方案,使得每一个query被抽中的概率尽量相等,也请附加相应的分析。需要注意的是,不到最后一刻你并不知道用户的总请求量是多少。

 

金子分析:本题需要一个假设,假设每天query的总数为n,题目就变成了n个数,随机抽取m个数的问题,但是因为题目不是将数存储在数组中,而是将抽取的数存放在a[m]中,所以具体实现如下:

1)先将每天的前mquery存放在数组a[m]中。

2

for(i=m; i<n; i++)    {          r = rand() % i; //随机[0,i]           if (r>=0 && r <m)          {                a[r] = a[i];           }      }     
3) 验证每个query被抽中的概率是否相等。

    首先看a[n-1],它被抽中的概率是 m/n;

     再看a[n-2], 随机到[0,m-1]的概率为 m/(n-1),要是它最后存在数组中的前m个,要保证对于a[n-1]时,获得的随机数不能与a[n-2]时的随机数相等,这样a[n-1]的随机数有 (n-1)种选择,则a[n-2]最后存在a[0...m-1]的概率为 m/(n-1)  *   (n-1)/n = m/n;

    以此类推,对于a[m,...,n-1]来说,被抽中的概率相等,且都为 m/n.

    

    最后我们在看已经放到数组中的前m个query, 对于a[0], 要想使a[0]不被替换,则从i=m 开始,一直到i=n-1,每个元素的随机数都不能为0,

    则 i=m时,随机数不为0的概率为, m/(m+1)

        i = m+1,   为(m+1)/(m+2).

        ......

        i = n-1时,为 (n-1)/n.

       最后,a[0]不被替换的概率为m/(m+1)  * (m+1)/(m+2)   * (m+2)/(m+3)*   ...... * (n-1)/n  = m/n;也就是说,a[0]不被替换,即被抽中的概率是 m/n.

     以上方法得证!













 


原创粉丝点击