算法:随机数

本文探讨了在C++中生成随机数的三种思路:1)使用时间戳作为种子生成伪随机数;2)调整随机数区间以获得等概率分布;3)通过组合多个随机数达到更广泛的区间覆盖。每种思路都提供了相应的代码模板。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

随机数

思路1

在C++中,生成随机数的函数为rand(),返回一个不大于RAND_MAX的非负数。但是,如果生成随机数的种子和生成的次数相同,那么生成的随机数相同,因此rand()只能生成伪随机数。为了尽量得到真随机数,每次应该取不同的种子,由于time(NULL)返回从1970.1.1/00:00到现在的秒数,可以采用srand(time(NULL))。因为程序运行非常快,两次取随机数的间隔可能不超过111秒,所以在生成随机数的函数中设置种子有可能返回同一个值,事实上每次运行程序设置一次种子就够了。为了生成区间[a,b][a, b][a,b]的随机数,可以简单地取rand() % (b-a+1) + a。各随机数对应区间如下:

随机数区间
rand() % (b-a+1)[0,b−a][0, b-a][0,ba]
rand() % (b-a+1) + a[a,b][a, b][a,b]

模板1

#include <cstdio>
#include <cstdlib>
#include <ctime>

typedef long long LL;
bool tag = false;

/**
  * @return: the random integer in [a, b]
  */
LL getRandInt(LL a, LL b) {
  if (!tag) {
    srand(time(NULL));
    tag = true;
  }
  return rand() % (b-a+1) + a;
}

思路2

然而,按照思路1的方法得到[a,b][a, b][a,b]中每一个数的概率并不相等。举个例子,设rand()返回的最大值RAND_MAXMMM,那么对于区间[0,M−1][0, M-1][0,M1],若rand()返回[0,M−1][0, M-1][0,M1]中的数,则生成对应的随机数;若rand()返回MMM,则生成000,因此生成000的概率是其它随机数的两倍。为了解决这个问题,引入生成区间[0,1)[0, 1)[0,1)中随机浮点数的函数getRandDouble(),返回rand() / (RAND_MAX + 1.0),然后再返回floor(getRandDouble() * (b-a+1)) + a。各随机数对应区间如下:

随机数区间
getRandDouble()[0,1)[0, 1)[0,1)
getRandDouble() * (b-a+1)[0,b−a+1)[0, b-a+1)[0,ba+1)
floor(getRandDouble() * (b-a+1))[0,b−a][0, b-a][0,ba]
floor(getRandDouble() * (b-a+1)) + a[a,b][a, b][a,b]

模板2

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>

typedef long long LL;
bool tag = false;

/**
  * @return: the random number in [a, b)
  */
double getRandDouble() {
  if (!tag) {
    srand(time(NULL));
    tag = true;
  }
  return rand() / (RAND_MAX + 1.0);
}

/**
  * @return: the random integer in [a, b]
  */
LL getRandInt(LL a, LL b) {
  if (!tag) {
    srand(time(NULL));
    tag = true;
  }
  return floor(getRandDouble() * (b-a+1)) + a;
}

思路3

实际上,getRandDouble()并不能返回区间[0,1)[0, 1)[0,1)中的任意一个浮点数,因此依靠getRandDouble()生成的随机数仍然不是真随机数。举个例子,设rand()返回的最大值RAND_MAXMMM,那么对于区间[0,M+1][0, M+1][0,M+1]rand()最多只能返回M+1M+1M+1个数,因此至少有一个数生成的概率为000。接下来考虑扩大rand()rand()rand()返回随机数的范围,如果取两个随机数为一对,那么从(0,0)(0, 0)(0,0)(M,M)(M, M)(M,M)共有(M+1)×(M+1)(M+1) \times (M+1)(M+1)×(M+1)对,也就是说,随机数rand() * (RAND_MAX+1) + rand()可以表示区间[0,(M+1)×(M+1)−1][0, (M+1) \times (M+1) - 1][0,(M+1)×(M+1)1],因此只要取一个以rand()为数位的(M+1)(M+1)(M+1)进制随机数就可以表示任意大的区间范围。为了使生成每一个数的概率相等,从最大区间截取一部分使得区间长度为(b−a+1)(b-a+1)(ba+1)的倍数,再对生成的随机数按思路1处理即可。

模板3

#include <cstdio>
#include <cstdlib>
#include <ctime>

typedef long long LL;
bool tag = false;

/**
  * @return: the random integer in [a, b]
  */
LL getRandInt(LL a, LL b) {
  if (!tag) {
    srand(time(NULL));
    tag = true;
  }

  LL len = b-a+1;
  LL tmp = RAND_MAX + 1;
  LL rk = 1;
  while (tmp < len) {
    tmp *= RAND_MAX + 1;
    ++rk;
  }
  tmp = tmp / len * len;  // tmp = k * len, k > 0

  LL ans;
  while (true) {
    ans = 0;
    for (int i = 0; i < rk; ++i) {
      ans = ans * (RAND_MAX + 1) + rand();
    }
    if (ans < tmp) break;  // [0, tmp-1]
  }
  ans = ans % len + a;  // [0, len-1] + a

  return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值