【问题标题】:Generating shuffled range using a PRNG rather than shuffling使用 PRNG 而不是改组生成改组范围
【发布时间】:2010-10-02 15:27:15
【问题描述】:

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

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

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

理想情况下,它的行为类似于LCG,句点和范围为n,但为LCG 选择ac 的艺术是微妙的。简单地满足 ac 在整个 LCG 周期内的约束可能会满足我的要求,但我想知道是否有更好的想法。

【问题讨论】:

  • 您要输出整个范围,还是只输出其中的几个元素?
  • 范围有多少:一开始我不知道。这是可配置的。它可能只是整个范围的一小部分,但它可能是例如10001 范围内的 10000 个项目,例如仅仅选择随机项目并测试重复项是不好的。

标签: algorithm language-agnostic random shuffle


【解决方案1】:

基于Jason's answer,我在C# 中做了一个简单直接的实现。找到大于 N 的 2 的下一个最大幂。这使得生成 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();
    }
}

【讨论】:

  • 好答案,我想实施 LCG 可能是最方便的解决方案 - 尽管我会继续关注问题,以防有其他想法。一个提示:模 M 可以简化为与,因此 (...) & (M - 1) 应该稍微更有效。
  • 此外,在测试时:您可能应该考虑使用无符号算术或 64 位算术,因为对于较大的 N,乘法 a*x 很容易溢出。
  • 我认为这段代码可能存在错误。 start 变量设置为小于 M 的随机数,但应设置为由 yield return x 返回的 x 的第一个值,以便 while (x != start) 按预期运行。很棒的代码!
【解决方案2】:

这是来自FryGuy's answerLinear Congruential Generator 的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,

【讨论】:

    【解决方案3】:

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

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

    一般LCG的周期最多为 m,并且对于一些更少的选择 比起那个来说。 LCG将有一个完整的 当且仅当:

    1. c 和 m 互质,
    2. a - 1 可以被 m 的所有素因子整除
    3. 如果 m 是 4 的倍数,a - 1 是 4 的倍数

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

    如果 n 很小,但您希望安全性很高,这可能是最棘手的情况,因为任何序列 S 的周期 N 都可能远高于 n,但导出不重复的数字序列也是不平凡的比 N 更短的周期。(例如,如果您可以获取 S mod n 的输出并保证不重复的数字序列,这将提供攻击者可能使用的有关 S 的信息)

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多