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


180

我想生成一个介于0到1000之间的永不重复的唯一随机数(即6不会出现两次),但是这并不能像对先前值进行O(N)搜索那样进行。这可能吗?



2
0在0到1000之间吗?
Pete Kirkham,

4
如果您在固定时间内禁止执行任何操作(例如O(n)在时间或内存上),则以下许多答案是错误的,包括已接受的答案。
jww

您将如何洗牌?
Panic Panic 2014年

9
警告!下面给出的许多答案不会产生真正的随机序列,比O(n)慢或有缺陷!在您使用任何一种或尝试自己编造的书之前,codinghorror.com / blog / archives / 001015.html是必读的内容!
ivan_pozdeev 2016年

Answers:


249

使用0-1000值初始化一个1001个整数的数组,并将变量max设置为数组的当前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个元素开始,最大值从10开始:

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

每次迭代时,都会在0到max之间选择一个随机数r,将array [r]和array [max]交换,返回新的array [max],并将max减1:

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|
+--+--+--+--+--+--+--+--+--+--+--+

此时,最大可以重置为10,并且该过程可以继续。


6
杰夫对洗牌后表明,这将不会返回..好随机数codinghorror.com/blog/archives/001015.html

14
@Peter Rounce:我认为不是;在我看来,这就像费舍尔·耶茨(Fisher Yates)算法一样,在杰夫的帖子中也曾引用(作为好人)。
Brent.Longborough,

3
@robert:我只是想指出它不会产生问题,就像问题的名字一样,“ O(1)中的唯一随机数”。
查尔斯

3
@mikera:同意,尽管从技术上讲,如果您使用固定大小的整数,则整个列表可以在O(1)中生成(具有大常数,即2 ^ 32)。同样,出于实际目的,“随机”的定义也很重要-如果您确实要使用系统的熵池,则限制是对随机位的计算而不是对自身的计算,在这种情况下,n log n是相关的再次。但是在可能的情况下,您将使用(等效于)/ dev / urandom而不是/ dev / random,那么您实际上回到了O(n)。
查尔斯

4
我有些困惑,您是否必须执行N迭代(在本示例中为11),才能每次都得到期望的结果O(n)?因为您需要进行N迭代才能N!从相同的初始状态获得组合,否则输出将仅为N个状态之一。
Seph 2011年

71

你可以这样做:

  1. 创建一个列表,... 1000。
  2. 随机排列列表。(请参见Fisher-Yates随机播放,以实现此目的。)
  3. 从随机列表中按顺序返回数字。

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


5
@Just Some Guy N = 1000,所以你说的是O(N / N)就是O(1)
Guvante

1
如果将每个插入混洗后的数组的操作都作为一个操作,则在插入1个值之后,您可以获得1个随机值。2表示2个值,依此类推,n表示n个值。生成列表需要n次操作,因此整个算法为O(n)。如果您需要1,000,000个随机值,则将需要进行1,000,000次操作
Kibbee,

3
这样考虑一下,如果它是恒定时间,则10个随机数所花费的时间与100亿个时间所花费的时间相同。但是由于改组为O(n),我们知道这是不正确的。
Kibbee,

1
实际上,这需要花费摊销时间O(log n),因为您需要生成n个lg n个随机位。
查尔斯

2
现在,我有充分的理由这样做!meta.stackoverflow.com/q/252503/13
克里斯·杰斯特·杨

60

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

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


12
“这不是随机的,但它愚弄了大多数人”。这适用于所有伪随机数生成器以及该问题的所有可行答案。但是大多数人不会考虑。因此,忽略此注释可能会导致更多投票...
f3lix

3
@bobobobo:O(1)内存是为什么。
2013年

3
Nit:它是O(log N)内存。
Paul Hankin 2013年

2
使用该方法,如何生成介于0到800000之间的数字?有些人可能会使用周期为1048575(2 ^ 20-1)的LFSR,如果数字超出范围,则会得到下一个SRSR,但这不会有效。
tigrou

1
作为LFSR,这不会产生均匀分布的序列:将要生成的整个序列由第一个元素定义。
ivan_pozdeev 2016年

21

您可以使用线性同余生成器。其中m(模数)是大于1000的最接近素数。当您获得的数字超出范围时,只需获得下一个即可。仅当所有元素都发生后,序列才会重复,而您不必使用表。请注意此生成器的缺点(包括缺乏随机性)。


1
1009是1000
。– Teepeemm

LCG在连续的数字之间具有很高的相关性,因此组合在很大程度上不会是非常随机的(例如,比k序列中间隔更远的数字永远不会一起出现)。
ivan_pozdeev

m应该是元素1001的数量(1000 + 1为零),您可以使用Next =(1002 * Current + 757)mod 1001;
马克斯·阿布拉莫维奇

21

您可以使用格式保留加密来加密计数器。您的计数器仅从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
...   ...

要获得不同的非重复伪随机序列,请更改加密密钥。每个加密密钥产生一个不同的非重复伪随机序列。


