rand() 和 srand()
<cstdlib> 中提供的 rand() 是比较常用的生成随机数的方法。它可以通过线性同余法提供一个 中的伪随机数,类型为 int。
1 |
根据定义,RAND_MAX 大小为 ,也就是说 rand() 生成的数字将会落在 这个区间内。
rand() 本身规则是以 1 为种子,如果在同一个程序中多次运行 rand(),可以得到不同的结果。不过,下一次运行程序时将会重复一模一样的序列,某种程度上,这样的随机数失去了意义。
解决方法一般是用 srand() 为 rand() 进行初始化。srand() 接受一个无符号整型作为输入,这个值将会作为种子供 rand() 进行迭代。srand() 可以多次使用,每次使用都会让 rand() 使用新种子开启新一轮迭代。
显然,如果 srand() 每次都接收到一个相同的值,那么这个初始化也就没有意义了。因此,一般使用一些会流变的特殊值进行初始化,其中最为方便的就是时间。
<ctime> 中提供了 time(),它可以获取自 1970 年 1 月 1 日以来经过的秒数,类型为 time_t,是一个整型。这个函数可以返回一个值,允许直接把返回值赋给 int 类型。同时,函数也允许提供一个类型为 time_t 的指针作为参数,返回值会写入指针地址。这个参数允许空指针。需要注意的是,由于 srand() 接受的是一个无符号整型,所以在使用时应该进行类型转换。
另一种方法借助于 <windows.h> 中提供的 GetTickCount() 函数,它返回自系统启动以来经过的毫秒数,类型为 DWORD。该类型本身就是一个无符号整型,可以直接传递给 srand()。这种方法的好处是精度更高(毫秒级),可以一定程度上避免并行时获取到相同种子。
1 | // 使用例 |
<random>
从 C++11 开始,<random> 被引入。这个库允许定义随机数生成工具,并且允许对随机数的分布进行定义。
均匀随机数生成器
<random> 库中定义了一些均匀随机数生成器(Uniform Random Number Generator, URNG),他们被称为引擎。这些引擎可以生成范围内的均匀随机数。比较常见的有以下几个:
mt19937:应用梅森旋转算法,32 位字长。mt19937_64:同样是梅森旋转算法,不过字长为 64 位。knuth_b:应用高德纳随机算法。minstd_rand:应用线性同余法。ranlux24:应用 Ranlux 算法,生成字长为 24 位的随机数。ranlux24_base:ranlux24的基础引擎。ranlux48/ranlux48_base:同上,但生成字长为 48 位的随机数。default_random_engine:默认引擎,不过不清楚实际调用了哪个引擎。(不推荐使用)
以上这些都可以在范围内生成均匀的随机数。在使用时可以按照以下方法:
1 | mt19937 E((unsigned)time(NULL)); // 定义引擎类型,此时可以传入种子 |
分布类型
<random> 还允许对分布类型进行定义。常见的分布类型有:
- 均匀分布
uniform_int_distribution:给定闭区间内生成整数的均匀分布。uniform_real_distribution:给定左闭右开区间内生成浮点数的均匀分布。generate_canonical:生成 内均匀的浮点数分布,可以指定精度。
- 伯努利分布
bernoulli_distribution:生成布尔值的伯努利分布。binomial_distribution:生成整数值的二项式分布。geometric_distribution:生成整数值的几何分布。negative_binomial_distribution:生成整数值的负二项式分布。
- 正态分布
cauchy_distribution:生成浮点值的柯西分布。chi_squared_distribution:生成浮点值的卡方分布。fisher_f_distribution:生成浮点值的 F 分布(Fisher-Snedecor 分布)。lognormal_distribution:生成浮点值的对数正态分布。normal_distribution:生成浮点值的正态分布(高斯分布)。student_t_distribution:生成浮点值的学生 t 分布。
- 泊松分布
exponential_distribution:生成浮点值的指数分布。extreme_value_distribution:生成浮点值的极值分布。gamma_distribution:生成浮点值的伽马分布。poisson_distribution:生成整数值的泊松分布。weibull_distribution:生成浮点值的韦伯分布。
- 抽样分布
discrete_distribution:生成离散整数分布。piecewise_constant_distribution:生成浮点值的分段常数分布。piecewise_linear_distribution:生成浮点值的分段线性分布。
不同的分布方法允许不同的传入参数,举例来说,normal_distribution 允许传入两个参数,前者为期望,后者为标准差。
1 | mt19937 e; |
需要注意的是,尖括号内虽然允许传递模板参数,但不能传递不同的数据类型。举例来说,uniform_int_distribution 后允许传入 short、int、long long 等整型,但不允许传入 float、double 等浮点型。
random_device
random_device 也是一种 URNG,但是不同之处是,random_device 可以获取真随机数。真随机数可能来源于 Linux 维护的熵池,在 Windows 上可能来源于微软的加密 API,在没有获取真随机数的途径时,它会生成伪随机数。
这种方法的优点就在于其真随机性,但是弊端也很明显:较慢的获取速度,同时熵池是有限的,在无法获取真随机数时,random_device 改为生成伪随机数。这表明 random_device 是不稳定的,并不适合直接作为随机数生成器调用多次。
一般来说,random_device 的用法是代替时间函数作为种子。用 random_device 生成一个真随机数,然后把这个随机数作为种子传递给其他伪随机数生成器。
1 | // 使用例 |
总结
一般情况下,rand() 配合时间函数作为种子,可以生成比较令人满意的随机数。在有特殊的对于随机数分布的要求或者是对于随机浮点数的需求时,可以调用 <random> 来实现。