2.2. 生成随机数¶
编写算法程序常常会需要自己生成一些测试数据。如果数据量小可以自己手动编一些,但是往往会需要大数据量的测试数据,这时候手动编数据就很困难了。我们需要用计算机来自动生成一批随机数,使用 cstdlib
库的 rand()
函数可以生成均匀分布的随机数。
int rand();
调用这个函数不需要提供任何参数,每次调用返回一个从0到 int
类型最大值(一般是231-1=2,147,483,647)之间的随机数。将得到的随机数除以任一大于1的正整数 \(a\) 取余数,余数的取值范围为 \([0,a)\),所以调用 rand() % a
即可得到在此范围内均匀分布的随机数。
更一般地,如果要生成 \([a,b)\) 区间内的随机数,可以通过调用 rand() % (b - a) + a
来实现。例如要生成从1到99之间均匀分布的随机数,那么就调用 rand() % 99 + 1
,而不是 rand() % 100 + 1
,后者生成的是从1到100的随机数;若要生成-100到100之间的随机数,则应调用 rand() % 201 - 100
。这个规则可以简单记作“除长加头”。
注意
请注意在计算机软件的世界里,表示范围的惯例就是“含头不含尾”,用数学语言来说就是“左闭右开”区间,千万要牢记并熟悉这一惯例。
rand()
函数生成的其实是伪随机数,并不是真正的随机数。这些“随机”数实际上是用一套精巧但是确定的数学公式计算出来的,看上去随机,实际上有规律。程序每一次运行,第一次调用 rand()
时,会用一个正整数来启动随机数生成过程。这个正整数叫做伪随机数生成过程的种子。用同一个种子启动伪随机过程,会生成完全相同的伪随机数序列。不巧的是,rand()
函数每次都用整数1来作为启动的种子,从来不变,永远是1!我们可以多次运行下面这个小程序来观察这一现象。
#include <cstdlib>
#include <cstdio>
int main()
{
for (int i = 0; i < 100; i++)
printf("%3d%c", rand() % 100, i % 20 == 19 ? '\n' : ',');
return 0;
}
我可以保证,不管运行多少次,你都会得到同样的一百个数,如果你也使用NOI Linux的g++ 4.8.4的话,十有八九是这样的:
1, 4, 6, 70, 87, 66, 57, 92, 60, 11, 81, 79, 85, 72, 50, 62, 43, 16, 36, 56
13, 70, 25, 99, 17, 8, 91, 97, 8, 64, 50, 61, 20, 56, 84, 7, 74, 41, 51, 87
4, 33, 66, 41, 57, 16, 3, 0, 84, 39, 9, 50, 61, 34, 1, 78, 94, 92, 28, 2
56, 78, 16, 28, 35, 0, 88, 9, 41, 39, 96, 97, 24, 62, 38, 82, 30, 41, 34, 15
32, 43, 17, 45, 29, 18, 23, 24, 10, 51, 78, 18, 30, 94, 46, 17, 94, 34, 26, 87
这可怎么办?这样不就没法生成真正可用的随机数了吗?当然不会。cstdlib
库提供了一个用来设置随机种子的函数 srand()
。
void srand(unsigned int seed);
这个函数没有返回值,调用它可以给伪随机过程人为设置一个启动种子。所以我们只要在程序开始时调用一下 srand()
函数,种下一颗“每次程序运行都不相同而且很难被复制”的种子,就可以用 rand()
来生成仿真程度相当好的随机数了。可是到哪里去找这样一个“每次程序运行都不相同而且很难被复制”的无符号整数呢?方法很多,最方便最常用的是使用 ctime
库提供的 time()
函数。调用 time(NULL)
可获取程序运行到当前语句时的实际时间,是从1970年1月1日凌晨0点0分0秒开始到当前为止的秒数,其返回类型 t_time
实际上就是 unsigned int
,它恰好能满足“每次运行都不相同而且很难被复制”的要求。
现在我们来试着运行一下用 time()
函数优化过的这个小程序。
#include <cstdlib>
#include <ctime>
#include <cstdio>
int main()
{
srand(time(NULL));
for (int i = 0; i < 100; i++)
printf("%3d%c", rand() % 100, i % 20 == 19 ? '\n' : ',');
return 0;
}
多运行几次,你会发现现在每次都会得到100个完全看不出规律的不同的随机数。
提示
在每一个要用到随机数的程序的开头,运行一下 srand(time(NULL))
。
思考
如果要生成浮点数类型的随机数,或者比 int
更大的整型,比如 long long
类型的随机数时应该怎么办呢?这个问题请自行思考解决。
警告
无论看上去多么没有规律,rand()
函数使用的伪随机数生成算法都是不安全的,只能用于一般的随机数模拟,不能用于真正的加解密应用。如果今后你要编写实用的加密解密算法,绝对不能使用它来生成密钥!