O(1)中唯一的(非重复)随机数?

Unique (non-repeating) random numbers in O(1)?

提问人:dicroce 提问时间:10/13/2008 最后编辑:Bernhard Barkerdicroce 更新时间:11/2/2019 访问量:116888

问:

我想生成 0 到 1000 之间永远不会重复的唯一随机数(即 6 不会出现两次),但这不会诉诸于对先前值的 O(N) 搜索之类的东西来做到这一点。这可能吗?

算法 数学 随机 语言 不可知

评论

4赞 jk. 10/13/2008
这不和 stackoverflow.com/questions/158716/ 的问题一样吗......
2赞 Pete Kirkham 1/20/2009
0 在 0 到 1000 之间吗?
6赞 jww 8/29/2014
如果你在恒定时间(比如时间或记忆)上禁止任何事情,那么下面的许多答案都是错误的,包括公认的答案。O(n)
0赞 Colonel Panic 12/24/2014
你会如何洗牌?
11赞 ivan_pozdeev 9/8/2016
警告!下面给出的许多答案不能产生真正的随机序列,比 O(n) 慢或有其他缺陷!codinghorror.com/blog/archives/001015.html 是你使用它们中的任何一个或尝试自己炮制之前必不可少的读物!

答:

73赞 C. K. Young 10/13/2008 #1

