c++ primer 随机数讲解

原理
C++产生随机数
随机数
rand函数产生的是伪随机数,也就是说它不是一个真实的随机数。它的原理大概如下:
如果约定:a1=f(seed),an+1=f(an)那你可以得到一个序列:a1,a2,a3…an,那么要制作一个伪随机函数rand,只需要让它每调用一次就返回序列的下一个元素就行。
就相当于第1次调用rand返回a1,第2次返回a2,…,第n次返回an,这样每次调rand都能拿到一个不同的数,只要整个序列的规律不明显,整个函数看起来就是随机的。
现在计算机上的rand函数都是用这样的原理实现的,这里的seed被称为“随机数种子”。
所以程序跑一遍退出后,再重新跑一遍,两次输出的结果是相同的。如下:
#include
#include
int main()
{
int a=rand();//每次都是输出41
printf("%d\n",a);
return 0;
}

我们知道rand()函数可以用来产生随机数,但是这不是真正意义上的随机数,是一个伪随机数,是根据一个数(我们可以称它为种子)为基准以某个递推公式推算出来的一系列数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是定了的,除非你破坏了系统。
但这里有一个问题,如果seed不变,那我们每次调用rand函数获取的序列都是相同的。所以我们还需要一个接口去设置seed值,这个接口就是srand函数。

函数介绍
1.rand()
功能:随机数发生器
用法:intrand(void)
所在头文件:stdlib.h
rand()返回一随机数值的范围在0至RAND_MAX间。RAND_MAX的范围最少是在32767之间(int)。用unsignedint双字节是65535,四字节是4294967295的整数范围。0~RAND_MAX每个数字被选中的机率是相同的。
用户未设定随机数种子时,系统默认的随机数种子为1。
rand()产生的是伪随机数字,每次执行时是相同的;若要不同,用函数srand()初始化它。

2.srand()
功能:初始化随机数发生器
用法:voidsrand(unsignedintseed)
所在头文件:stdlib.h
srand()用来设置rand()产生随机数时的随机数种子。参数seed必须是个整数,如果每次seed都设相同值,rand()所产生的随机数值每次就会一样。

3.使用当前时钟作为随机数种子
rand()产生的随机数在每次运行的时候都是与上一次相同的。若要不同,用函数srand()初始化它。可以利用srand((unsignedint)(time(NULL))的方法,产生不同的随机数种子,因为每一次运行程序的时间是不同的。但要注意,time(NULL)的值是隔1秒才改变一次的,必要情况下可以考虑使用精度更高的时间函数,如gettimeofday。

4.产生随机数的用法
1)给srand()提供一个种子,它是一个unsignedint类型;
2)调用rand(),它会根据提供给srand()的种子值返回一个随机数(在0到RAND_MAX之间);
3)根据需要多次调用rand(),从而不间断地得到新的随机数;
4)无论什么时候,都可以给srand()提供一个新的种子,从而进一步“随机化”rand()的输出结果。
#include
#include<stdlib.h>
#include<time.h>
usingnamespacestd;
intmain()
{
srand((unsigned)time(NULL));
for(inti=0;i<10;i++)
cout<<rand()<<’\t’;
cout<<endl;
return0;
}
5.产生一定范围随机数的通用表示公式
要取得[a,b)的随机整数,使用(rand()%(b-a))+a;
要取得[a,b]的随机整数,使用(rand()%(b-a+1))+a;
要取得(a,b]的随机整数,使用(rand()%(b-a))+a+1;
通用公式:a+rand()%n;其中的a是起始值,n是整数的范围。
要取得a到b之间的随机整数,另一种表示:a+(int)b*rand()/(RAND_MAX+1)。
要取得0~1之间的浮点数,可以使用rand()/double(RAND_MAX)。
//但是这种方法不正确,原因是随机整数的精度通常低于随机浮点数,这样,有一些浮点值就永远不会被生成了。
【说明】
以前一般使用随机数用rand函数,rand函数有很多缺点。现在标准库引入了新的随机数引擎,对于生产随机数更加方便且功能强大了。
它一般由引擎+分布类型的方式来生成随机数。
【使用】
它需包含头文件
#include

直接用引擎生成两个随机数:
default_random_engine e;
for (size_t i = 0; i < 2; i++)
{
cout << e() << " ";
}
cout << endl;
//生成范围
cout << “min:” << e.min() << “max:” << e.max() << endl;

