【问题标题】:Why is the Big-O of this algorithm N^2*log N为什么这个算法的Big-O是N^2*log N
【发布时间】:2015-02-18 20:29:31
【问题描述】:

将数组 a 从 a[0] 填充到 a[n-1]:生成随机数,直到得到一个不在先前索引中的数字。

这是我的实现:

public static int[] first(int n) {
    int[] a = new int[n];
    int count = 0;

    while (count != n) {
        boolean isSame = false;
        int rand = r.nextInt(n) + 1;

        for (int i = 0; i < n; i++) {
            if(a[i] == rand) isSame = true;
        }

        if (isSame == false){
            a[count] = rand;
            count++;
        }
    }

    return a;
}

我以为是 N^2,但显然是 N^2logN,我不确定何时考虑 log 函数。

【问题讨论】:

  • 在我看来它没有时间复杂度的上限。您可能会一直无法生成唯一编号。你能给出一个参考,它说这是 O(n^2 log n) 吗?
  • 我们说的是平均时间吗?
  • 不能说我对分析一个可怕的算法的 big-O 非常感兴趣,因为它存在一个微不足道的 O(n) 替换。
  • 这是一个教科书式的概率算法,你不会真正实现它
  • @rpattiso 如果您选择随机数的范围大大超出了您愿意创建的列表的大小,该怎么办?

标签: java algorithm big-o permutation


【解决方案1】:

0 条目立即被填充。 1 条目有1 - 1 / n = (n - 1) / n 被随机数填充的概率。所以我们平均需要n / (n - 1) 随机数来填充第二个位置。一般来说,对于k 条目,我们平均需要n / (n - k) 随机数,对于每个数字,我们需要k 比较以检查它是否唯一。

所以我们需要

n * 1 / (n - 1) + n * 2 / (n - 2) + ... + n * (n - 1) / 1

平均比较。如果我们考虑总和的右半部分,我们会看到这半部分大于

n * (n / 2) * (1 / (n / 2) + 1 / (n / 2 - 1) + ... + 1 / 1)

已知分数之和为Θ(log(n)),因为它是harmonic series。所以总和是Ω(n^2*log(n))。以类似的方式,我们可以显示总和为O(n^2*log(n))。这意味着我们平均需要

Θ(n^2*log(n))

操作。

【讨论】:

  • 现在,这是一个正确的解释。
【解决方案2】:

这类似于Coupon Collector 问题。你从 n 个项目中挑选,直到你得到一个你还没有的项目。平均而言,您有 O(n log n) 次尝试(请参阅链接,分析并非微不足道)。在最坏的情况下,您会在每次尝试中检查 n 个元素。这导致平均复杂度为 O(N^2 log N)

【讨论】:

  • 为什么?这个分析与内循环没有任何关系。外循环平均运行 O(n lg n) 次,额外的 n 因子来自内循环,无论是否中断
  • "average" "O(n lg n)" 呃,大 O 表示法适用于最坏情况而不是平均情况。 =\
  • @corsiKa - Big-O 是函数的渐近界。说函数的预期运行时是上限是完全正确的,就像您可能会限制最坏情况下的运行时一样。
  • Big-O 表示法的定义中没有任何内容表明它是最坏的情况。 Tilde notation 没有被广泛使用,实际上是更多限制; 2x 是 O(x),但不是 2x ~ x。
  • @JeroenVannevel You are wrong.
【解决方案3】:

您拥有的算法不是O(n^2 lg n),因为您拥有的算法可能会永远循环并且不会完成。想象一下,在您的第一次通过时,您获得了一些价值 $X$,而在随后的每一次通过中,试图获得第二个价值,您将永远继续获得 $X$。毕竟,我们在这里谈论的是最坏的情况。那将永远循环。因此,由于您的最坏情况永远不会结束,因此您无法真正进行分析。

如果您想知道,如果您知道 n 始终是数组的大小和值的上限,您可以这样做:

int[] vals = new int[n];
for(int i = 0; i < n; i++) {
    vals[i] = i;
}
// fischer yates shuffle
for(int i = n-1; i > 0; i--) {
   int idx = rand.nextInt(i + 1);
   int t = vals[idx];
   vals[idx] = vals[i];
   vals[i] = t;
}

一个循环下来,一个循环回来。 O(n)。很简单。

【讨论】:

  • 每次生成相同数字的随机数生成器不是随机数生成器。
  • @JackAidley 一个无法每次都生成相同数字的随机数生成器不是随机数生成器。 (但 PRNG 不会有这种行为)。
  • David 是正确的,它的现代实现不会表现得那样,但我们不能保证。毕竟,我们谈论的是最坏的情况,而不是最坏的预期情况。
【解决方案4】:

如果我没记错的话,log N部分来自这个部分:

for(int i = 0; i < count; i++){
    if(a[i] == rand) isSame = true;
}

请注意,我将 n 更改为 count,因为您知道每个循环的数组中只有 count 元素。

【讨论】:

  • 你需要解释原因。
  • 这意味着原始算法实际上是 N³
  • @rpattiso : dfb 在下面的回答中几乎解释了这一点。
  • @ManuelRamírez,确实,现在没有理由修改,但你发布了,我在此之前发表了评论。
猜你喜欢
  • 1970-01-01
  • 2021-11-11
  • 2020-01-23
  • 2015-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多