随机数生成器每次运行应用程序时都生成相同的数字

10

我知道这个问题已经被提出多次,但是这些解决方案都对我没有用。

首先,在我的方法RandomNumGenerator(items)中我做了这个。

List<int> randNum = new List<int>();
foreach (var item in items)
{
    randNum.Add(new Random(1000).Next());
}

我之前一直得到相同的数字,后来看了这个回答后,我做了以下操作:

Random rnd = new Random(1000);
foreach (var item in items)
{
    randNum.Add(rnd.Next());
}

这给我提供了以下数字

325467165 
506683626   
1623525913  
2344573     
1485571032

虽然这对于每次循环来说都没问题,但问题在于,当我停止并重新运行应用程序时,我会再次得到之前得到的相同数字。

325467165 
506683626   
1623525913  
2344573     
1485571032

这种行为是仅在调试期间存在的,还是每次调用 RandomNumGenerator 都会遇到相同的问题?

1
我建议您在类内将 rnd 声明为 static readonly 并重复使用同一实例。 - Alessandro D'Andria
1
这里有很多好的答案,让我很难选择要标记为正确的答案。我在 Neel 和 Tim Schmelter 给出的答案之间犹豫不决。虽然 Tim 的答案解决了我的问题,但 Neel 的答案为我提供了一个可能未来问题的解决方案。 - user20358
16
你正在使用相同的数字来初始化生成器。省略该参数,它将从时钟中派生一个种子。 - Jodrell
1
现在这并不是问题的关键,但请使用实际可编译的代码以备将来之需。我们都知道,如果您不向我们展示给出结果的代码,问题可能是任何东西。 - Jeroen Vannevel
1
这个问题以前已经被问过和回答了很多次。除了楼主的明显困惑,我没有看到任何与众不同的地方。 - RBarryYoung
显示剩余2条评论
6个回答

156

在这里,您始终使用相同的种子1000来生成Random实例:

Random rnd = new Random(1000);

这样做是不行的,因为当前时间被用作种子:

Random rnd = new Random();

请看接受一个 int 参数的 构造函数

向不同 Random 对象提供相同的种子值,将导致每个实例生成 相同序列的随机数。


43

根据MSDN的说法。

public Random(
    int Seed
)

种子

用于计算伪随机数序列起始值的数字。如果指定了负数,则使用该数字的绝对值。

大多数初学者在使用 RNG(随机数生成器)时犯的错误是缺乏对“种子”是什么以及它的作用的理解。


那么,“种子”到底是什么?

Random 类是用于生成伪随机数的类,或者说是看起来像随机的数字。它们通常是一个数学函数,使用一个参数——“种子”——生成一系列看起来像随机的数字。

对于 new Random(1000),前 5 个非负随机整数为

325467165
506683626
1623525913
2344573
1485571032

在你的第一段代码中,每次需要随机数时,你都会创建一个带有相同种子的新伪随机数序列,因此显然你的数组被填充了相同的数字:325467165,这恰好是由 new Random(1000) 生成的第一个非负整数。

这也解释了为什么你的第二段代码每次启动应用程序时都会生成相同的伪随机数序列。

为了确保你的应用程序始终生成不同的伪随机序列,你需要每次使用不同的种子。最简单的方法是慢慢来,真的。

Random rnd = new Random(DateTime.UtcNow.Millisecond);
// Taking the millisecond component, because it changes quickly

幸运的是,你不必输入这么多文字,因为 Random 类的默认构造函数 已经实现了类似的功能。

Random rnd = new Random(); // Much simpler, isn't it?

请记住,Random不是线程安全的;如果多个线程同时尝试访问同一个Random对象,你的随机数生成器将在其余生命周期中仅返回0。

还需要注意的一点是,连续创建多个Random对象 - 即使使用时间作为种子 - 可能会导致相同的伪随机数序列。

Random r1 = new Random();
Random r2 = new Random();
Random r3 = new Random();
Random r4 = new Random();

在上面的代码中,很有可能r1r2r3r4都会生成相同的序列。
这是怎么可能的呢?因为CPU非常快。1 GHz的CPU可以每秒执行大约10亿条指令(多少不等),也就是每1纳秒执行1条指令,或者说每1百万分之1毫秒执行1条指令。创建一个新的Random对象可能需要很多指令,但肯定不会超过100万条。
那么为什么我们需要手动定义种子呢?如果使用时钟的当前毫秒计数是我们“所有人”想要的,并且已经是默认值,为什么还需要手动定义种子呢?
因为它对于保持多个终端同步非常有用。
想象一下一个游戏,重要的现象随机出现,例如天气变化可能完全颠覆游戏。你不希望只有一方受到雾的影响,而其他人仍然从晴朗的天气中获益,对吧?
当然,你可以让服务器或主机生成随机的天气变化并通知玩家;或者你可以在游戏开始前定义一个种子,并使用该种子来确保整个游戏中所有玩家的“随机性”相同。
编程是不是很有趣呢?

支持自定义种子,但不记录用于生成输出的算法是一个愚蠢的组合。当算法在版本之间或同一API的不同实现之间发生变化时,会带来很多痛苦。 - CodesInChaos

19

你需要改变这个:

Random rnd = new Random(1000);

Random rnd = new Random();

根据Random构造函数文档

默认种子值来自于系统时钟,具有有限分辨率。因此,在短时间内通过调用默认构造函数创建的不同Random对象将具有相同的默认种子值,从而产生相同的随机数集合。可以通过使用单个Random对象生成所有随机数来避免此问题。您还可以通过修改系统时钟返回的种子值,然后显式地提供此新种子值给Random(Int32)构造函数来解决它。有关更多信息,请参见Random(Int32)构造函数。


5

关键概念是随机种子 - 初始数据,从中派生所有其他随机数据。如果种子相同,则“随机”序列也将相同。

默认情况下,种子设置为零,这显然会导致程序运行中重复的序列。

为了避免这种情况,您可以像这样构建您的Random:

Random rnd = new Random();

...在技术层面上,实际上是:

Random rnd = new Random(Environment.TickCount);

这将使用自操作系统启动以来的毫秒数初始化Random对象。每次程序启动时都会不同,因此您每次都会获得不同的随机序列。

这是.NET在默认构造函数中内部执行的操作。 - marsze
没问题。我修改了我的回答。 - Kirill Gamazkov

2

Random .Next() 方法生成伪随机数。您应该声明和初始化一个随机对象,而不是每次创建新对象。并且不需要使用任何加密技术.. :)


-1
你应该使用类级别的随机变量。如果你在方法级别上使用一个新的Random作为本地变量,那么时间相关的种子会重复生成相同的随机数序列。
class Program
{
 static Random _r = new Random();
 static void Main()
 {
// use _r variable to generate random number
 }
}

1
使用类级别的变量在这里并不重要,实际上重点是种子的被误解本质。 - doppelgreener

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