内容来自互联网,做了一些修改
参考:
http://blog.csdn.net/tianshuai1111/article/details/7583297
http://blog.csdn.net/tianshuai1111/article/details/7583297
http://blog.csdn.net/tianshuai1111/article/details/7583297
http://blog.csdn.net/tianshuai1111/article/details/7583297
1
(1)C库函数rand()通常返回约15个随机位。如果要求返回至少30个随机位应该怎么处理?
rand()函数介绍:http://blog.csdn.net/wordwarwordwar/article/details/41006669
给定一个rand(),可以产生从0到RAND_MAX的随机数,其中RAND_MAX很大(常见值:16位int能表示的最大整数32767),写出利用rand()生成[a,b]中任意整数的函数,其中a>=0, b<=RAND_MAX,且b-a<<RAND_MAX.
利用库函数<stdlib.h>中的rand()函数可以产生0到RAND_MAX范围内的随机整数。
RAND_MAX是在前面头文件中定义的宏,具体大小与实现有关,至少为32767(2^15-1).
一般32位机,int型为4字节,故RAND_MAX大小为2147483647(2^31-1).
两个相关函数:
产生很大随机整数bigrand():RAND_MAX *rand() + rand();实际上就是先产生前15位,再产生后15位,这样的话就可以返回至少30个随机位。
也即rand()<<15 | rand();
产生指定范围随机整数 randint(l,u): rand()%(u-l+1)+l; 产生的数据在[l,u]之间.
(2)产生[m,n]范围内的随机整数
<span style="font-size:18px;"></pre><pre name="code" class="cpp">#include<iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;
void GetRandomNum(int n,int m)//产生[0~n-1)范围内的m个随机数.注意包括了0但是不包括n-1.
{
srand(time(NULL));
for(int j=0;j<3;j++)
{
cout<<rand()%(n-m+1)+m<<endl;
}
}
int main()
{
int n=60;
int m=40;
GetRandomNum(n,m);
return 0;
}
</span>
2
题目不要求子集的概率相等,就可以不使用前面的方法
的“以等概率选择搜有元素,但是有些m元子集被选中的概率比其他子集大”的算法:直接选择1个数,则这个m元集合为它本身即后续的一共m个数,可能包括回绕。
如果要求子集的概率相等,这里就不能够采用上面的做法了。
抽象后的问题如下:输入两个整数m和n,(m < n).输出0~n-1范围内的m个随机整数的有序列表,不允许重复。也就是说,要对0~n-1范围内的数字进行选择,每个数字被选中的概率相等.
有两点要注意:不允许重复,结果有序;
2.2方法一:使用set集
这种方法也是一种较为通用的方法,使用set不仅排除了生成相同的数字,对其遍历的过程也实现了排序。
void getsets(int m, int n)
{
set<int> S;
while(S.size() < m)
S.insert(rand()%n); //insert保证了如果出现S中已有数字,那就什么都不做
set<int>::iterator i;
for(i = S.begin(); i != S.end(); i++)
cout << *i << endl;
}
时间效率:set每次插入时间为O(logm),while循环总共进行了m次插入,遍历集合需要O(m).故总时间为O(mlogm).额外的空间需求为set集的大小:O(m).
2.3方法二:Knuth的算法S
依次考虑整数 0,1,2,……,n-1,通过一个适当的随机测试对每个整数进行选择。通过按序访问整数,保证输出结果有序。
设m=2, n=5,那么选择第一个整数0的概率就是2/5,这种确定概率,通过 if (rand()%5) < 2 来判断是否选取该数字,然后判断是否选择整数1,若0被选中,以 1/4 的概率选择1, 若0未被选中,以 2/4 的概率选择1,…… ,总之,对于从r个剩余整数中选s个,以 s/r 来选择下一个数。
void getknuth(int m, int n)
{
for(int i = 0; i < n; i++)
if(rand() % (n-i) < m)
{
cout << i << endl;
m--;
}
}
时间效率:该程序的时间复杂度是与n有关的:O(n),空间上只需要几十个字节。
2.4方法三:打乱数组顺序
对于包含整数0~n-1的数组,打乱数组的前m个元素,然后把前m个元素排序输出即可。
void genshuf(int m, int n)
{
vector<int> x(n);
for(int i = 0; i < n; i++)
x[i] = i;
for(int j = 0; j < m; j++)
{
int k = randint(i, n-1);//输出i到n-1之间的随机数
SWAP(x[i], x[k]);
}
sort(x, x+m);
for(int j = 0; j < m; j++)
cout << x[j] << endl;
}
时间效率:时间上为O(n+mlogm), 一次初始化和排序。空间上也需要O(n).
2.5其他方法
根据问题的实际情况(m和n的相对大小),如若n为100万,m为n-10时,可以生成10个元素的随机样本,然后输出不在样本中的整数
从一般到特殊
以上讨论的几种方式都不限定m和n的取值,只需m<=n即可。对于特殊的取值,有特殊的解决方案,以下是编程珠玑上的两例:
1.n = 106而m=n-10,这时可以先生成10个元素,然后输出其余的元素。进行这种处理的额外代码可以提高算法的平均速度。
2.n=231而m = 107,m<<n,这时可以先生成1.1*107个元素,排序后去掉重复的(由于n很大,m中出现重复元素的概率很低),得到 107个元素的样本。
3
当m < n/2时,
总共试了k次,则前面k-1次找到的数都是在集合中,那么只有第k次不在里面,那么概率
p = (m/n)^(k-1) * (n-m)/n
那么期望是 连加 k = 1至无穷大,根据二项式分布可知,期望等于
n/(n-m) < 2
从而可知得证
4
参考算法导论中文版64页。
搜集n张随机赠送的赠券,需要多少次? nln(n)次
对于集合插入问题,每次产生随机数后都需要在集合中检测该元素是否已经存在,这个测试次数和调用随机数函数的次数相同。对于从n个元素中选择m个元素的问题,平均需要测试多少次才能保证选择了m个元素?
这里先处理特殊的问题,即“赠券收集问题”:必须收集多少张棒球卡才能保证拥有所有的n种卡?
记Pi为从收集了i-1种到i种的概率,Pi=(n-i+1)/n
此时需要收集的卡片数目ni = 1* Pi + 2*(1-Pi)*Pi + 3*(1-Pi)2*Pi + ... + n*(1-Pi)n*Pi + ...,这个无穷级数可以求解为ni=1/Pi
那么所需要总的卡片数目sum(ni) = n1+n2 +... +nn约为nlnn + γn + O(1)。相关维基百科
对于从n个元素中选择m个,相应地sum(ni) = n1+n2 +... +nm,约为nlnm+γn。(根据调和数的推导)
8
从0~n-1中随机选择m个数,
输出顺序随机,不重复:(使用方法2.4,但不进行排序)
fori = [0,n)
x[i] = i;
for j = [0,m)
int k = randint(i, n-1);
SWAP(x[i], x[k]);
for i = [0,m)
cout << x[i]
输出顺序随机,可重复:(最普通的情况)
for i = [0, m)
cout << rand() % n;
输出有序,不重复:
前面写的三种方法均可。
输出有序,可重复:
删除对生成数字是否已在结果集的判断,(可以采用第13章将要介绍的各种数据结构,也可以直接使用multiset标准STL)
对输出顺序随机,可重复的结果再进行排序
9上面方法中,如果m接近于n的时候,基于集合的算法(使用容器set)生成的很多随机数都要丢掉。求一个算法,即使在最坏的情况下也只是使用m个随机数。
<span style="font-size:18px;">#include <iostream>
#include<time.h>
#include <set>
using namespace std;
void getSet(int m,int n)//在0 -- n-1 中挑选m个 随机数
{
srand(time(NULL));//这个很关键
set<int> S;
for(int i=n-m;i<n;++i)
{
int t=rand()%(i+1); //t是[0~i)之间的随机数
if(S.find(t) == S.end())//s.find(t)如果s容器中存在按t索引的元素,则返回指向该元素的迭代器。如 //果不存在则返回超出末端迭代器
S.insert(t);
else
S.insert(i);
}
set<int>::iterator j;
for(j=S.begin();j!=S.end();++j)
cout<<*j<<" ";
}
int main()
{
getSet(5,10);
return 0;
} </span>
10
问题:如何随机从n个对象中选择一个对象,这n个对象是按序排列的,但是在此之前你并不知道n的值?
具体些说,在事先并不知道行数的情况下,如何读一个文本文件,随机选择并输出一行?
解答:我们总是选择第一行,并使用二分之一的概率选择第二行,使用三分之一的概率选择第三行,以此类推。在该过程结束的时候,每一行具有相同的选中概率(1/n,其中n是文件的总行数):
i = 0 while more input lineswith probability 1.0/++ichoice = this input line //如果前面做了选择,并不会break,而是直到最后一个为止。print choice
这里比较有些疑惑的是第一行:总是选第一行 为什么概率还是1/n?
概率=1*(1/2)*(2/3)*(3/4)……(n-1/n) =1/n
<span style="font-size:18px;">int random_select(void)
{
int i,num=1;
for(i=1;i<n;i++)
//i<n代表某种终止条件,n未知
if(rand()%i ==0)
num = i;
return num;
}</span>
12每个玩家有一张包含16个覆盖点的纸牌,覆盖点下面隐藏着1~16的随机排列。玩家每次刮开一个点,如果出现3,则判玩家负;如果出现1或2,则判玩家胜。那么,随机选择覆盖点刮开,获胜的概率是多少?
不要把问题复杂化,应该尽量简化。4~16的数字都是没有用的。这里只用考虑1,2,3的排列,共有3*2*1=6种。获胜排列方法只有两种就是*1*2*3*或*2*1*3*
所以获胜的概率是1/3.