6

我已经看到很多建议不要在每次执行时多次播种伪随机数生成器,但从未附有详尽的解释。当然,很容易看出为什么以下 (C/C++) 示例不是一个好主意:

int get_rand() {
  srand(time(NULL));
  return rand();
}

get_rand因为每秒调用几次会产生重复的结果。

但是下面的例子难道不是一个可以接受的解决方案吗?

MyRand.h

#ifndef MY_RAND_H
#define MY_RAND_H

class MyRand
{
  public:
    MyRand();
    int get_rand() const;
  private:
    static unsigned int seed_base;
};

#endif

我的兰德.cpp

#include <ctime>
#include <cstdlib>
#include "MyRand.h"

unsigned int MyRand::seed_base = static_cast<unsigned int>(time(NULL));

MyRand::MyRand()
{
  srand(seed_base++);
}

int MyRand::get_rand() const
{
  return rand();
}

主文件

#include <iostream>
#include "MyRand.h"

int main(int argc, char *argv[]) 
{
  for (int i = 0; i < 100; i++) 
  {
    MyRand r;
    std::cout << r.get_rand() << " ";
  }
}

即,即使MyRand:s 构造函数被快速连续调用多次,每次调用srand都有不同的参数。显然,这不是线程安全的,但同样也不是rand.

4

4 回答 4

8

每次调用伪随机数生成器函数时,生成器都会获取一些内部状态并生成伪随机数和新的内部状态。转换内部状态的算法经过仔细选择,因此输出看起来是随机的。

当您播种随机数生成器时,您基本上是在设置此内部状态。如果您将内部状态重置为某个可预测的值,您将失去随机性的外观。

例如,流行的简单 RNG 是线性同余生成器。数字是这样生成的:

X[n+1] = (a X[n] + c) mod m

在这种情况下,X[n+1] 既是结果也是新的内部状态。如果您每次都按照上面的建议为生成器播种,您将获得如下所示的序列:

{(ab + c) mod m, (a(b+1) + c) mod m, (a(b+2) + c) mod m, ...}

其中 b 是你的seed_base。这看起来一点也不随意。

于 2009-06-10T17:40:44.953 回答
1

如果你的种子是可预测的,它就在这里,因为你只是在增加它,那么 rand() 的输出也将是可预测的。

这实际上取决于您为什么要生成随机数,以及“随机”对您来说如何是可接受的随机数。在您的示例中,它可能会避免快速连续重复,这对您来说可能已经足够了。毕竟,重要的是它运行。

在几乎每个平台上,都有比 rand() 更好的方法来生成随机数。

于 2009-06-10T17:33:44.407 回答
1

好吧,这是不需要完成的额外处理。

在那种情况下,我只需在循环开始之前使用基于时间的种子调用构造函数一次。这将保证随机结果,而无需为每次迭代更改种子的额外开销。

我认为您的方法不会比这随机。

于 2009-06-10T17:35:48.287 回答
0

您可以将随机数生成(这不再是严格意义上的实现方式,而是用作说明)视为一个值表。如果您记得在统计数据中为简单随机样本做任何这些事情,那么种子基本上会告诉您在随机数的大表中从哪一行和哪一列开始。一遍又一遍地重新播种是完全没有必要的,因为我们已经可以假设这些数字已经是正态分布的。

多次播种根本没有额外的好处,因为这应该足够好(取决于应用程序)。如果您确实需要“更多”随机数,有很多随机数生成方法。我能想到的一种情况是以线程安全的方式生成随机数。

虽然您的解决方案是可以接受的,但您的数字不会比在全球范围内播种一次更随机。srand 通常不应该属于构造函数。如果您想支持随机数,请在程序启动时播种一次,然后忘记它。

于 2009-06-10T18:02:02.697 回答