使用PRNG而不是改组来生成随机范围

时间:2009-01-21 08:49:05

标签: algorithm language-agnostic random shuffle

在给定任意种子值的情况下,是否有任何已知的算法可以在线性时间和常量空间(当迭代生成输出时)生成混洗范围[0..n]?

假设n可能很大,例如在数百万中,不需要潜在地产生每种可能的排列的要求,尤其是因为它是不可行的(种子值空间需要很大)。这也是需要恒定空间的原因。 (所以,我特别不是在寻找一种阵列混洗算法,因为这需要将范围存储在长度为n的数组中,因此会使用线性空间。)

我知道question 162606,但它没有给出这个特定问题的答案 - 从排列索引到该问题中给出的排列的映射需要一个巨大的种子值空间。

理想情况下,它会像LCG一样,其周期和范围为n,但为LCG选择ac的艺术是微妙的。简单地满足ac在整个期间LCG中的约束可能满足我的要求,但我想知道是否有更好的想法。

5 个答案:

答案 0 :(得分:6)

基于Jason's answer,我在C#中做了一个简单直接的实现。找到大于N的下一个最大二次幂。这使得生成a和c变得微不足道,因为c需要是相对素数(意味着它不能被2整除,也就是奇数),并且(a-1)需要可以被2整除,并且(a-1)需要被4整除。从统计上来说,它应该需要1-2个同余来生成下一个数字(因为2N> = M> = N)。

class Program
{
    IEnumerable<int> GenerateSequence(int N)
    {
        Random r = new Random();
        int M = NextLargestPowerOfTwo(N);
        int c = r.Next(M / 2) * 2 + 1; // make c any odd number between 0 and M
        int a = r.Next(M / 4) * 4 + 1; // M = 2^m, so make (a-1) divisible by all prime factors, and 4

        int start = r.Next(M);
        int x = start;
        do
        {
            x = (a * x + c) % M;
            if (x < N)
                yield return x;
        } while (x != start);
    }

    int NextLargestPowerOfTwo(int n)
    {
        n |= (n >> 1);
        n |= (n >> 2);
        n |= (n >> 4);
        n |= (n >> 8);
        n |= (n >> 16);
        return (n + 1);
    }

    static void Main(string[] args)
    {
        Program p = new Program();
        foreach (int n in p.GenerateSequence(1000))
        {
            Console.WriteLine(n);
        }

        Console.ReadKey();
    }
}

答案 1 :(得分:6)

以下是来自Linear Congruential GeneratorFryGuy's answer的Python实现。因为无论如何我都需要写它并认为它可能对其他人有用。

import random
import math

def lcg(start, stop):
    N = stop - start

    # M is the next largest power of 2
    M = int(math.pow(2, math.ceil(math.log(N+1, 2))))

    # c is any odd number between 0 and M
    c = random.randint(0, M/2 - 1) * 2 + 1

    # M=2^m, so make (a-1) divisible by all prime factors and 4
    a = random.randint(0, M/4 - 1) * 4 + 1

    first = random.randint(0, M - 1)
    x = first
    while True:
        x = (a * x + c) % M
        if x < N:
            yield start + x
        if x == first:
            break

if __name__ == "__main__":
    for x in lcg(100, 200):
        print x,

答案 2 :(得分:4)

听起来你想要一个算法,保证产生一个从0到n-1的循环,没有任何重复。根据您的要求,几乎可以肯定有很多这些; group theory如果你想深入研究它背后的理论,那将是最有用的数学分支。

如果你想要快速而不关心可预测性/安全性/统计模式,那么LCG可能是最简单的方法。您链接的维基百科页面包含这个(相当简单的)要求集:

  

一般LCG的期限最多   m,以及一些少得多的选择   比起那个来说。 LCG将有一个完整的   期间当且仅当:

     
      
  1. c和m是相对素数,
  2.   
  3. a - 1可被m
  4. 的所有素因子整除   如果m是4的倍数,则
  5. a - 1是4的倍数   

或者,您可以选择周期N> = n,其中N是具有方便数值属性的最小值,并且只丢弃在n和N-1之间产生的任何值。例如,最低N = 2 k - 1> = n将允许您使用linear feedback shift registers(LFSR)。或者找到您最喜欢的加密算法(RSA,AES,DES,无论如何)并给出一个特定的密钥,找出它所排列的数字的空间N,并为每个步骤应用加密一次。

如果n很小但你想要安全性很高,那可能是最棘手的情况,因为任何序列S可能有一个远高于n的周期N,但也很难得到一个非重复的数字序列比N更短的时间。(例如,如果您可以获取S mod n的输出并保证不重复的数字序列,那么将提供有关攻击者可能使用的S的信息)

答案 3 :(得分:1)

请参阅我在secure permutations with block ciphers上的文章了解一种方法。

答案 4 :(得分:1)

查看线性反馈移位寄存器,它们可以用于此。 解释它们的简短方法是从种子开始,然后使用公式

进行迭代
x = (x << 1) | f(x)

其中f(x)只能返回0或1。

如果选择一个好的函数f,x将以良好的伪随机方式循环遍历1到2 ^ n-1之间的所有值(其中n是某个数字)。 可以找到示例函数here,例如对于63个值,您可以使用

f(x) = ((x >> 6) & 1) ^ ((x >> 5) & 1)