【问题标题】:Efficient algorithm for generating unique (non-repeating) random numbers生成唯一(非重复)随机数的高效算法
【发布时间】:2018-11-13 03:36:39
【问题描述】:

我想解决以下问题。我必须在一个非常大的集合中进行采样,大约 10 ^ 20 的数量级,并在不重复大小约 10%-20% 的情况下提取样本。鉴于集合的大小,我认为像 Fisher-Yates 这样的算法是不可行的。

我在想像随机路径树这样的东西可能会在 O(n log n) 内完成它并且不能更快地完成,但我想问一下这样的东西是否已经实现了。

感谢您的宝贵时间!

【问题讨论】:

  • 您真的想对 10^19 到 2 * 10^19 件商品进行采样吗?你有几百万TB的存储空间?
  • @gasher729:时间也是一个因素。 10^19 纳秒是三百年多一点。这表明需要大规模并行算法。
  • 是的。我可以使用超级计算机,但我们必须很好地计划这次执行。我已经尝试在较小的相同类型的集合上进行替换采样,并且正如预期的那样,它不起作用。分发本身是非标准的。幸运的是,存储不是问题,我们实时生成对象并将结果记录在有界大小的哈希表中,然后我们可以摆脱它们。
  • 另一个选项是Linear Feedback Shift Register,可以配置为以伪随机顺序生成从1到N的所有值,不重复。

标签: algorithm sorting sampling resampling


【解决方案1】:

我不知道我在下面描述的技术在随机性的正式测试中表现如何,但它确实给出了“看起来很随机”的结果。

您可以使用multiplicative inverse 来完成此操作。这个想法是您使用数学函数将 1-N 范围内的每个整数映射到同一范围内的唯一整数。这通常用于生成混淆密钥,但您可以通过更改种子值和从中提取项目的范围来调整它以生成随机子集。

不久前,我写了一篇 blog entry 关于如何生成混淆顺序密钥的文章。代码如下:

private void DoIt()
{
    const long m = 101;         // Number of keys + 1
    const long x = 387420489;   // must be coprime to m

    // Compute the multiplicative inverse
    var multInv = MultiplicativeInverse(x, m);

    // HashSet is used to hold the obfuscated value so we can ensure that no duplicates occur.
    var nums = new HashSet<long>();

    // Obfuscate each number from 1 to 100.
    // Show that the process can be reversed.
    // Show that no duplicates are generated.
    for (long i = 1; i <= 100; ++i)
    {
        var obfuscated = i * x % m;
        var original = obfuscated * multInv % m;
        Console.WriteLine("{0} => {1} => {2}", i, obfuscated, original);
        if (!nums.Add(obfuscated))
        {
            Console.WriteLine("Duplicate");
        }
    }    
}

private long MultiplicativeInverse(long x, long modulus)
{
    return ExtendedEuclideanDivision(x, modulus).Item1 % modulus;
}

private static Tuple<long, long> ExtendedEuclideanDivision(long a, long b)
{
    if (a < 0)
    {
        var result = ExtendedEuclideanDivision(-a, b);
        return Tuple.Create(-result.Item1, result.Item2);
    }
    if (b < 0)
    {
        var result = ExtendedEuclideanDivision(a, -b);
        return Tuple.Create(result.Item1, -result.Item2);
    }
    if (b == 0)
    {
        return Tuple.Create(1L, 0L);
    }
    var q = a / b;
    var r = a % b;
    var rslt = ExtendedEuclideanDivision(b, r);
    var s = rslt.Item1;
    var t = rslt.Item2;
    return Tuple.Create(t, s - q * t);
}

该程序的前几行输出是:

1 => 43 => 1
2 => 86 => 2
3 => 28 => 3
4 => 71 => 4
5 => 13 => 5
6 => 56 => 6
7 => 99 => 7
8 => 41 => 8
9 => 84 => 9
10 => 26 => 10

如果您要更改函数开头的 mx 值以反映您的数字范围,这将适合您。与其总是从 1 开始,然后抓住前 10% 或 20%,不如从 50% 开始,然后从那里开始。或者使用某种技术来抓取每五个数字,或者其他什么,只要您的方法不会两次访问同一个数字。

如果您需要更多运行,只需更改 x 值。

生成乘法逆(将其视为随机数生成器的种子)是一个 O(log n) 操作。之后,生成每个数字是 O(1)。

当然,如果您使用 10^20 范围内的数字,则必须修改代码以使用大整数类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-24
    • 1970-01-01
    • 2021-08-29
    • 2011-11-24
    • 2013-04-06
    相关资源
    最近更新 更多