随机数问题——基础知识必备
来源:互联网 发布:外文五大数据库 编辑:程序博客网 时间: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非常多,所以系统不能够存下每一条。假设我们的系统每天只能够存储m个query,现在需要设计一个算法,对用户时时请求的query进行随机选择m个,请给出一个方案,使得每一个query被抽中的概率尽量相等,也请附加相应的分析。需要注意的是,不到最后一刻你并不知道用户的总请求量是多少。
金子分析:本题需要一个假设,假设每天query的总数为n,题目就变成了n个数,随机抽取m个数的问题,但是因为题目不是将数存储在数组中,而是将抽取的数存放在a[m]中,所以具体实现如下:
1)先将每天的前m个query存放在数组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.
以上方法得证!
- 随机数问题——基础知识必备
- NOIP考试必备——随机数
- Java基础知识总汇—初学者必备
- 电脑装机全程图解,装机必备——硬件基础知识!
- 若只如初见——WEB/BS开发必备基础知识
- QML入门必备基础知识之——UI布局管理
- 程序必备基础知识学习:通信协议——Http、TCP、UDP
- android学习必备java基础知识——内部类
- 程序员必备基础知识:通信协议——Http、TCP、UDP
- 程序员必备基础知识:通信协议——Http、TCP、UDP
- 程序员必备基础知识:通信协议——Http、TCP、UDP
- Javascript中的数值转换问题(基础知识必备)
- [基础知识]随机数、随机字符串
- 【IOS基础知识】生成随机数
- ArcGIS Engine10.0轻松入门级教程(1)——必备基础知识
- paip.若只如初见——WEB或BS开发必备基础知识
- ArcGIS Engine10.0轻松入门级教程(1)——必备基础知识
- 【牛腩新闻发布系统】——必备基础知识HTML、XML、XHTML
- Automatic Reference Counting
- Ubuntu 安装 ATI驱动 失败处理
- 架构师1
- iOS开发——keychain的使用
- Eclipse报错,Path for project must have only one segment
- 随机数问题——基础知识必备
- 翻转字符串代码
- 程序员推荐简单有效的科学健脑方法
- PostgreSQL相关进程分类
- C语言中字符数组和字符串指针分析(转载)
- 如何提高Java开发能力
- 游戏王千金囊开发实录四——考题主窗体
- 从网页上提取用户邮箱为每个邮箱发送一封邮件
- 导入项目后,报错Missing library:xdoclet…ctory for XDoclet. 1.2.1