【问题标题】:How to ensure that randomly generated numbers are not being repeated? [duplicate]如何确保随机生成的数字不重复? [复制]
【发布时间】:2011-03-06 18:59:33
【问题描述】:

可能的重复:
Unique (non-repeating) random numbers in O(1)?
How do you efficiently generate a list of K non-repeating integers between 0 and an upper bound N

我想在某个音调中生成随机数,我必须确定,每个新数字都不是前者的重复。一种解决方案是将以前生成的号码存储在容器中,并且每个新号码都会检查容器。如果容器中有这样的数字,那么我们生成 agin,否则我们使用并将其添加到容器中。但是随着每个新的数字,这个操作变得越来越慢。有没有更好的方法,或者任何 rand 函数可以更快地工作并确保生成的唯一性?

编辑:是的,有一个限制(例如从 0 到 1.000.000.000)。但我想生成 100.000 个唯一数字! (如果解决方案是使用 Qt 功能,那就太好了。)

【问题讨论】:

  • 如果每个数字都生成了,你打算怎么做?或者您生成的数字是否固定?
  • 我认为应该重新提出这个问题。 1.000.000.000 是一个相当大的数字。通常的生成列表和随机播放方法是不可行的。到目前为止,我还没有在其他适当处理如此庞大数字的线程中看到答案。
  • 简单地按顺序返回完整的数字范围(例如 0-1,000,000,000)。由于这些数字的任何(均匀)随机排列具有相同的发生可能性,因此该顺序与任何其他顺序一样可能。从技术上讲,你无法证明它不是随机的 =p

标签: c++ qt random


【解决方案1】:

随机数有范围吗?如果您对随机数有限制并且不断生成唯一的随机数,那么您最终会得到一个以随机顺序排列的 x..y 中所有数字的列表,其中 x-y 是您的随机数的有效范围。如果是这种情况,您可以通过简单地生成所有数字 x..y 的列表并将其改组而不是生成数字来大大提高速度。

