1) rand函数和srand函数介绍
随机函数rand() 会随机生成一个位于0-RAND_MAX之间的整数;
#include
#include
int main()
{
for (int i = 0; i < 10; i++)
printf("%d ", rand());
printf("\n");
return 0;
}
使用rand()函数的缺点是, 对于同一个种子,总是会产生相同的随机数序列;下面的代码在每次运行程序之后,产生的随机数序列都是相同的;多次运行的结果都是下面输出, 如下图所示:
如果要想每次程序运行产生不一样的随机数序列, 需要改变种子;使用函数
void srand(unsigned int seed);
实际开发过程中使用运行程序的当前时间作为srand参数, 只要运行时间不同, 生成的种子不同。种子不同将使得随机数序列不同;
代码如下:
#include
#include
#include
int main()
{
srand(time(NULL));
for (int i = 0; i < 10; i++)
printf("%d ", rand());
printf("\n");
return 0;
}
多次运行程序的结果是:因为for循环非常快, time函数得到的是秒, 因此在多个循环中time(NULL)的返回值是一样的,导致种子一样;
#include
#include
#include
int main()
{
for (int i = 0; i < 100; i++) {
srand(time(NULL));
printf("%d ", rand());
}
return 0;
}
注意,使用下面代码得到的随机数组是不正确的,
2) 生成一定范围内的随机数
生成[a, b) 的随机整数, 使用 (rand() %(b-a)) + a;
生成(a, b] 的随机整数, 使用(rand() %(b-1)) + a+1;
生成[a, b] 的随机整数, 使用(rand() %(b-a+1)) + a;
生成0-1之间的浮点数, 使用rand()/double(RAND_MAX);
产生随机数最简单的方法是线性同余数生成器,
x(i+1) = (A * xi mod M);
为了开始这个序列, 必须给出x0, x0 就是种子;
一般选取M = 2^31 -1 = 2 147 483 647;
选取A = 48 271;
基于线性同余数生成器的随机数生成器代码如下,C++ 实现:
#include
#include
using namespace std;
static const int A = 48271;
static const int M = (1 << 31) - 1;
class Random {
public:
explicit Random(int initialValue = 1);
int randomInt();
double random0_1();
int randomInt(int low, int high);
private:
int state;
};
//将种子传入Random类
Random::Random(int initialValue) {
if (initialValue < 0)
initialValue += M;
state = initialValue;
if (state == 0)
state = 1;
}
//计算随机数
int Random::randomInt()
{
return state = (A * state) % M;
}
//计算0-1 之间的随机数
double Random::random0_1() {
return (double)randomInt() / M;
}
上述代码存在一个缺陷, 乘法运算 A * state 存在一个溢出的问题,导致结果中出现负数;
根据《数据结构与算法分析》书中的推算和论证, 可以使用下面公式优化;
具体的代码如下:
#include
#include
using namespace std;
static const int A = 48271;
static const int M = (1 << 31) - 1;//2 147 483 647
static const int Q = M / A;
static const int R = M % A;
class Random {
public:
explicit Random(int initialValue = 1);
int randomInt();
double random0_1();
int randomInt(int low, int high);
private:
int state;
};
//将种子传入Random类
Random::Random(int initialValue) {
if (initialValue < 0)
initialValue += M;
state = initialValue;
if (state == 0)
state = 1;
}
//计算随机数
int Random::randomInt()
{
int tmpState = A * (state % Q) - R * (state / Q);
if (tmpState >= 0)
state = tmpState;
else
state = tmpState + M;
return state;
}
//计算0-1 之间的随机数
double Random::random0_1() {
return (double)randomInt() / M;
}
int Random::randomInt(int low, int high) {
return (randomInt() % (high - low) + low);
}
int main()
{
Random a(time(NULL));//每次程序运行时候,生成的种子都是不同的
cout << a.randomInt() << endl;
cout << a.random0_1() << endl;
cout << a.randomInt(1, 10) << endl;
return 0;
}
1) 问题分析
rand5() 函数能够生成1, 2, 3, 4, 5的随机数;
rand7() 函数能够生成1, 2, 3, 4, 5, 6, 7的随机数;
现在已有rand5() 函数,求通过rand5() 生成rand7();
思路, 先考虑通过rand7() 函数生成rand5() 函数, 代码如下所示:
#include
#include
#include
int rand7() {
return (rand() % 7 + 1);
}
int rand5() {
int ret;
while ((ret = rand7()) > 5);
return ret;
}
int main()
{
srand(time(NULL));
for (int i = 0; i < 10; i++)
printf("%d ", rand5());
return 0;
}
在rand7() 函数中, 等概率地生成1, 2, 3, 4, 5, 6, 7;
在rand5() 函数中,舍去6, 7, 其余5个数的概率都是1/7, 因此也是等概率的;并且在rand5() 中生成1的概率是= (1/7) /(5/7) = 1/5;
得到一个一般的结论:
如果a > b, 那么一定可以用randa() 去实现randb();
2) 使用rand5() 生成 rand7()
先将rand5() 函数映射到一个能产生更大随机数的randa() , 其中a > 7;映射后的randa() 一定满足等概率生成1-a;
考虑
rand5() + rand5() -1, 生成1-9的数,但是不是等概率的,生成1 的情形只有(1, 1); 生成2 的情形有(1, 2) 和(2, 1);
组合
5*(rand5() -1) + rand5();生成1-25的随机数,并且概率是一致的;
加法的前半部分产生0, 5, 10, 15, 20;
加法的后半部分产生1, 2, 3, 4, 5;
两者加起来刚好是1-25之间, 并且每个数的概率都是1/5, 1/5;
思考:
使用rand5 生成一个更大范围的随机数, 肯定至少调用2次rand5();
此时生成随机数1的概率为1/5 × 1/5 = 1/25;
要求随机数等概率, 就得生成随机数的范围为1-25;
因此将1-25 分为5个区间[1, 5], [6, 10], [11, 15], [16, 20], [21, 25];
使用第一个随机数从5个区间中选取一个区间,使用第二个随机数从选定的区间中选取一个数;
得到: 5*(rand5() -1) + rand5();
相关的代码如下:
#include
#include
#include
int rand5() {
return (rand() % 5 + 1);
}
int rand7() {
int ret;
while ((ret = 5 * (rand5() - 1) + rand5()) >7);
return ret;
}
int main()
{
srand(time(NULL));
for (int i = 0; i < 10; i++)
printf("%d ", rand7());
return 0;
}
3) 代码优化
上述代码的一个缺点是,会产生1-25的随机数, 但是要截取8-25的数组,需要进行多次while循环之后才能返回得到1-7之间的随机数;
考虑舍去22, 23, 24, 25 这四个数,当返回的是1-21 的时候, 取余数+1 得到1-7的随机数;
#include
#include
#include
int rand5() {
return (rand() % 5 + 1);
}
int rand7() {
int ret;
while ((ret = 5 * (rand5() - 1) + rand5())) {
if (ret <= 21)
return ret % 7 + 1;
}
}
int main()
{
srand(time(NULL));
for (int i = 0; i < 10; i++)
printf("%d ", rand7());
return 0;
}
1) 思路1
使用两个rand5()调用最大只能产生1-25 范围的随机数, 使用3次rand5() 调用能生成1-125范围的随机数,然后在生成rand31()
2) 思路2
可以考虑使用rand5 生成rand7,然后使用rand5 和 rand7 生成1-35的随机数,之后裁剪到1-31 的随机数;