随机数生成器 - 为什么每次都要进行种子初始化。

18

我相对于C和C++还是新手。在我习惯编程的语言Java中,实现随机数生成非常容易。只需要从一个名为Math的类中调用静态的random方法即可。

int face = ((int)(Math.random() * 6) + 1);

模拟掷骰子的过程...

在C和C++中,您需要通过调用srand函数来"初始化随机数生成器"

srand ( time(NULL) );

做这件事有什么意义 - 我的意思是每次运行代码都必须重新生成随机数有什么优点吗?


14
注意:你不应该每次运行特定的代码时都进行种子初始化;而应该在进程启动时进行种子初始化。 - Roger Lipscombe
1
@RogerLipscombe 这真的取决于你想要实现什么。 - juanchopanza
wikipedia 的文章相当不错。在 Java 中,它仍然在第一次调用时种子化 PRNG,但这是在“引擎盖下”的操作。(http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#random()) - kbshimmyo
7个回答

27

给定相同的种子,伪随机数生成器将每次产生相同的序列。因此,关键在于您希望每次运行时是否要生成不同的伪随机数序列。

这实际上取决于您的需求。有时您需要重复一个序列,有时则不需要。您需要了解每个具体应用程序的需求。

绝不能在单个序列生成过程中反复播种。这样做很可能会破坏序列的分布。


种子的意义是什么?我在谷歌上搜索了很多链接,但大多数都没有解释它的含义。 - Erran Morad
你绝不能在生成单个序列期间反复进行种子设置。这样做很可能会破坏你的序列分布。为什么呢?因为每次重新设置种子时,都会重置随机数生成器的状态,从而导致生成的序列不再是真正的随机序列,而是伪随机序列。这将使得序列中的数字之间的关系变得更加复杂和不可预测,从而破坏了序列的分布特性。因此,在生成序列时,应该只设置一次种子,并使用该种子生成整个序列。 - Jose V
@Jose https://dev59.com/JUfSa4cB1Zd3GeqPBfNl https://www.johndcook.com/blog/2016/01/29/random-number-generator-seed-mistakes/ - David Heffernan

12

通常所称的随机数生成器实际上是伪随机数生成器。这通常意味着如果您提供该序列的“密钥”(称为“种子”),则可以生成相同的随机序列。当您希望测试基于随机化的算法并需要确保可重复结果时,这非常有用。

如果您没有“播种”您的随机数生成器,则默认情况下会使用某些(通常基于系统时间)随机数进行初始化,因此每次运行程序时都会产生不同的序列。


10
如果不为生成器设置种子,每次运行程序时它都会有相同的种子,随机数序列也将是相同的。此外,请注意只在程序开始时为生成器设置种子一次。

我原以为相反的情况才是真的,即如果我们种子生成器,那么每次都会得到相同的随机序列? - user5965026
@user5965026 的想法是使用一个唯一的种子。这就是为什么通常使用time的结果,它通常返回一个具有秒分辨率的值。 - Some programmer dude

8
优点是您可以通过提供相同的种子来重复随机数序列。
游戏《精英》就用这种方法将由成千上万颗星球组成的整个世界存储为一个数字。 要再次生成完全相同的世界,只需提供相同的种子即可。

4
这对于涉及随机数生成的代码调试非常有用。 - kbshimmyo
如果您需要大部分不可预测的种子,但又想要重现已观察到的行为,请让您的程序在结果中显示/记录使用的种子。并添加一个命令行开关来指定种子,如果存在则使用该种子,而不是time(NULL) - Medinoc
这对于模拟也非常方便。 - joshin4colours

4

为了从先前的随机数序列中生成不同的随机数序列(并非总是如此),伪随机数生成器需要种子。如果您不希望出现重复的序列,则需要对伪随机数生成器进行种子设置。

尝试这些代码并查看它们之间的差异。
没有种子:

#include <stdio.h>
#include <stdlib.h>

int main()
{
        printf("%d", rand()%6 + 1);
}  

使用种子:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
        srand(time(NULL));
        printf("%d", rand()%6 + 1);
}

1
随机数生成器并非真正的随机:假设您使用12作为种子生成100个随机数,再重复该过程并再次使用12作为种子生成另外100个随机数,它们将是相同的。我附上了一个使用种子12进行两次运行,每次运行20个条目的小样本,以说明它们在创建它们的代码之后立即被生成。
#include <iostream>
#include <cstdlib>

using namespace std;

int main()
{
    srand(12);
   for (int i =0;i<100; i++)
   {
       cout << rand() << endl;
   }
   return 0;
}

two randomly generated sequences both seeded with 12

为避免这种重复,常规做法是使用更独特的值,由于时间始终在变化,而且两个程序在精确到毫秒级别生成随机序列的几率很小,因此可以合理地安全地使用时间作为几乎唯一的种子。
要求:只需针对需要生成的每个唯一随机序列进行一次种子处理。
然而,这样做有一个意外的好处和坏处: 如果已知第一个序列生成的确切时间,则可以手动输入种子值,在将随机数生成器按照相同方式步进其过程时重新生成确切的序列(这对于存储随机序列来说是好处,但对于保持其随机性则是坏处)。

1
在C/C++中,模拟掷骰子的方法如下:
int face = (rand() % 6) + 1);
                   ^
                   |___________ Modulo operator

"% 6将随机数限制在0到5之间,+ 1用于抵消这个限制,使其变为1到6。"

OP提到了Java中的Math.random(),它返回0.0到1.0之间的浮点数。 - Klas Lindbäck
2
@KlasLindbäck 感谢您的澄清。我编辑了我的答案。至于C/C++,我也不知道是否有Math.random() - Fiddling Bits

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接