如何确保随机生成的数字不会重复?

7

可能的重复问题:
如何在O(1)时间内生成唯一(不重复)的随机数?
如何高效地生成0到上限N之间K个不重复整数的列表

我想在特定范围内生成随机数,并确保每个新数字都不是以前的重复项。一种解决方案是将以前生成的数字存储在容器中,每个新数字检查容器。如果容器中有这样的数字,则我们再次生成,否则我们使用并将其添加到容器中。但是,随着每个新数字的出现,此操作变得越来越慢。是否有更好的方法或任何可以更快地工作并确保生成独特性的rand函数?

编辑:是的,有一个限制(例如从0到10亿)。但我想生成100,000个唯一数字!(如果解决方案使用Qt功能将非常好。)


1
如果每个数字都已经生成,你打算做什么?或者你生成的数字数量是固定的吗? - fredoverflow
同时,https://dev59.com/3nVC5IYBdhLWcg3w4VNy - MSalters
我认为这个问题应该重新开放。10亿是一个非常巨大的数字。通常的生成列表和洗牌方法是不可行的。到目前为止,我还没有看到其他帖子中处理如此巨大数字的适当答案。 - sellibitze
取消之前的翻译。我刚刚添加了一个:https://dev59.com/3nVC5IYBdhLWcg3wvT7g#3094476 - sellibitze
简单地按顺序返回完整的数字范围(例如0-1,000,000,000)。由于这些数字的任何(均匀)随机排列具有相同的出现可能性,因此这个顺序和其他任何顺序一样可能发生。从技术上讲,你无法证明它不是随机的 =p - bta
20个回答

0

随机生成器不可能根据先前输出的值输出值,因为它们不会是随机的。但是,您可以通过使用不同的随机值池来提高性能,每个池都有一个不同的盐值组合的值,这将把要检查的数字数量除以您拥有的池的数量。


实际上,在软件中我们只有伪随机数生成器,对于它们而言,每个生成的数字确实取决于先前生成的数字。 - sbi

0

如果随机数的范围不重要,您可以使用非常大的随机数范围,并希望您不会遇到任何碰撞。如果您的范围比您预计创建的元素数量大数十亿倍,则发生碰撞的几率很小但仍然存在。如果数字没有实际的随机分布,您可以使用由两部分组成的数字{计数器} {随机x个数字},这将确保一个唯一的数字,但其分布不是随机的。


0

在返回结果数量上,没有一个纯函数式方法不是O(n^2)的 - 每次生成一个数字时,您都需要检查到目前为止的每个结果。此外,考虑一下当您返回例如1000个中的第1000个数字时会发生什么 - 平均需要尝试1000次,直到随机算法找到最后一个未使用的数字,每次尝试需要与已生成的数字进行平均499.5次比较。

从这个例子可以清楚地看出,您发布的描述并不完全符合您的要求。更好的方法是像其他人建议的那样,提前获取一个包含1000个数字的列表,对其进行洗牌,然后逐步从该列表中返回数字。这将确保您不会返回任何重复项,并且在初始设置后以O(1)时间返回数字。


0

你可以为每个可能的数字分配一个位,为位数组分配足够的内存,并检查/设置每个生成的数字的位。例如,对于从0到65535的数字,您只需要8192(8kb)的内存。


0

这是我想出的一个有趣的解决方案:

假设你有数字1到1000,但内存不够用。

你可以把所有1000个数字放入一个数组中,然后逐个删除,但会导致内存溢出错误。

你可以将数组分成两部分,一部分是1-500的数组,另一部分为空数组。

然后你可以检查数字是否存在于数组1中,或者不存在于第二个数组中。

因此,假设你有1000个数字,你可以从1-1000中随机选择一个数字。如果它小于500,则检查数组1并在其中删除它。如果它不在数组2中,你可以添加它。

这样可以减少一半的内存使用。

如果你使用递归来推广这个方法,你可以将你的500个数组分成250和空数组。

假设空数组不占用空间,你可以大大减少内存使用。

搜索速度也会大大加快,因为如果你将其分解,你会生成一个数字,例如29。它小于500,小于250,小于125,小于62,小于31,大于15,所以你进行这6个计算,然后检查包含平均16/2个项目-总共8个的数组。

