88

这个问题是关于这个问题中的评论 推荐的初始化srand的方法?第一条评论说srand()应该在应用程序中只调用一次。为什么会这样?

4

7 回答 7

122

这取决于您要达到的目标。

随机化作为具有起始值的函数执行,即种子

因此,对于相同的种子,您将始终获得相同的值序列。

如果您尝试在每次需要随机值时设置种子,并且种子是相同的数字,那么您将始终获得相同的“随机”值。

种子通常取自当前时间,即秒,如 中time(NULL),所以如果你总是在取随机数之前设置种子,只要你在同样的第二个

为了避免这个问题,每个应用程序只设置一次 srand,因为怀疑两个应用程序实例是否会在同一秒内初始化,因此每个实例将具有不同的随机数序列。

但是,您可能会在一秒钟内多次运行您的应用程序(特别是如果它是一个简短的应用程序,或者是一个命令行工具或类似的东西),那么您将不得不求助于其他方式来选择一个种子(除非您可以在不同的应用程序实例中使用相同的序列)。但就像我说的,这取决于您的应用程序使用上下文。

此外,您可能希望尝试将精度提高到微秒(最大限度地减少相同种子的机会),需要 ( sys/time.h):

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
于 2011-09-08T06:20:07.457 回答
25

随机数实际上是伪随机的。首先设置一个种子,每次调用rand都会从中获取一个随机数,并修改内部状态,并在下一次rand调用中使用这个新状态以获取另一个数字。因为使用某个公式来生成这些“随机数”,所以在每次调用后设置种子的某个值rand将从调用中返回相同的数字。例如srand (1234); rand ();将返回相同的值。使用种子值初始化初始状态将生成足够的随机数,因为您没有使用 设置内部状态srand,从而使数字更有可能是随机的。

一般我们time (NULL)在初始化种子值时使用返回的秒值。说srand (time (NULL));是在一个循环中。然后循环可以在一秒钟内迭代不止一次,因此在循环中的第二次rand调用中循环在循环内迭代的次数将返回相同的“随机数”,这是不希望的。在程序启动时初始化一次会设置一次种子,每次rand调用都会生成一个新的数字,并修改内部状态,所以下一次调用rand返回一个足够随机的数字。

例如来自http://linux.die.net/man/3/rand的代码:

static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
    next = seed;
}

内部状态next被声明为全局。每次myrand调用都会修改内部状态并更新它,并返回一个随机数。每次调用myrand都会有不同的next值,因此该方法每次调用都会返回不同的数字。

mysrand实现;它只是设置你传递给的种子值next。因此,如果您在next每次调用之前都将值设置为相同rand,它将返回相同的随机值,因为应用了相同的公式,这是不可取的,因为函数是随机的。

但是根据您的需要,您可以将种子设置为某个特定值,以在每次运行时生成相同的“随机序列”,例如针对某些基准或其他基准。

于 2011-09-08T10:15:04.247 回答
17

简短的回答:调用srand()不像随机数生成器的“掷骰子”。它也不像洗牌。如果有的话,它更像是切一副纸牌。

像这样想。 rand()从一副大牌中发牌,每次你跟注时,它所做的只是从牌堆顶部挑选下一张牌,给你价值,然后将那张牌放回牌堆底部。(是的,这意味着“随机”序列将在一段时间后重复。不过,这是一个非常大的牌组:通常有 4,294,967,296 张牌。)

此外,每次你的程序运行时,都会从游戏商店购买一副全新的卡片,并且每一副全新的卡片总是具有相同的顺序。所以除非你做一些特别的事情,每次你的程序运行时,它都会从rand().

现在,你可能会说,“好吧,那我该如何洗牌呢?” 答案——至少就目前rand而言srand——是没有办法洗牌。

那么做srand什么呢?根据我在这里建立的类比,跟注srand(n)基本上就像是在说“n从顶部切掉套牌”。但是等等,还有一件事:它实际上是从另一个全新的牌组开始,然后n从顶部切牌

因此,如果您每次都以相同的方式调用srand(n), rand(), srand(n), rand(), ... ,您将不会只是得到一个非常随机的序列,实际上您每次都会得到相同的数字。(可能不是您交给的号码,而是一遍又一遍的相同号码。)nrand()srandrand

所以你能做的最好的就是在你的程序开始时切牌一次,也就是说srand(),在你的程序开始时调用一次,n它是相当随机的,这样你每次在大牌组中的一个不同的随机位置开始你的程序运行。有了rand(),这确实是你能做的最好的事情。

[PS 是的,我知道,在现实生活中,当你购买一副全新的纸牌时,它通常是按顺序排列的,而不是随机排列的。为了让这个类比起作用,我想象你从游戏商店购买的每一副牌都以看似随机的顺序排列,但与你从同一家商店购买的所有其他牌组的看似随机顺序完全相同。有点像他们在桥牌比赛中使用的相同洗牌的牌组。]


附录:对于一个给定的 PRNG 算法和给定的种子值,你总是得到相同的序列这一事实的一个非常可爱的演示,请参阅这个问题(这是关于 Java,而不是 C,但无论如何)。

于 2016-10-25T21:29:07.890 回答
8

原因是它srand()设置了随机生成器的初始状态,如果您自己不接触中间的状态,生成器生成的所有值都只是“足够随机的”。

例如你可以这样做:

int getRandomValue()
{
    srand(time(0));
    return rand();
}

然后,如果您重复调用该函数,以便time()在相邻调用中返回相同的值,您只会得到相同的值 - 这是设计使然。

于 2011-09-08T06:15:26.957 回答
3

如图所示,一种更简单的解决方案用于srand()为在同一秒运行的应用程序实例生成不同的种子。

srand(time(NULL)-getpid());

这种方法使您的种子非常接近随机,因为无法猜测您的线程何时开始,并且 pid 也会不同。

于 2016-01-11T11:46:59.623 回答
2

srand 为伪随机数生成器提供种子。如果您多次调用它,您将重新播种 RNG。如果您使用相同的参数调用它,它将重新启动相同的序列。

为了证明这一点,如果你做一些简单的事情,比如:

#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d\n", rand());
    }
}

您将看到相同的数字打印了 100 次。

于 2011-09-08T06:20:46.040 回答
0

1\似乎每次 rand() 运行时,它都会为下一个 rand() 设置一个新种子。

2\如果 srand() 运行多次,问题是如果两次运行在一秒钟内发生(时间(NULL)不变),下一个 rand() 将与上一个之后的 rand() 相同srand()。

于 2017-12-02T06:14:49.973 回答