关于算法:O(1)中的唯一(非重复)随机数?

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

我想生成0到1000之间的唯一随机数,这些数字永远不会重复(即6不会出现两次),但这并不需要像o(n)搜索以前的值来实现。这有可能吗?


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

更新:尽管我在回答这个问题时自己提出了这个方法,但经过一些研究,我意识到这是费希尔·耶茨的一个改进版本,被称为杜尔斯滕费尔德·费希尔·耶茨或克努斯·费希尔·耶茨。由于描述可能有点难理解,我提供了下面的示例(使用11个元素而不是1001):

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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,数组元素将随机排列:

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

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


您可以这样做:

  • 创建一个列表,0..1000。
  • 无序播放列表。(请参阅fisher yates shuffle了解一种很好的方法。)
  • 从无序排列的列表中按顺序返回数字。
  • 因此,这不需要每次搜索旧值,但它仍然需要O(N)作为初始无序排列。但正如尼尔斯在评论中指出的,这是摊销o(1)。


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

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


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


    您可以使用保留格式的加密来加密计数器。您的计数器从0开始向上,加密使用您选择的一个键将其转换为一个看似随机的值,不管您想要什么基数和宽度。例如,对于这个问题的例子:基数10,宽度3。

    块密码通常具有固定的块大小,例如64或128位。但是,保留格式的加密允许您采用标准密码(如AES)并使用一种仍具有密码学鲁棒性的算法,根据您需要的基数和宽度,生成一个较小宽度的密码。

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

    这种技术不需要内存来存储无序数组等,这在内存有限的系统中是一个优势。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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
    ...   ...

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


    对于0…1000这样的低数字,创建一个包含所有数字的列表,然后直接向前移动。但是,如果要从中提取的数字集非常大,还有另一种优雅的方法:可以使用密钥和加密散列函数构建伪随机排列。参见下面的C++-ISH示例伪代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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的步骤都是可逆的。这是受飞石密码的启发。


    您可以使用下面描述的我的xincrol算法:

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

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

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

    实际上我用它是为了

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

    它是开放的,免费的。试试看……


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

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

    将计数器初始化为零,并在连续调用时递增。xor计数器与位掩码(启动时随机选择或固定)一起生成psuedRandom数。如果数字不能超过1000,请不要使用大于9位的位掩码。(换句话说,位掩码是一个不高于511的整数。)

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


    下面是我键入的一些代码,它使用第一个解决方案的逻辑。我知道这是"语言不可知论",但我只是想在C中举个例子,以防有人在寻找快速实用的解决方案。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 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]);
    }


    这种方法在极限值较高时会产生合适的结果,您只需要生成一些随机数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/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)
    ";
    }

    请注意,这些数字是按升序生成的,但您可以在之后进行无序排列。


    比如说,你想一遍又一遍地翻阅无序的列表,而不需要每次重新开始洗牌时都延迟O(n),在这种情况下,我们可以这样做:

  • 创建2个列表a和b,0到1000,占用2n空间。

  • 使用Fisher Yates洗牌列表A,需要n时间。

  • 画一个数字时,费希尔·耶茨在另一张单子上做一步移动。

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

  • 预处理

    1
    2
    3
    4
    5
    6
    cursor = 0

    selector = A
    other    = B

    shuffle(A)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    temp = selector[cursor]

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

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

    return temp


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

    从这里我们得到了10位prng的设计。

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

    • 使用galois lfsr获取快速代码


    我认为线性同余发生器是最简单的解。

    enter image description here

    对a、c和m值只有3个限制

  • M和C是相对主要的,
  • a-1可被m的所有素数因子除
  • 如果m可被4除,则a-1可被4除
  • ps该方法已经被提到,但是post对常量值的假设是错误的。下面的常量对于您的情况应该可以正常工作

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

    1
    X = (1002 * X + 757) mod 1001

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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)复杂性。注:随机应是静态的,螺纹安全适用。


    下面是一些可以使用的COBOL代码示例。我可以给你发送randgen.exe文件,这样你就可以玩它,看看它是否需要你。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
       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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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个其他值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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;
    }


    当n大于1000并且需要随机抽取k个样本时,可以使用一个包含到目前为止样本的集合。对于每个绘图,都使用拒绝采样,这将是一个"几乎"的O(1)操作,因此总运行时间几乎是O(k)和O(n)存储。

    当k接近n时,该算法会发生碰撞,这意味着运行时间比o(k)要差得多。一个简单的解决方法是反转逻辑,这样,对于k>n/2,您就可以保存所有尚未绘制的样本的记录。每次抽取都会从拒绝集合中删除一个样本。

    拒绝抽样的另一个明显问题是它是O(N)存储,如果N在数十亿或更多,这是坏消息。然而,有一种算法可以解决这个问题。该算法以其发明者的名字命名为维特算法。这里描述了算法。维特算法的要点是,在每次绘制之后,使用一定的分布来计算一个随机跳跃,从而保证均匀采样。


    你如何有效地生成一个在0和上界n之间的k个非重复整数的列表被链接为一个副本-如果你想要的是每个生成的随机数的0(1)(没有O(n)启动成本)有一个简单的调整接受的答案。

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

  • 选择一个介于0和max之间的随机数r。
  • 确保地图元素r和max都存在于无序地图中。如果它们不存在,则使用与其索引相同的值创建它们。
  • 交换元素r和max
  • 返回元素max并将max递减1(如果max变为负数你完了。
  • 回到步骤1。
  • 与使用初始化数组相比,唯一的区别是元素的初始化被推迟/跳过,但它将从相同的prng生成完全相同的数字。


    另一种可能性:

    您可以使用一组标志。当下一个已经被选中的时候再拿它。

    但是,注意在1000次调用之后,函数将永远不会结束,因此必须进行保护。


    请参阅我的答案:https://stackoverflow.com/a/46807110/8794687

    它是最简单的算法之一,具有表示样本大小的平均时间复杂性。还有一些链接指向散列表算法,这些算法的复杂性被称为O(S)。


    费希尔耶茨

    1
    2
    3
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
    }