【讨论】:

    【解决方案2】:

    我认为有 3 种可能的方法,取决于范围大小和所需的性能模式,您可以使用另一种算法。

    1. 创建一个随机数,看看它是否在(排序的)列表中。如果不添加并返回,则尝试另一个。
      • 您的列表会随着您需要的每个数字而增长并消耗内存。如果每个数字都是 32 位,那么它每次都会增长至少 32 位。
      • 每个新的随机数都会增加命中率,这会使其变慢。
      • O(n^2) - 我认为
    2. 为范围内的每个数字创建一个位数组。如果已经返回,用 1/True 标记。
      • 现在每个数字只占用 1 位,如果范围很大,这仍然是个问题,但每个数字现在只分配 1 位。
      • 每个新的随机数都会增加命中率,这会使其变慢。
      • O(n*2)
    3. 用所有数字预先填充一个列表,将其随机排列,然后返回第 N 个数字。
      • 列表不会增长,返回数字不会变慢,
      • 但生成列表可能需要很长时间,并且需要大量内存。
      • O(1)

    根据所需的速度,您可以将所有列表存储在数据库中。除了速度之外,它们不需要在内存中。

    【讨论】:

      【解决方案3】:

      用你需要的数字填写一个列表,然后随机排列列表并从一端选择你的数字。

      【讨论】:

      • 如果他需要 32 位数字就不太实用。
      • 无论如何他都需要一个所有使用过的数字的索引。
      • 是的,但如果这确实是他的需要,他应该从 1 开始并不断向上迭代,同时至少在理论上接受这一点,这与任何其他顺序一样随机 ;-)
      • @shoosh:bits 的数量并不重要,重要的是 numbers 的数量。 (如果我需要 5 个 32 位数字,为什么会有问题?)
      • @kasperjj 那些是随机的.. 直到你说出来
      【解决方案4】:

      如果你使用一个简单的 32 位线性同余 RNG(比如所谓的"Minimal Standard"),你所要做的就是存储你使用的种子值,并将每个生成的数字与它进行比较。如果你再次达到那个值,你的序列就会开始重复,你就失去了价值。这是 O(1),但当然限于 2^32-1 个值(尽管我想你也可以使用 64 位版本)。

      【讨论】:

      • 我个人认为这是最好的答案,原因有两个:1)它在时间和空间方面是最有效的,2)它表明解决方案是使用随机数生成器。
      【解决方案5】:

      我相信有一类伪随机数生成器具有您想要的属性:Linear congruential generator。如果定义正确,它将生成一个从 0 到 N-1 的整数列表,在您使用列表中的所有数字一次之前,不会有两个数字重复。

      #include <stdint.h>
      
      /*
       * Choose these values as follows:
       *
       * The MODULUS and INCREMENT must be relatively prime.
       * The MULTIPLIER-1 must be divisible by all prime factors of the MODULUS.
       * The MULTIPLIER-1 must be divisible by 4, if the MODULUS is divisible by 4.
       *
       * In addition, modulus must be <= 2**32 (0x0000000100000000ULL).
       *
       * A small example would be 8, 5, 3.
       * A larger example would be 256, 129, 251.
       * A useful example would be 0x0000000100000000ULL, 1664525, 1013904223.
       */
      
      #define MODULUS    (0x0000000100000000ULL)
      #define MULTIPLIER (1664525)
      #define INCREMENT  (1013904223)
      
      static uint64_t seed;
      
      uint32_t lcg( void ) {
          uint64_t temp;
      
          temp = seed * MULTIPLIER + INCREMENT;   // 64-bit intermediate product
          seed = temp % MODULUS;                  // 32-bit end-result
      
          return (uint32_t) seed;
      }
      

      您所要做的就是选择一个 MODULUS,使其大于您在给定运行中所需的数字数量。

      【讨论】:

        【解决方案6】:

        如果有这样的模式就不会随机了?

        据我所知,您必须存储和过滤所有不需要的号码...

        【讨论】:

          【解决方案7】:
          unsigned int N = 1000;
          vector <unsigned int> vals(N);
          for(unsigned int i = 0; i < vals.size(); ++i)
             vals[i] = i;
          std::random_shuffle(vals.begin(), vals.end());
          
          unsigned int random_number_1 = vals[0];
          unsigned int random_number_2 = vals[1];
          unsigned int random_number_3 = vals[2];
          //etc
          

          【讨论】:

            【解决方案8】:

            您可以将数字存储在向量中,并通过索引 (1..n-1) 获取它们。每次随机生成后,从向量中删除索引号,然后在区间 1..n-2 中生成下一个数字。等等

            【讨论】:

              【解决方案9】:

              如果它们不能重复,它们就不是随机的。

              编辑:

              此外..

              如果它们不能重复,那么它们就无法放入有限的计算机中

              【讨论】:

              • 这实际上不是真的。您可以将“随机”定义为“不包括最后一个数字的一​​组数字中的随机数”
              • 你甚至可以重新定义redefine这个词。
              • @Hernán:所以随机性是有限的。然而,这并不意味着它不再是随机的。随机数中的位数也限制了它们的随机性。不过,你不会说它们不是随机的,因为你不能有超过 2^16 个数字。
              • @sbi,“不能重复”和“范围有限”是有区别的,我有一些旧的(六面,有限)骰子,如果数量多的话,它们还在工作不能重复,很可能一周内我都不能再使用它们了,无论如何你当然是对的,有限的范围可能是随机的,我提到有限的计算机有点讽刺
              • 洗牌。现在不用看,你会说套牌中的顶牌是随机的吗?最底层的?当然,两者都是随机的。如果您偷看最上面的牌,这不会改变,因为顺序保持不变,因此仍然是随机的。
              【解决方案10】:

              你需要多少个随机数?也许您可以将shuffle algorithm 应用于预先计算的随机数数组?

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                • 实际上,在软件中我们只有伪随机数生成器,而对它们而言,每个生成的数字确实取决于之前生成的数字。
                【解决方案12】:

                如果随机数的范围无关紧要,您可以使用非常大范围的随机数,并希望您不会遇到任何冲突。如果您的范围比您期望产生碰撞的元素数量大数十亿倍,那么您的碰撞机会很小但仍然存在。如果数字不具有实际的随机分布,您可以有一个两部分的数字 {counter}{random x digits},这将确保一个唯一的数字,但它不会随机分布。

                【讨论】:

                  【解决方案13】:

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

                  从这里应该清楚的是,您发布的描述并不完全是您想要的。正如其他人所说,更好的方法是列出例如预先输入 1000 个数字,将其随机排列,然后逐步从该列表中返回数字。这将保证您不会返回任何重复项,并在初始设置后在 O(1) 时间内返回数​​字。

                  【讨论】:

                    【解决方案14】:

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

                    【讨论】:

                      【解决方案15】:

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

                      假设您有数字 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,那么您最终会得到第一个列表已满,因为您从未删除任何元素,而您的第二个列表已满,因为您添加了所有元素。所以你又回到了没有足够的内存。
                      【解决方案16】:

                      特别是给定所需数量的值,您需要一个线性反馈移位寄存器。

                      为什么?

                      没有随机播放步骤,也不需要跟踪您已经点击的值。只要你去的时间少于整个期间,你应该没问题。

                      事实证明,Wikipedia article 有一些 C++ 代码示例,这些示例比我想告诉你的任何东西都经过测试。请注意,您需要从循环内部提取值——循环只是迭代移位寄存器。您可以在 sn-p here 中看到这一点。

                      (是的,我知道有人提到了这一点,只是在骗局中提到过——在我修改的时候看到了。鉴于它没有在这里提出并且是解决发帖人问题的最佳方法,我认为应该是再次提起。)

                      【讨论】:

                        【解决方案17】:

                        假设 size=100.000 然后创建一个具有此大小的数组。创建随机数,然后将它们放入数组中。问题是该数字将是哪个索引? randomNumber%size 会给你索引。

                        当你输入下一个数字时,使用该函数作为索引并检查该值是否存在。如果不存在,则将其放入,如果存在则创建新号码并尝试。你可以用这种方式以最快的方式创建。这种方式的缺点是你永远找不到最后一节相同的数字。

                        例如最后几节是 1231232444556 3458923444556

                        您的列表中永远不会出现这样的数字,即使它们完全不同但最后部分相同。

                        【讨论】:

                          【解决方案18】:

                          首先,随机和伪随机之间存在巨大差异。如果不引入一些物理过程,例如击键之间的延迟或其他熵源,就无法从确定性过程(例如计算机)生成完全随机数。

                          保存所有生成的数字的方法会很快减慢计算速度;您拥有的数字越多,您的存储需求就越大,直到您填满所有可用内存。更好的方法是(正如有人已经建议的那样)使用众所周知的伪随机数生成器,例如Linear Congruential Generator;它超级快,只需要模乘和加法,它背后的理论在 Vol. 2 中得到了很多提及。 2 Knuth 的 TAOCP。这样,所涉及的理论在重复之前保证了相当长的时间,并且唯一需要的存储是使用的参数和种子。

                          【讨论】:

                          • 在 PRNG 的 seed 重复之前有很长一段时间。输出会更频繁地重复。
                          • 取决于您使用的生成器,以及您生成的整数或浮点数是否在 0 和 1 之间且精度有限。 LCG 专门设置为使用适当的参数生成所有以某个模数为模的整数。
                          【解决方案19】:

                          如果前一个可以计算出一个值没有问题,LFSR和LCG都可以。当您不希望一个输出值可以被另一个计算时,您可以使用counter mode 中的分组密码来生成输出序列,假设密码块长度等于输出长度。

                          【讨论】:

                            【解决方案20】:

                            使用 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();
                            

                            【讨论】:

                            • 不错的解决方案。如果这是 OP 要求的 C++,我会给出 +1。
                            • 在我的国际象棋引擎中,我创建了一个哈希表,并且在将新项目添加为 List 泛型类时,正如预期的那样,算法应该不会很慢。然后我意识到 Hashset 附带 4.0,如果我没记错的话。如果在 c++ 中不存在,您也可以创建一个。
                            • 我还没有意识到在 c++ 标签中提出的这个问题 :) 现在我意识到了。
                            • 在 C++ 中,我们更喜欢堆栈对象而不是堆对象,哈希集拼写为 std::unordered_set&lt;&gt;,添加到它是使用其 insert() 成员函数完成的,没有 foreach 循环(目前),我们通过std::cout &lt;&lt; item &lt;&lt; '\n'; 向控制台写入行并使用std::getline() 读取。但除了包含至少一个错误的每一行之外,它是有效解决方案的坚实基础(但随机数生成器从何而来?),所以我不会反对它。 :)
                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 2017-05-23
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-11-21
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多