【问题标题】:Random number generation algorithm随机数生成算法
【发布时间】:2015-06-25 12:53:07
【问题描述】:

我遇到了一个简单的随机数生成算法,它产生一系列数字,如下所示:

for (int i = 0; i < MAX; i++)
   if (rand.nextInt(100) >= 100 - probability) // probability is between 0 and 100
       randomNumbersList.add(i);

我想知道是否有一种方法可以在不遍历 0 到 MAX 之间的每个数字的情况下获得统计上等效的结果。

【问题讨论】:

  • 首先,这只是在probablity 和 100 之间创建一个(可能为空)“随机”数字列表。其次,您并没有真正迭代 0MAX 之间的每个数字,代码尝试MAX 次来生成一个数字。如果你想做某事 X 次,就必须在某处有一个循环。
  • @Buurman 不正确 - 它创建了一个在 0 和 MAX 之间严格递增的整数的列表,最多为 MAX 个,其中列表的平均长度由 MAX*(probability/100.0) 给出
  • 哎呀,对不起,你是对的@tucuxi。我错过了它将i 添加到列表中。谢谢指正。

标签: java algorithm random


【解决方案1】:

让我们分别表示p=probability/100q=1-p

考虑要添加的第一个数字是什么。概率q 为0;概率(1-q)*q 为1,概率(1-q)^2*q 为2,以此类推。这是geometric distribution。您可以使用以下方法轻松生成按几何分布分布的随机数:生成均匀分布在 [0,1] 中的随机数 u 并计算 x=⌊ln(u)/ln(q)⌋ — 这个 x 将具有几何分布(参见 @987654322 @)。

这就是你如何计算要添加的第一个数字的方法。

现在考虑第二个和第一个数字之间的差异。它也会呈几何分布(仅从 1 开始,而不是从 0 开始),因此您可以以相同的方式计算此差异,从而获得第二个数字,依此类推。

伪代码类似于

cur = -1
lnq = ln(q)
while true
    u = random(0,1)   // float!
    cur = cur + 1 + floor(ln(u)/lnq)
    if cur >= MAX
        break
    randomNumbersList.add(cur);

@traveh 对应的 Java 代码

List<Integer> randomNumbersList = new LinkedList<Integer>();
int cur = -1;
double p = probability / 100;
double q = 1 - p;
double lnq = Math.log(q);
Random random = new Random();
while (true) {
    double u = random.nextDouble();
    cur = cur + 1 + (int)Math.floor(Math.log(u) / lnq);
    if (cur >= MAX)
        break;
    randomNumbersList.add(cur);
}

【讨论】:

  • 也可以预先计算1/ln(q)
  • 作为一个小挑剔,你的方法偶尔会添加cur == MAX,而OP最多可以添加MAX-1
  • @traveh,我认为您应该自己判断一下,也许经过几次实验。此解决方案需要在每个时间步计算对数,因此可能比其他解决方案慢。同时,它需要对每个生成的元素进行一次随机调用,因此可能会更快。所以我建议尝试真实数据,或者甚至根据概率值在两种方法之间切换。
  • 我假设通过查找计算自然日志非常便宜 - 这几乎不会比 TreeSet 查找慢,并且也不需要内存访问。如果它是正确的(而且看起来确实如此),它比我的答案快得多
  • 经过测试并且像魅力一样工作:) @tucuxi 由于没有时间我暂时不会测试性能差异(如果我得到它,将来会更新)。我感谢你们所有人的努力,尤其是公平竞争 - 赞成你们的答案。你们真棒!
【解决方案2】:

您的算法会创建一个最多包含 MAX 个元素的列表。每个元素都是从 0 到 MAX-1 的整数,没有重复项。由于rand.nextInt(n) 返回数字 x 均匀分布在 0 和 n 之间,因此 0 x >= 100-p 在 p == 0(x 永远不会为 100)时应始终为假,并且始终如果 p == 100(x 始终 >= 0),则为真。因此,预期的元素数为 MAX*(p/100.0)。

如果MAX 很高但p 很低,这可以显着改善:在大多数情况下,你会扔你的加权硬币,但它会出现反面,你不会添加任何东西。浪费工作。但是,如果p 很高(例如,超过 0.5),那么您通常会生成按顺序排列的 MAX 元素;并且您不太可能使事情变得更快(您应该期望做 O(MAX) 工作来创建 O(MAX) 随机元素)。如果MAX 很小,则方法之间几乎没有区别 - 所以我会坚持使用更简单的方法:您已经拥有的方法。

假设大MAX 和小p

我们可以使用众所周知的binomial distribution 对列表的长度进行建模(因为您正在投掷 MAX 非公平硬币,这些硬币以p 的概率落在“正面”)。 Java 代码在Colt library 中提供。使用他们的类,这应该可以工作:

 Binomial b = new Binomial(MAX, p, new MersenneTwister());
 int heads = b.nextInt();

现在我们需要生成 0 到 MAX-1 之间的“正面”排序整数。让我们假设 MAX 比正面大得多。我们可以使用

 TreeSet<Integer> chosen = new TreeSet<>();
 for (int i=0, r=0; i<heads; i++) {
     do { r = random.nextInt(MAX) } while (chosen.contains(r));
     chosen.add(r);
 }

请注意,当p 为高时,这会产生可怕的性能,因为内部循环将被执行多次;但是对于这种情况,你的初始算法已经足够好了

p 为低时,建议的算法将需要与 MAX*(p/100) 而不是 MAX 成正比的时间。这应该足以弥补维持订购的TreeSet 的成本。

【讨论】:

  • 感谢您的详细回答!我确实忘记提及 MAX 很大而 p 很小(大约 1% 或更少)。我将对此进行测试并返回确认。
【解决方案3】:

对于每个数字,您都在确定选择是否成功(选择了数字)。因此,在MAX试验中,你拥有的数字基本上就是成功的数量。如果您可以预先确定成功的次数,那么您可以从有效范围内获得那么多唯一的随机数。它在统计上是相同的。所以你要找的是binomial distribution。使用成功概率和试验次数 (MAX) 从此分布中获取一个随机数。这将为您提供随机数的计数。然后得到这么多随机唯一数字就完成了。

【讨论】:

  • 我不认为“许多随机唯一数字”会起作用,我希望(尽管没有检查)例如,连续数字之间的差异分布会有所不同。
  • 我不同意,@Petr - 包含 0 和 MAX 之间的任何给定随机整数的概率与是否包含任何其他整数无关。只要您保持这种独立性,差异的分布将是相同的。
  • @tucuxi,好吧,我想我同意
【解决方案4】:

Petr 答案的 Java 代码:

List<Integer> randomNumbersList = new LinkedList<Integer>();
int cur = -1;
double p = probability / 100;
double q = 1 - p;
double lnq = Math.log(q);
Random random = new Random();
while (true) {
    double u = random.nextDouble();
    cur = cur + 1 + (int)Math.floor(Math.log(u) / lnq);
    if (cur >= MAX)
        break;
    randomNumbersList.add(cur);
}

【讨论】:

    猜你喜欢
    • 2010-09-24
    • 2012-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多