我应该申请专利这种搜索方法,尽管我打赌它已经存在了!


如果你的前500个数字都大于500,那么你的第一个列表就会因为你从未删除任何元素而变满,而你的第二个列表则会因为你添加了所有元素而变满。所以你又回到了没有足够内存的状态。 - Dennis Zickefoose

0

特别是在需要指定数值的情况下,你需要一个线性反馈移位寄存器。

为什么?

因为它不需要洗牌步骤,也不需要跟踪已经使用过的值。只要不到完整周期,你就应该没问题。

事实证明,维基百科文章提供了一些C++代码示例,这些示例比我头脑中可以给你的任何东西都要可靠。请注意,您将想要从循环内部提取值 - 循环只是通过移位寄存器进行迭代。你可以在 这里 的片段中看到它。

(是的,我知道这个重复的内容在简洁版中已经提到过了- 在修订时看到了它。考虑到它还没有在这里被提出,并且是解决发布者问题的最佳方法,我认为它应该再次提出。)


0
假设size=100.000,然后用这个大小创建一个数组。创建随机数,然后放入数组中。问题是这个数字将会在哪个索引位置?randomNumber%size可以给你索引。

当你放入下一个数字时,使用该函数来获取索引并检查该值是否存在。如果不存在,则放入;如果存在,则创建新的数字并尝试。你可以以最快的方式使用这种方法创建。这种方式的缺点是你永远无法找到末尾部分相同的数字。

例如,对于末尾部分为: 1231232444556 3458923444556

即使它们完全不同,但你的列表中永远不会有这样的数字。


0
首先,随机数和伪随机数之间存在巨大的区别。在没有引入一些物理过程(如按键之间的延迟或其他熵源)的情况下,无法从确定性过程(如计算机)中生成完全随机的数字。
保存所有生成的数字的方法会很快地减慢计算速度;您拥有的数字越多,存储需求就越大,直到填满所有可用内存。更好的方法是(正如某人已经建议的那样)使用一个众所周知的伪随机数生成器,例如线性同余生成器;它非常快速,只需要模乘和加法,其背后的理论在Knuth的TAOCP第2卷中得到了广泛的提及。这种方法保证了重复之前的相当长的周期,并且只需要使用参数和种子的存储。

PRNG的种子重复之前有一个很长的周期。输出会更频繁地重复。 - Ben Voigt
取决于您使用的生成器以及是否使用有限精度生成介于0和1之间的整数或浮点数。 LCG设置为特定地,通过适当的参数生成所有模某个模数的整数。 - user373198

0

如果一个值可以由前一个值计算出来,那么使用LFSR和LCG是没问题的。但是当你不希望一个输出值可以被另一个值计算出来时,你可以使用块密码在计数器模式下生成输出序列,前提是密码块长度等于输出长度。


-1
使用Hashset泛型类。该类不包含相同的值。您可以将所有生成的数字放入其中,然后在Hashset中使用它们。您还可以检查它是否存在。Hashset可以以最快的方式确定项目的存在。当列表变得更大时,Hashset不会变慢,这是它最大的特点。
例如:
HashSet<int> array = new HashSet<int>();
            array.Add(1);
            array.Add(2);
            array.Add(1);
            foreach (var item in array)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();

1
不错的解决方案。如果这是 OP 请求的 C++,我会给予我的 +1。 - ereOn
在我的国际象棋引擎中,我创建了一个哈希表,并且预期算法在添加新项时不应该很慢,就像List泛型类一样。然后我意识到,如果我没有记错的话,Hashset是在4.0版本中推出的。如果在C++中不存在,您也可以创建一个。 - Freshblood
我没有意识到这个问题是在C++标签下提出的 :) 现在我意识到了。 - Freshblood
1
在C++中,我们更喜欢使用堆栈对象而不是堆对象,哈希集合的拼写为std::unordered_set<>,添加元素使用其insert()成员函数完成,没有foreach循环(但是),我们通过执行std::cout << item << '\n';将行写入控制台,并使用std::getline()读取。但是除了每一行都至少包含一个错误之外,它是有效解决方案的坚实基础(随机数生成器从哪里开始呢?),所以我不会对其进行负面评价。 :) - sbi

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