您可以这样做:

  1. 创建一个列表 0..1000。
  2. 随机播放列表。(参见 Fisher-Yates shuffle 了解执行此操作的好方法。
  3. 从随机列表中按顺序返回数字。

因此,这不需要每次都搜索旧值,但它仍然需要 O(N) 进行初始洗牌。但正如尼尔斯在评论中指出的那样,这是摊销的O(1)。

评论

5赞 Guvante 10/22/2008
@Just 某人 N = 1000,所以你说它是 O(N/N) 是 O(1)
1赞 Kibbee 1/4/2009
如果每次插入到洗牌数组中都是一个操作,那么在插入 1 个值后,可以得到 1 个随机值。2 表示 2 个值,依此类推,n 表示 n 个值。生成列表需要 n 次运算,因此整个算法为 O(n)。如果您需要 1,000,000 个随机值,则需要 1,000,000 次运算
3赞 Kibbee 1/4/2009
这样想吧,如果时间是恒定的,那么 10 个随机数所需的时间与 100 亿个随机数所需的时间相同。但是由于洗牌取 O(n),我们知道这不是真的。
1赞 Charles 9/25/2010
这实际上需要摊销时间 O(log n),因为您需要生成 n lg n 个随机位。
2赞 C. K. Young 5/8/2014
而现在,我有充分的理由这样做!meta.stackoverflow.com/q/252503/13
1赞 Toon Krijthe 10/13/2008 #2

另一种可能性:

您可以使用标志数组。并采取下一个当它;已经选择了。

但是,请注意,在 1000 次调用后,该功能将永远不会结束,因此您必须采取保护措施。

评论

0赞 ivan_pozdeev 9/8/2016
这个是 O(k^2),它有许多额外的步骤,平均与到目前为止选择的值的数量成正比。
267赞 Robert Gamble 10/13/2008 #3

使用值 0-1000 初始化一个包含 1001 个整数的数组,并将变量 max 设置为数组的当前最大索引(从 1000 开始)。在 0 和 max 之间选择一个随机数 r,将位置 r 处的数字与位置 max 处的数字交换,然后返回位置 max 处的数字。将最大值递减 1 并继续。当 max 为 0 时,将 max 设置回数组的大小 - 1 并重新启动,而无需重新初始化数组。

更新:虽然我在回答这个问题时自己想出了这种方法,但经过一番研究,我意识到这是费舍尔-耶茨的修改版本,称为 Durstenfeld-Fisher-Yates 或 Knuth-Fisher-Yates。由于描述可能有点难以理解,我在下面提供了一个示例(使用 11 个元素而不是 1001 个元素):

数组从初始化为 array[n] = n 的 11 个元素开始,max 从 10 开始:

+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
                                ^
                               max    

在每次迭代中,在 0 和 max 之间选择一个随机数 r,交换 array[r] 和 array[max],返回新的 array[max],并递减 max:

max = 10, r = 3
           +--------------------+
           v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 9, r = 7
                       +-----+
                       v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 8, r = 1
     +--------------------+
     v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 7, r = 5
                 +-----+
                 v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

...

经过 11 次迭代后,数组中的所有数字都已被选中,max == 0,数组元素被洗牌:

+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

此时,max 可以重置为 10,该过程可以继续。

评论

6赞 pro 1/3/2009
Jeff 关于洗牌的帖子表明,这不会返回好的随机数。codinghorror.com/blog/archives/001015.html
16赞 Brent.Longborough 1/3/2009
@Peter Rounce:我认为不是;在我看来,这类似于 Jeff 的帖子中也引用了 Fisher Yates 算法(作为好人)。
3赞 Charles 9/27/2010
@robert:我只是想指出,它不会像问题名称中那样产生“O(1)中的唯一随机数”。
3赞 Charles 9/27/2010
@mikera:同意,尽管从技术上讲,如果您使用固定大小的整数,则可以在 O(1) 中生成整个列表(具有较大的常数,即 2^32)。此外,出于实际目的,“随机”的定义很重要——如果你真的想使用系统的熵池,限制是随机位的计算,而不是计算本身,在这种情况下,n log n 又是相关的。但是,在可能的情况下,您将使用(相当于)/dev/urandom 而不是 /dev/random,您将“实际上”回到 O(n)。
4赞 Seph 12/4/2011
我有点困惑,您必须执行迭代(在本例中为 11 次)才能每次获得所需的结果,这难道不意味着它是吗?因为您需要进行迭代才能从相同的初始状态获取组合,否则您的输出将只是 N 种状态之一。NO(n)NN!
23赞 Paul de Vrieze 10/13/2008 #4

您可以使用线性同余生成器。其中(模量)是最接近大于 1000 的素数。当你得到一个超出范围的数字时,就得到下一个。只有在所有元素都发生后,该序列才会重复,并且您不必使用表。不过要注意这个生成器的缺点(包括缺乏随机性)。m

评论

1赞 Teepeemm 5/9/2014
1009 是 1000 之后的第一个素数。
0赞 ivan_pozdeev 9/8/2016
LCG 在连续数字之间具有高度相关性,因此组合总体上不会是随机的(例如,序列中相距较远的数字永远不会同时出现)。k
0赞 Max Abramovich 12/17/2016
m 应该是元素的数量 1001(1000 + 1 表示零),您可以使用 Next = (1002 * Current + 757) mod 1001;
64赞 plinth 10/15/2008 #5

使用最大线性反馈移位寄存器

它可以在几行 C 语言中实现,在运行时只做几个测试/分支、一些添加和位移。这不是随机的,但它愚弄了大多数人。

评论

12赞 f3lix 3/18/2009
“这不是随机的,但它愚弄了大多数人”。这适用于所有伪随机数生成器以及这个问题的所有可行答案。但大多数人不会考虑它。因此,省略此注释可能会导致更多的赞成票......
4赞 Ash 4/21/2013
@bobobobo:O(1)内存是原因。
3赞 Paul Hankin 4/27/2013
Nit:它是 O(log N) 内存。
2赞 tigrou 7/4/2016
使用这种方法,你如何生成数字,比如说 0 到 800000 之间?有些人可能会使用周期为 1048575 (2^20 - 1) 的 LFSR,如果数字超出范围,则使用下一个,但这不会有效。
1赞 ivan_pozdeev 9/8/2016
作为LFSR,这不会产生均匀分布的序列:将生成的整个序列由第一个元素定义。
5赞 Max 1/3/2009 #6

你甚至不需要数组来解决这个问题。

您需要一个位掩码和一个计数器。

将计数器初始化为零,并在连续调用时递增计数器。使用位掩码(在启动时随机选择或固定)对计数器进行异或运算,以生成伪随机数。如果数字不能超过 1000,请不要使用宽度超过 9 位的位掩码。(换言之,位掩码是不高于 511 的整数。

确保当计数器超过 1000 时,将其重置为零。此时,您可以选择另一个随机位掩码(如果您愿意),以不同的顺序生成相同的数字集。

评论

2赞 starblue 10/23/2009
这将比LFSR愚弄更少的人。
0赞 sellibitze 6/22/2010
512...1023 中的“位掩码”也可以。对于更多虚假的随机性,请参阅我的答案。:-)
0赞 ivan_pozdeev 9/8/2016
本质上等同于 stackoverflow.com/a/16097246/648265,也未能实现序列的随机性。
2赞 pro 1/3/2009 #7

您可以使用一个好的 10 位伪随机数生成器,然后扔掉 1001 到 1023,留下 0 到 1000。

这里我们得到一个 10 位 PRNG 的设计。

  • 10 位,反馈多项式 x^10 + x^7 + 1(周期 1023)

  • 使用 Galois LFSR 快速获取代码

评论

0赞 Nuoji 3/23/2013
@Phob 不,这不会发生,因为基于线性反馈移位寄存器的 10 位 PRNG 通常由一种结构制成,该结构假定所有值(一个值除外)一次,然后返回第一个值。换句话说,它只会在一个周期中恰好选择一次 1001。
1赞 Nuoji 4/19/2013
@Phob这个问题的重点是只选择每个数字一次。然后你抱怨 1001 不会连续发生两次?具有最佳点差的 LFSR 将以伪随机方式遍历其空间中的所有数字,然后重新开始循环。换句话说,它不用作通常的随机函数。当用作随机数时,我们通常只使用位的子集。阅读一些关于它的信息,它很快就会变得有意义。
1赞 ivan_pozdeev 9/9/2016
唯一的问题是给定的LFSR只有一个序列,因此在选择的数字之间具有很强的相关性 - 特别是,没有生成所有可能的组合。
8赞 sellibitze 6/22/2010 #8

对于像 0...1000 这样的小数字,创建一个包含所有数字的列表并对其进行洗牌是很简单的。但是,如果要从中抽取的数字集非常大,还有另一种优雅的方法:您可以使用密钥和加密哈希函数构建伪随机排列。请参阅以下 C++-ish 示例伪代码:

unsigned randperm(string key, unsigned bits, unsigned index) {
  unsigned half1 =  bits    / 2;
  unsigned half2 = (bits+1) / 2;
  unsigned mask1 = (1 << half1) - 1;
  unsigned mask2 = (1 << half2) - 1;
  for (int round=0; round<5; ++round) {
    unsigned temp = (index >> half1);
    temp = (temp << 4) + round;
    index ^= hash( key + "/" + int2str(temp) ) & mask1;
    index = ((index & mask2) << half1) | ((index >> half2) & mask1);
  }
  return index;
}

这里,只是一些任意伪随机函数,它将字符串映射到一个可能巨大的无符号整数。该函数是 0...pow(2,bits)-1 内所有数字的排列,假设一个固定键。这是从构造中得出的,因为改变变量的每个步骤都是可逆的。这是受Feistel密码的启发。hashrandpermindex

评论

0赞 ivan_pozdeev 9/11/2016
stackoverflow.com/a/16097246/648265 相同,序列的随机性失败也相同。
2赞 Ilmari Karonen 9/12/2016
@ivan_pozdeev:从理论上讲,假设计算能力无限,是的。然而,假设上面代码中使用的是一个安全的伪随机函数,这种构造将可证明(Luby & Rackoff,1988)产生伪随机排列,它不能与真正的随机随机洗牌区分开来,使用的工作量比对整个密钥空间的穷举搜索要少得多,这在密钥长度上是指数级的。即使对于合理大小的密钥(例如128位),这也超出了地球上可用的总计算能力。hash()
0赞 Ilmari Karonen 9/12/2016
(顺便说一句,为了使这个论点更严谨一些,我更愿意用 HMAC 替换上面的临时结构,而 HMAC 的安全性又可以证明降低到底层哈希压缩函数的安全性。此外,使用 HMAC 可能会降低某人错误地尝试将此结构与不安全的非加密哈希函数一起使用的可能性。hash( key + "/" + int2str(temp) )
3赞 firedrawndagger 8/26/2010 #9

这是我键入的一些代码,它们使用第一个解决方案的逻辑。我知道这是“与语言无关的”,但只是想将其作为 C# 中的一个示例,以防有人正在寻找快速实用的解决方案。

// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber];      // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber];     // Shuffled Array - not set

// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)                  
{
    OrderedArray[i] = i;
    listBox1.Items.Add(OrderedArray[i]);
}

// Execute the Shuffle                
for (int i = MaxNumber - 1; i > 0; i--)
{
    RandArrayNum = RandomClass.Next(i + 1);         // Save random #
    ShuffledArray[i] = OrderedArray[RandArrayNum];  // Populting the array in reverse
    LastNumInArray = OrderedArray[i];               // Save Last Number in Test array
    PickedNumInArray = OrderedArray[RandArrayNum];  // Save Picked Random #
    OrderedArray[i] = PickedNumInArray;             // The number is now moved to the back end
    OrderedArray[RandArrayNum] = LastNumInArray;    // The picked number is moved into position
}

for (int i = 0; i < MaxNumber; i++)                  
{
    listBox2.Items.Add(ShuffledArray[i]);
}
3赞 salva 1/20/2012 #10

当限制很高并且您只想生成几个随机数时,此方法的结果是正确的。

#!/usr/bin/perl

($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)

$last = -1;
for $i (0 .. $n-1) {
    $range = $top - $n + $i - $last;
    $r = 1 - rand(1.0)**(1 / ($n - $i));
    $last += int($r * $range + 1);
    print "$last ($r)\n";
}

请注意,数字是按升序生成的,但您可以先洗牌。

评论

0赞 ivan_pozdeev 9/11/2016
由于这会生成组合而不是排列,因此它更适合 stackoverflow.com/questions/2394246/...
1赞 ivan_pozdeev 9/11/2016
测试表明,这偏向于较低的数字:2M 样本的测量概率为 : 。我在 Python 中进行了测试,因此数学的细微差异可能会在这里发挥作用(我确实确保所有计算操作都是浮点型的)。(top,n)=(100,10)(0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635)r
0赞 salva 9/12/2016
是的,为了使此方法正常工作,上限必须远大于要提取的值的数量。
0赞 ivan_pozdeev 12/16/2017
即使“上限 [远大于值的数量]”,它也不会“正确”工作。概率仍然不均匀,只是幅度较小。
2赞 Erez Robinson 5/24/2012 #11
public static int[] randN(int n, int min, int max)
{
    if (max <= min)
        throw new ArgumentException("Max need to be greater than Min");
    if (max - min < n)
        throw new ArgumentException("Range needs to be longer than N");

    var r = new Random();

    HashSet<int> set = new HashSet<int>();

    while (set.Count < n)
    {
        var i = r.Next(max - min) + min;
        if (!set.Contains(i))
            set.Add(i);
    }

    return set.ToArray();
}

N 根据需要,非重复随机数的复杂度为 O(n)。
注意:Random 应该是静态的,并应用了线程安全。

评论

0赞 ivan_pozdeev 9/8/2016
O(n^2),因为重试次数平均与到目前为止选择的元素数量成正比。
0赞 Erez Robinson 9/9/2016
想想看,如果选择 min=0 max=10000000 和 N=5,无论选择多少次,都会重试 ~=0。但是,是的,你有一个观点,如果 max-min 很小,o(N) 就会分解。
0赞 ivan_pozdeev 9/10/2016
如果 N<<(max-min),那么它仍然是成比例的,只是系数非常小。系数对于渐近估计并不重要。
0赞 paparazzo 3/2/2017
这不是 O(n)。每次集合包含值 this is 和 extra 循环时。
32赞 Craig McQueen 4/19/2013 #12

可以使用格式保留加密来加密计数器。你的计数器只是从 0 开始,加密使用你选择的密钥将其转换为一个看似随机的值,无论你想要什么基数和宽度。例如,对于这个问题中的示例:基数 10,宽度 3。

分组密码通常具有固定的块大小,例如 64 位或 128 位。但是,格式保留加密允许您采用像 AES 这样的标准密码,并使用仍然具有加密鲁棒性的算法来制作更小宽度的密码,无论您想要什么基数和宽度。

保证永远不会发生冲突(因为加密算法会创建 1:1 映射)。它也是可逆的(2 向映射),因此您可以获取结果数字并返回到您开始的计数器值。

这种技术不需要内存来存储随机数组等,这在内存有限的系统上可能是一个优势。

AES-FFX 是实现此目的的一种建议标准方法。我已经尝试了一些基于 AES-FFX 思想的基本 Python 代码,尽管并不完全一致——请参阅此处的 Python 代码。例如,它可以将计数器加密为随机的 7 位十进制数或 16 位数字。以下是基数 10、宽度 3(给出 0 到 999 之间的数字)的示例,如问题所述:

000   733
001   374
002   882
003   684
004   593
005   578
006   233
007   811
008   072
009   337
010   119
011   103
012   797
013   257
014   932
015   433
...   ...

若要获取不同的非重复伪随机序列,请更改加密密钥。每个加密密钥都会生成不同的非重复伪随机序列。

评论

1赞 ivan_pozdeev 9/8/2016
这本质上是一个简单的映射,因此与 LCG 和 LFSR 没有任何区别,具有所有相关的扭结(例如,序列中相距超过的值永远不会同时出现)。k
0赞 Craig McQueen 9/8/2016
@ivan_pozdeev:我很难理解你评论的意思。您能解释一下这个映射有什么问题吗,什么是“所有相关的扭结”,是什么?k
0赞 ivan_pozdeev 9/9/2016
这里所有有效的“加密”就是用其他但仍然恒定的顺序中相同数字的序列替换序列。然后从这个序列中一个接一个地拉出数字。 是选择的值的数量(OP 没有为它指定一个字母,所以我不得不引入一个)。1,2,...,Nk
3赞 sh1 9/12/2016
@ivan_pozdeev FPE 不是必须实现特定的静态映射,也不是“返回的组合完全由第一个数字定义”。由于配置参数远大于第一个数字(只有一千个状态)的大小,因此应该有多个序列从相同的初始值开始,然后继续到不同的后续值。任何现实的生成器都无法覆盖整个可能的排列空间;当 OP 没有要求时,不值得提出这种故障模式。
5赞 Ilmari Karonen 9/12/2016
+1.如果实现得当,使用安全分组密码和随机统一选择的密钥,使用这种方法生成的序列在计算上将与真正的随机洗牌没有区别。也就是说,没有办法将这种方法的输出与真正的随机洗牌区分开来,这比测试所有可能的分组密码密钥并查看其中是否有任何一个生成相同的输出要快得多。对于具有 128 位密钥空间的密码,这可能超出了人类目前可用的计算能力;使用 256 位密钥,它可能会永远保持这种状态。
5赞 Tod Samay 12/20/2013 #13

您可以使用此处描述的 Xincrol 算法:

http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html

这是一种纯粹的算法方法,可以生成随机但唯一的数字,没有数组、列表、排列或繁重的 CPU 负载。

最新版本还允许设置数字范围,例如,如果我想要 0-1073741821 范围内的唯一随机数。

我实际上已经用它来

  • MP3播放器,随机播放每首歌曲,但每个专辑/目录只能播放一次
  • 像素级视频帧溶解效果(快速流畅)
  • 在签名和标记的图像上创建秘密的“噪点”雾(隐写术)
  • 数据对象 ID,用于通过数据库序列化大量 Java 对象
  • 三多数存储位保护
  • 地址+值加密(每个字节不仅被加密,而且还被移动到缓冲区中的新加密位置)。这真的让密码分析研究员对我生气了:-)
  • 纯文本到纯文本,如短信、电子邮件等的加密文本。
  • 我的德州扑克计算器 (THC)
  • 我的几款模拟游戏,“洗牌”,排名
  • 更多

它是开放的,免费的。试一试...

评论

0赞 Craig McQueen 8/14/2016
该方法是否适用于十进制值,例如将 3 位十进制计数器打乱以始终获得 3 位十进制结果?
0赞 ivan_pozdeev 9/8/2016
作为 Xorshift 算法的一个例子,它是一个 LFSR,具有所有相关的扭结(例如,序列中相距大于相距的值永远不会同时发生)。k
2赞 Myron Denson 2/22/2015 #14

下面是一些您可以使用的示例 COBOL 代码。
我可以向您发送RANDGEN.exe文件,以便您可以使用它,看看它是否确实想要您。

   IDENTIFICATION DIVISION.
   PROGRAM-ID.  RANDGEN as "ConsoleApplication2.RANDGEN".
   AUTHOR.  Myron D Denson.
   DATE-COMPILED.
  * ************************************************************** 
  *  SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
  *    ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
  *    DUPLICATIONS.  (CALL "RANDGEN" USING RANDGEN-AREA.)
  *     
  *  CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
  *    AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA     
  *
  *    FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE. 
  *    RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED 
  *    AND PASSED BACK TO YOU.
  *
  *  RULES TO USE RANDGEN:
  *
  *    RANDOM-NUMBERS-NEEDED > ZERO 
  *     
  *    COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
  *         
  *    RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
  *    WHEN COUNT-OF-ACCESSES IS ALSO = 0 
  *     
  *    RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
  *    (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)       
  *     
  *    YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
  *     THE FIRST TIME YOU USE RANDGEN.
  *     
  *    BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER > ZERO AND 
  *        RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
  *       
  *    YOU CAN LET RANDGEN BUILD A SEED FOR YOU
  *     
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER = ZERO AND 
  *        RANDOM-NUMBER-NEEDED > ZERO  
  *         
  *     TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
  *        A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
  *        RANDOM NUMBERS.
  *        COMPUTE LOW-RANGE =
  *             ((SECONDS * HOURS * MINUTES * MS) / 3).         
  *        A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
  *        AFTER RANDOM-NUMBER-BUILT IS CREATED 
  *        AND IS BETWEEN LOW AND HIGH RANGE
  *        RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
  *               
  * **************************************************************         
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
   DATA DIVISION.
   FILE SECTION.
   WORKING-STORAGE SECTION.
   01  WORK-AREA.
       05  X2-POWER                     PIC 9      VALUE 2. 
       05  2X2                          PIC 9(12)  VALUE 2 COMP-3.
       05  RANDOM-NUMBER-BUILT          PIC 9(12)  COMP.
       05  FIRST-PART                   PIC 9(12)  COMP.
       05  WORKING-NUMBER               PIC 9(12)  COMP.
       05  LOW-RANGE                    PIC 9(12)  VALUE ZERO.
       05  HIGH-RANGE                   PIC 9(12)  VALUE ZERO.
       05  YOU-PROVIDE-SEED             PIC X      VALUE SPACE.
       05  RUN-AGAIN                    PIC X      VALUE SPACE.
       05  PAUSE-FOR-A-SECOND           PIC X      VALUE SPACE.   
   01  SEED-TIME.
       05  HOURS                        PIC 99.
       05  MINUTES                      PIC 99.
       05  SECONDS                      PIC 99.
       05  MS                           PIC 99. 
  *
  * LINKAGE SECTION.
  *  Not used during testing  
   01  RANDGEN-AREA.
       05  COUNT-OF-ACCESSES            PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBERS-NEEDED        PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBER                PIC 9(12) VALUE ZERO.
       05  RANDOM-MSG                   PIC X(60) VALUE SPACE.
  *    
  * PROCEDURE DIVISION USING RANDGEN-AREA.
  * Not used during testing 
  *  
   PROCEDURE DIVISION.
   100-RANDGEN-EDIT-HOUSEKEEPING.
       MOVE SPACE TO RANDOM-MSG. 
       IF RANDOM-NUMBERS-NEEDED = ZERO
         DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
         ACCEPT RANDOM-NUMBERS-NEEDED.
       IF RANDOM-NUMBERS-NEEDED NOT NUMERIC 
         MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF RANDOM-NUMBERS-NEEDED = ZERO
         MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES NOT NUMERIC
         MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
         MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
           TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
         DISPLAY 'DO YOU WANT TO PROVIDE SEED  Y OR N: '
           NO ADVANCING
           ACCEPT YOU-PROVIDE-SEED.  
       IF RANDOM-NUMBER = ZERO AND
          (YOU-PROVIDE-SEED = 'Y' OR 'y')
         DISPLAY 'ENTER SEED ' NO ADVANCING
         ACCEPT RANDOM-NUMBER. 
       IF RANDOM-NUMBER NOT NUMERIC
         MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
         GO TO 900-EXIT-RANDGEN.
   200-RANDGEN-DATA-HOUSEKEEPING.      
       MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
       IF COUNT-OF-ACCESSES = ZERO
         COMPUTE LOW-RANGE =
                ((SECONDS * HOURS * MINUTES * MS) / 3).
       COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.  
       COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
       MOVE X2-POWER TO 2X2.             
   300-SET-2X2-DIVISOR.
       IF 2X2 < (HIGH-RANGE + 1) 
          COMPUTE 2X2 = 2X2 * X2-POWER
           GO TO 300-SET-2X2-DIVISOR.    
  * *********************************************************         
  *  IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED.    *
  * ********************************************************* 
       IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
          COMPUTE RANDOM-NUMBER-BUILT =
                ((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
       IF COUNT-OF-ACCESSES = ZERO        
         DISPLAY 'SEED TIME ' SEED-TIME 
               ' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT 
               ' LOW-RANGE  ' LOW-RANGE.          
  * *********************************************     
  *    END OF BUILDING A SEED IF YOU WANTED TO  * 
  * *********************************************               
  * ***************************************************
  * THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT  *  
  * ***************************************************   
   400-RANDGEN-FORMULA.
       COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
       DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER 
         REMAINDER RANDOM-NUMBER-BUILT. 
       IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
          RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
         GO TO 600-RANDGEN-CLEANUP.
       GO TO 400-RANDGEN-FORMULA.
  * *********************************************     
  *    GOOD RANDOM NUMBER HAS BEEN BUILT        *               
  * *********************************************
   600-RANDGEN-CLEANUP.
       ADD 1 TO COUNT-OF-ACCESSES.
       COMPUTE RANDOM-NUMBER = 
            RANDOM-NUMBER-BUILT - LOW-RANGE. 
  * *******************************************************
  * THE NEXT 3 LINE OF CODE ARE FOR TESTING  ON CONSOLE   *  
  * *******************************************************
       DISPLAY RANDOM-NUMBER.
       IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
        GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.     
   900-EXIT-RANDGEN.
       IF RANDOM-MSG NOT = SPACE
        DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
        MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER. 
        MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
       DISPLAY 'RUN AGAIN Y OR N '
         NO ADVANCING.
       ACCEPT RUN-AGAIN.
       IF (RUN-AGAIN = 'Y' OR 'y')
         GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
       ACCEPT PAUSE-FOR-A-SECOND.
       GOBACK.

评论

1赞 Mac 5/28/2018
我不知道这是否真的可以满足 OP 的需求,但 COBOL 贡献的道具!
1赞 sh1 6/2/2015 #15

这里的大多数答案都不能保证它们不会两次返回相同的数字。这是一个正确的解决方案:

int nrrand(void) {
  static int s = 1;
  static int start = -1;
  do {
    s = (s * 1103515245 + 12345) & 1023;
  } while (s >= 1001);
  if (start < 0) start = s;
  else if (s == start) abort();

  return s;
}

我不确定约束是否指定得当。人们假设在 1000 个其他输出之后允许一个值重复,但天真地允许 0 紧跟在 0 之后,只要它们都出现在 1000 个集合的末尾和开头。相反,虽然可以在重复之间保持 1000 个其他值的距离,但这样做会强制出现序列每次都以完全相同的方式重播的情况,因为在该限制之外没有发生其他值。

下面是一个方法,在可以重复一个值之前,它始终保证至少 500 个其他值:

int nrrand(void) {
  static int h[1001];
  static int n = -1;

  if (n < 0) {
    int s = 1;
    for (int i = 0; i < 1001; i++) {
      do {
        s = (s * 1103515245 + 12345) & 1023;
      } while (s >= 1001);
      /* If we used `i` rather than `s` then our early results would be poorly distributed. */
      h[i] = s;
    }
    n = 0;
  }

  int i = rand(500);
  if (i != 0) {
      i = (n + i) % 1001;
      int t = h[i];
      h[i] = h[n];
      h[n] = t;
  }
  i = h[n];
  n = (n + 1) % 1001;

  return i;
}

评论

0赞 ivan_pozdeev 9/11/2016
这是一个 LCG,就像 stackoverflow.com/a/196164/648265 一样,对于序列以及其他相关扭结都是非随机的。
0赞 sh1 9/12/2016
@ivan_pozdeev我的比 LCG 更好,因为它确保它不会在第 1001 次调用时返回重复项。
2赞 Khaled.K 4/28/2016 #16

假设你想一遍又一遍地检查洗牌的列表,而每次重新开始洗牌时都没有延迟,在这种情况下,我们可以这样做:O(n)

  1. 创建 2 个列表 A 和 B,其中 0 到 1000 占用空间。2n

  2. 使用 Fisher-Yates 随机排列列表 A 需要时间。n

  3. 抽取数字时,在另一个列表中进行 1 步费舍尔-耶茨洗牌。

  4. 当光标位于列表末尾时,切换到另一个列表。

预处理

cursor = 0

selector = A
other    = B

shuffle(A)

temp = selector[cursor]

swap(other[cursor], other[random])

if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1

return temp

评论

0赞 ivan_pozdeev 9/8/2016
没有必要保留 2 个列表 - 或者在盯着之前用尽一个列表。Fisher-Yates从任何初始状态给出均匀随机的结果。有关说明,请参阅 stackoverflow.com/a/158742/648265
0赞 Khaled.K 9/8/2016
@ivan_pozdeev 是的,这是相同的结果,但我的想法是通过使随机播放成为绘图动作的一部分来使其摊销 O(1)。
0赞 ivan_pozdeev 9/9/2016
你不明白。在再次随机播放之前,您根本不需要重置列表。洗牌将产生与洗牌相同的结果。[1,3,4,5,2][1,2,3,4,5]
1赞 Emanuel Landeholm 11/22/2016 #17

当 N 大于 1000 并且需要绘制 K 个随机样本时,可以使用包含到目前为止的样本的集合。对于每次抽奖,您都使用拒绝采样,这将是“几乎”O(1)操作,因此总运行时间接近O(K),存储为O(N)。

当 K “接近”N 时,该算法会遇到冲突。这意味着运行时间将比 O(K) 差得多。一个简单的解决方法是反转逻辑,以便对于 K > N/2,您可以记录所有尚未绘制的样本。每次抽取都会从剔除集中删除一个样本。

剔除采样的另一个明显问题是它是 O(N) 存储,如果 N 在数十亿或更多,这是个坏消息。但是,有一种算法可以解决这个问题。该算法以其发明者的名字称为 Vitter 算法。此处介绍了该算法。Vitter 算法的要点是,在每次绘制后,您使用一定的分布来计算随机跳跃,从而保证均匀采样。

评论

0赞 Emanuel Landeholm 3/7/2019
伙计们,拜托了!费舍尔-耶茨方法被打破了。您选择第一个概率为 1/N 的概率,第二个概率为 1/(N-1) != 1/N。这是一种有偏差的抽样方法!你真的需要 Vittter 算法来解决偏差。
9赞 Max Abramovich 12/17/2016 #18

我认为线性全等生成器是最简单的解决方案。

enter image description here

并且对 ACM 值只有 3 个限制

  1. mc 是相对素数,
  2. A-1 可被 m 的所有质因数整除
  3. 如果 m 能被 4 整除,则 a-1 能被 4 整除

PS 该方法已经提到过,但帖子对常量值有错误的假设。下面的常量应该适用于您的情况

在您的情况下,您可以使用 、 、a = 1002c = 757m = 1001

X = (1002 * X + 757) mod 1001
0赞 paparazzo 3/2/2017 #19

费舍尔·耶茨

for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

它实际上是 O(n-1),因为最后两个
只需要一个交换,这是 C#

public static List<int> FisherYates(int n)
{
    List<int> list = new List<int>(Enumerable.Range(0, n));
    Random rand = new Random();
    int swap;
    int temp;
    for (int i = n - 1; i > 0; i--)
    {
        swap = rand.Next(i + 1);  //.net rand is not inclusive
        if(swap != i)  // it can stay in place - if you force a move it is not a uniform shuffle
        {
            temp = list[i];
            list[i] = list[swap];
            list[swap] = temp;
        }
    }
    return list;
}

评论

0赞 paparazzo 3/2/2017
这已经有了一个答案,但它相当啰嗦,并且没有意识到您可以在 1(不是 0)处停止
3赞 Hans Olsson 7/3/2017 #20

问题 如何有效地生成 0 和上限 N 之间的 K 个非重复整数列表被链接为重复项 - 如果您想要每个生成的随机数是 O(1) 的东西(没有 O(n) 启动成本)),可以对接受的答案进行简单的调整。

创建一个空的无序映射(一个空的有序映射将每个元素采用 O(log k))从整数到整数 - 而不是使用初始化的数组。 如果这是最大值,则将 max 设置为 1000,

  1. 选择一个介于 0 和最大值之间的随机数 r。
  2. 确保映射元素 r 和 max 都存在于无序映射中。如果它们不存在,请使用等于其索引的值创建它们。
  3. 交换元素 r 和 max
  4. 返回元素 max 并将 max 递减 1(如果 max 变为负数 你完成了)。
  5. 返回步骤 1。

与使用初始化数组相比,唯一的区别是元素的初始化被推迟/跳过 - 但它将从相同的 PRNG 生成完全相同的数字。

0赞 Pavel Ruzankin 10/30/2017 #21

请在 https://stackoverflow.com/a/46807110/8794687 查看我的回答

它是最简单的算法之一,其平均时间复杂度为 O(s log s),s 表示样本量。那里还有一些指向哈希表算法的链接,其复杂度声称为 Os)。

-2赞 Grog Klingon 11/2/2019 #22

有人发帖“在excel中创建随机数”。我正在使用这个理想。 创建一个包含 2 个部分的结构,str.index 和 str.ran; 对于 10 个随机数,创建一个包含 10 个结构的数组。 将 str.index 设置为 0 到 9,将 str.ran 设置为不同的随机数。

for(i=0;i<10; ++i) {
      arr[i].index = i;
      arr[i].ran   = rand();
}

根据 arr[i].ran 中的值对数组进行排序。 str.index 现在采用随机顺序。 下面是 c 代码:

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

struct RanStr { int index; int ran;};
struct RanStr arr[10];

int sort_function(const void *a, const void *b);

int main(int argc, char *argv[])
{
   int cnt, i;

   //seed(125);

   for(i=0;i<10; ++i)
   {
      arr[i].ran   = rand();
      arr[i].index = i;
      printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
   printf("\n===================\n");
   for(i=0;i<10; ++i)
   {
      printf("arr[%d] Random  Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   return 0;
}

int sort_function(const void *a, const void *b)
{
   struct RanStr *a1, *b1;

   a1=(struct RanStr *) a;
   b1=(struct RanStr *) b;

   return( a1->ran - b1->ran );
}