这本质上是一个简单的映射,因此与LCG和LFSR没有任何区别,具有所有相关的纽结(例如k,序列中大于相除的值永远不会一起出现)。
ivan_pozdeev

@ivan_pozdeev:我很难理解您的评论的含义。您能解释一下这种映射有什么问题k吗?什么是“所有相关的纽结” ?什么是?
克雷格·麦昆

在此,所有“加密”实际上所做的就是用1,2,...,N其他但仍然恒定的顺序,用相同编号的序列替换该序列。然后将数字从该序列中一个接一个地拉出。k是选取的值的数量(OP没有为其指定字母,因此我不得不介绍一个字母)。
ivan_pozdeev

3
@ivan_pozdeev并非不是FPE必须实现特定的静态映射,也不是“返回的组合完全由第一个数字定义”。由于配置参数远大于第一个数字(只有一千个状态)的大小,因此应该有多个序列以相同的初始值开始,然后进行不同的后续值。任何现实的生成器都无法覆盖整个排列的可能空间;当OP不要求失败模式时,不值得提出该失败模式。
2013年

4
+1。如果正确实施,并使用带有均匀随机选择的密钥的安全分组密码,则使用此方法生成的序列在计算上将与真正的随机混洗没有区别。也就是说,没有比通过测试所有可能的分组密码密钥并查看它们是否产生相同输出明显更快地将这种方法的输出与真正的随机混洗区分开来的方法。对于具有128位密钥空间的密码,这可能超出了人类现有的计算能力。如果使用256位密钥,它将永远保持下去。
Ilmari Karonen

7

对于像0 ... 1000这样的低数字,创建一个包含所有数字的列表并将其改组是很简单的。但是,如果要提取的数字集非常大,则还有另一种优雅的方式:您可以使用密钥和加密哈希函数构建伪随机排列。请参见下面的C ++样例伪代码:

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;
}

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


stackoverflow.com/a/16097246/648265相同,相同序列的随机性失败。
ivan_pozdeev

1
@ivan_pozdeev:理论上,假设计算能力无限,是的。但是,假设hash()上面的代码中使用的是安全的伪随机函数,则该构造将被证明是有效的(Luby&Rackoff,1988)(伪随机排列),该伪随机排列无法与真正的随机混洗区分开来,其耗费的工作量远小于穷举。搜索整个密钥空间,密钥空间是指数长度。即使对于大小合理的密钥(例如128位),这也超出了地球上可用的总计算能力。
Ilmari Karonen

(顺便说一句,只是为了使此论据更加严格,我更喜欢hash( key + "/" + int2str(temp) )HMAC代替上面的临时结构,其安全性又可以证明可降低到底层哈希压缩函数的安全性。此外,使用HMAC可能会使不太可能有人错误地尝试将这种结构与不安全的非加密哈希函数一起使用。)
Ilmari Karonen

6

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

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

这是一种纯粹的算法方法,可以生成随机但唯一的数字,而无需数组,列表,排列或大量的CPU负载。

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

我已经用了

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

它是免费的。试试看...


该方法是否可以用于十进制值,例如将3位十进制计数器加扰以始终具有3位十进制结果?
克雷格·麦昆

作为Xorshift算法的一个示例,它是一个LFSR,具有所有相关的纽结(例如k,序列中大于零的值永远不会一起出现)。
ivan_pozdeev 2016年

5

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

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

将计数器初始化为零,并在后续调用中将其递增。将计数器与位掩码(在启动时随机选择或固定)进行异或,以生成伪随机数。如果您的数字不能超过1000,请不要使用大于9位的位掩码。(换句话说,位掩码是不大于511的整数。)

确保当计数器超过1000时,将其重置为零。此时,您可以根据需要选择另一个随机位掩码,以不同的顺序生成同一组数字。


2
与LFSR相比,这会骗更少的人。
starblue

512 ... 1023中的“位掩码”也可以。有关更多的伪随机性,请参阅我的答案。:-)
sellibitze 2010年

本质上等效于stackoverflow.com/a/16097246/648265,也无法使序列具有随机性。
ivan_pozdeev'9

4

我认为线性同余生成器将是最简单的解决方案。

在此处输入图片说明

并且acm值只有3个限制

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

PS该方法已被提及,但帖子对常量值有错误的假设。以下常量应适合您的情况

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

X = (1002 * X + 757) mod 1001

3

这是我使用第一个解决方案的逻辑输入的一些代码。我知道这是“不可知的语言”,但只是想以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

当限制较高且您只想生成几个随机数时,此方法会很合适。

#!/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";
}

请注意,数字是按升序生成的,但是您可以在此之后进行随机排序。


由于这会产生组合而不是排列,因此它更适合stackoverflow.com/questions/2394246/…–
ivan_pozdeev

1
测试表明,这偏向于较低的数字:2M样本的测量概率为 (top,n)=(100,10)是:(0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635)。我使用Python进行了测试,因此数学上的细微差异可能会在这里起作用(我确实确保所有用于计算的运算r都是浮点数)。
ivan_pozdeev