上面的使用方式类似于rand。一般实际使用还需配合分布类型,如下:
//定义0到9的分布类型
uniform_int_distribution u(0, 9);
for (size_t i = 0; i < 2; i++)
{
cout << u(e) << " ";
}
cout << endl;
uniform_int_distribution此类型生成均匀分布的unsigned值。分布类型定义了一个调用运算符,它接收一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。注意不能使用u(e()),因为我们传递的是引擎本身,而不是它生成的下一个值。

当然这些随机数每次重新打开生成还是一样的,这就需要随机数种子了,种子是在引擎构建时传入了,一般我们用time(0):
//构建随机数引擎,传入随机种子
default_random_engine e2(time(0));
//浮点0到1
uniform_real_distribution d(0, 1);
for (size_t i = 0; i < 2; i++)
{
cout << d(e2) << " ";
}
上面产生了0到1之间的随机数。

***注意如果一个函数定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static,否则每次调用函数都会生成相同的序列

随机数引擎操作
Engine e; 默认构造函数;使用该引擎类型默认种子
Engine e(s); 使用整型值s作为种子
e.seed(s) 使用种子s重置引擎的状态
e.min() 此引擎可生成的最小值
e.max()
Engine::result_type 此引擎生成的unsigned整型类型
e.discard(u) 将引擎推进u步;u的类型为unsigned long long

分布类型
uniform_int_distribution 均匀分布的随机整型(和上面的 int rand(); 功能类似)
uniform_real_distribution 均匀分布的随机浮点型
bernoulli_distribution 随机布尔型(这个类型可以允许调整概率 b(.55)这样就有55/45 括号表示返回true的概率)
binomial_distribution 二项分布的随机整型
binomial_distribution 几何分布的随机整型
exponential_distribution 指数分布的随机整型
等等
分布类型从操作
Dist d; //默认构造函数,使d 准备好被使用;其他构造函数依赖于Dist的类型
分布类型的构造函数时explicit
d(e) //用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列,e是 一个随机数引擎对象
d.min //返回d(e)能生成的最小值
d.max //
d.rest() //重建d 的状态,使得随后对d的使用不依赖于已经生成的值

不重复随机数
1.方法一
最初的思想是每生成一个随机数,便于前面的所有随机数进行比较,如果有重复,则舍去不要,重新选取。但该方法十分费时,并且在数据量巨大的并且有一定限制的时候,会引发巨大问题。例如要生成10000个随机数,范围是0-9999,且不能重复,那么最后几个随机数有可能需要相当长的时间才能筛选出来。
下面我们从另外一个角度来思考,假设我们已经由一个数组长度为10000的数组,里面分别存储了数据0-9999,我现在的做法是想办法让10000个数进行随机排列,便得到了这样一个随机数列,如果我只要其中的100个数,那么从前面取出100个就好。这里利用algorithm.h里面的一个函数,来进行简单处理。
template
voidrandom_shuffle(
RandomAccessIterator_First,
RandomAccessIterator_Last
);
这个函数操作的对象是容器的迭代器,即我们需要将存储数据从数组变为容器就好了,下面代码实现一下:
复制代码
#include
#include
#include
usingnamespacestd;
voidrandperm(intNum)
{
vectortemp;
for(inti=0;i<Num;++i)
{
temp.push_back(i+1);
}
random_shuffle(temp.begin(),temp.end());
for(inti=0;i<temp.size();i++)
{
cout<<temp[i]<<"";
}
}
cout<<endl;
2.方法二
按顺序产生这些数,但随机产生它们的位置。例如下面产生100个100以内不重复随机数的代码:
inta[100];
for(i=0;i<=99;++i)a[i]=i;
for(i=99;i>=1;–i)swap(a[i],a[rand()%i]);
上面这段代码只需要遍历一次就可以产生这100个不重复的随机数,它是如何做到的呢?首先第二行按顺序用0到99填满整个数组;第三行,是随机产生从0到m-2个数组下标,把这个下标的元素值跟m-1下标的元
素值交换,一直进行到下标为1的元素。因此它只需要遍历一次就能产生全部的随机数。
再看下面的代码,原理跟上面例子相似,但效率比上面的差点,但仍不失为一个好方法:
复制代码
inta[100]={0};
inti,m;
for(i=1;i<=99;++i)
{
while(a[m=rand()%100]);
a[m]=i;
}
这段代码也是随机产生位置,但它预先把整个数组初始化为0,然后随机产生其中一个位置,如果该元素值为0,表示这个位置还没有被使用过,就把i赋予它;否则,就重新随机产生另一个位置,直到整个数组
被填满。这个方法,越到后面,遇到已使用过的元素的可能性越高,重复次数就越多,这是不及第一个方法的地方,但总的来说,效率还是不错的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值