是的,为了使此方法正常工作,上限必须比要提取的值数大得多。
salva

即使“上限比值的数量大得多”,它也不会“正确”地工作。概率仍将是不均匀的,只是幅度较小。
ivan_pozdeev

2

您可以使用一个好的伪随机数生成器10位的,然后将1001丢弃到1023,将0丢弃到1000。

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

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

  • 使用Galois LFSR获得快速代码


@Phob不会,因为基于线性反馈移位寄存器的10位PRNG通常是由一个构造构成的,该构造假定所有值(一个除外)一次,然后返回第一个值。换句话说,它在一个周期内只能选择1001次。
诺基亚2013年

1
@Phob这个问题的重点是每个数字只能选择一次。然后您抱怨1001不会连续出现两次?具有最佳扩展的LFSR将以伪随机方式遍历其空间中的所有数字,然后重新开始循环。换句话说,它不用作通常的随机函数。当用作随机数时,我们通常仅使用比特的子集。阅读有关它的内容,它将很快变得有意义。
Nuoji 2013年

1
唯一的问题是给定的LFSR仅具有一个序列,因此在选取的数字之间具有很强的相关性-特别是不会生成每种可能的组合。
ivan_pozdeev

2
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应该是静态的。


O(n ^ 2),因为重试次数与到目前为止所选元素的平均数量成正比。
ivan_pozdeev 2016年

考虑一下,如果选择min = 0 max = 10000000和N = 5,则无论选择多少,都重试〜= 0。但是,是的,如果max-min很小,o(N)就会分解。
Erez Robinson

如果N <<(max-min),则它仍然是成比例的,只是系数很小。系数对于渐近估计无关紧要。
ivan_pozdeev 2016年

这不是O(n)。每当集合包含该值时,这是一个额外的循环。
狗仔队

2

假设您要一遍又一遍地浏览混洗的列表,而O(n)每次重新开始都没有延迟,请再次进行混洗,在这种情况下,我们可以这样做:

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

  2. 使用Fisher-Yates的随机播放列表A需要花费n时间。

  3. 绘制数字时,在另一个列表上进行1步Fisher-Yates随机播放。

  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

不必保留2个列表-在凝视之前用尽一个列表。Fisher-Yates从任何初始状态都给出一致的随机结果。有关说明,请参见stackoverflow.com/a/158742/648265
ivan_pozdeev 2016年

@ivan_pozdeev是的,结果是一样的,但是我的想法是通过使shuffle成为绘图动作的一部分来使其摊销O(1)。
哈立德·K,2013年

你不明白 您无需完全重置列表,然后再重新洗牌。洗牌[1,3,4,5,2]会产生相同的结果洗牌[1,2,3,4,5]
ivan_pozdeev

2

问题如何有效地生成0到上限N之间的K个非重复整数的列表将其作为重复链接-并且是否希望每个生成的随机数为O(1)(没有O(n)启动成本)),您可以对接受的答案进行简单的调整。

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

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

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


1

另一个可能性:

您可以使用标志数组。并选择下一个。

但是,要提防1000次调用后,该函数将永远不会结束,因此您必须采取保护措施。


这是O(k ^ 2),它具有与目前为止所选值的数量平均成比例的附加步骤。
ivan_pozdeev 2016年

1

这是一些示例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
我不知道这是否真的可以满足OP的需求,但可以为COBOL做出贡献的道具!
Mac

1

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

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;
}

这是一个LCG,就像stackoverflow.com/a/196164/648265一样,对于序列以及其他相关的扭结也是非随机的。
ivan_pozdeev

@ivan_pozdeev mine比LCG更好,因为它确保了在第1001次调用时不会返回重复项。
2013年

1

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

当K“接近” N时,该算法会发生冲突。这意味着运行时间将比O(K)差很多。一个简单的解决方法是颠倒逻辑,以便对于K> N / 2,保留所有尚未绘制的样本的记录。每次抽奖都会从拒绝集中删除一个样本。

拒绝采样的另一个明显问题是它是O(N)存储,如果N在数十亿或更多,这是个坏消息。但是,有一种算法可以解决该问题。该算法发明人之后被称为Vitter算法。该算法在此处描述。维特算法的要点是,每次抽奖后,您都使用一定的分布来计算随机跳跃,以保证均匀采样。


伙计们,拜托!Fisher-Yates方法已损坏。您选择概率为1 / N的第一个,概率为1 /(N-1)!= 1 / N的第二个。这是一种有偏见的采样方法!您确实需要Vittter的算法来解决偏差。
伊曼纽尔·兰德霍尔姆'19

0

费舍尔·耶茨

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;
}

已经有了一个答案,但是它缠绕了很长一段时间,无法识别您可以停在1(而不是0)
狗仔队


-1

有人张贴“在excel中创建随机数”。我正在使用这个理想。创建一个由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 );
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.