【问题标题】:Time Complexity (Big O) - Can value of N decides whether the time complexity is O(1) or O(N) when we have 2 nested FOR loops?时间复杂度 (Big O) - 当我们有 2 个嵌套的 FOR 循环时,N 的值能否决定时间复杂度是 O(1) 还是 O(N)?
【发布时间】:2021-01-06 07:00:43
【问题描述】:

假设我有 2 个嵌套的 for 循环和 1 个大小为 N 的数组,如下面的代码所示:

int result = 0;

for( int i = 0; i < N ; i++)
{
    for( int j = i; j < N ; j++)
    {
        result = array[i] + array[j]; // just some funny operation
    }
}

这里有两种情况:

(1) 如果严格的约束是 N >= 1,000,000,那么我们可以肯定地说时间复杂度是 O(N^2)。众所周知,这是真的。

(2) 现在,如果严格的约束条件是 N 用现代计算机运行和完成这 2 个循环的时间很少?听起来对吗?

请告诉我 N 的值是否在决定时间复杂度 O(N) 的结果中起作用?如果是,那么 N 值需要多大才能发挥该作用(1,000 ? 5,000 ? 20,000 ? 500,000 ?)换句话说,这里的一般经验法则是什么?


有趣的理论问题: 如果 15 年后,计算机速度如此之快,即使 N = 25,000,000,这 2 个 for 循环也可以在 1 秒内完成。那时,我们可以说即使 N = 25,000,000 时间复杂度也是 O(1) 吗?我想当时的答案是YES。你同意吗?

【问题讨论】:

  • Big-O 是关于渐近复杂性的。将它用于有界变量是没有意义的,尤其是当边界非常小时。

标签: performance optimization time computer-science


【解决方案1】:

tl:dr No. N 的值对时间复杂度没有影响。 O(1) vs O(N) 是关于“所有 N”或当 N 增加时计算量如何增加的陈述。

好问题!这让我想起了我第一次尝试理解时间复杂度的时候。我认为很多人都必须经历类似的旅程才能开始有意义,所以我希望这次讨论可以帮助其他人。

首先,您的“有趣操作”实际上比您想象的更有趣,因为您的整个嵌套 for 循环都可以替换为:

result = array[N - 1] + array[N - 1]; // just some hilarious operation hahaha ha ha

由于result 每次都会被覆盖,因此只有最后一次迭代会影响结果。我们会回到这个。

就您在这里真正要问的而言,Big-O 的目的是提供一种有意义的方式来比较算法,该方式独立于输入大小并且独立于计算机的处理速度强>。换句话说,O(1) 与 O(N) 与 N 的大小无关,也与您的计算机的“现代”程度无关。这一切都会影响算法在具有特定输入的特定机器上的 执行时间,但不会影响 时间复杂度,即 O(1) 与 O(N)。

这实际上是关于算法本身的陈述,所以数学讨论是不可避免的,正如 dxiv 在他的评论中如此亲切地提到的那样。免责声明:我将省略数学中的某些细微差别,因为关键的东西已经有很多需要解释了,我将遵从网络和教科书上其他地方的大量完整解释。

您的代码是理解 Big-O 所做 告诉我们的一个很好的例子。你写它的方式,它的复杂性是 O(N^2)。这意味着无论您在什么机器或什么时代运行代码,如果您要计算计算机必须执行的操作数,对于每个 N,并将其绘制为函数,例如 f(N),存在某个二次函数,例如 g(N)=9999N^2+99999N+999,对于所有 N,它都大于 f(N)。

但是等等,如果我们只需要找到足够大的系数以使 g(N) 成为上限,我们就不能声称该算法是 O(N) 并找到一些 g(N)= aN+b 具有足够大的系数,它的上限是 f(N)??? 这是您真正理解 BIG-O 表示法所需要了解的最重要的数学观察结果。剧透警报。答案是否定的。

对于视觉效果,请在 Desmos 上尝试此图表,您可以在其中调整系数:[https://www.desmos.com/calculator/3ppk6shwem][1]

无论您选择什么系数,aN^2+bN+c 形式的函数最终总是会超出 aN+b 形式的函数(两者都具有正 a)。你可以像 g(N)=99999N+99999 那样把一条线推到你想要的高度,但即使是函数 f(N)=0.01N^2+0.01N+0.01 也会越过这条线并在 N=9999900 之后越过它。没有线性函数是二次函数的上限。类似地,没有常数函数是线性函数或二次函数的上限。然而,我们可以找到这个 f(N) 的二次上界,例如 h(N)=0.01N^2+0.01N+0.02,所以 f(N) 在 O(N^2) 中。这种观察使我们可以只说 O(1) 和 O(N^2) 而不必区分 O(1)、O(3)、O(999)、O(4N+3)、O(23N +2), O(34N^2+4+e^N) 等。通过使用诸如“存在这样的函数”之类的短语,我们可以将所有常数系数都刷到地毯下。

因此,具有二次上界,也就是在 O(N^2) 中,意味着函数 f(N) 不大于二次,在这种情况下恰好是二次的。听起来这只是比较多项式的次数,为什么不直接说该算法是 2 次算法呢?为什么我们需要这个超级抽象“存在一个上限函数,使得 bla bla bla ...”?这是 Big-O 解释非多项式函数所必需的概括,一些常见的函数是 logN、NlogN 和 e^N。

例如,如果您的算法所需的操作数由 f(N)=floor(50+50*sin(N)) 给出,我们会说它是 O(1),因为有一个常数函数,例如g(N)=101,它是 f(N) 的上限。在此示例中,您有一些执行时间波动的奇怪算法,但您可以通过简单地说它是 O(1) 来向其他人传达对于大输入它不会减慢多少。整洁的。另外,我们有一种方法可以有意义地说,这种具有三角执行时间的算法比具有线性复杂度 O(N) 的算法更有效。整洁的。请注意计算机有多快并不重要,因为我们不是以秒为单位进行测量,而是在操作中进行测量。所以你可以在纸上手工评估算法,即使花费你一整天,它仍然是 O(1)。

至于您问题中的示例,我们知道它是 O(N^2),因为某些 a、b、c 涉及 aN^2+bN+c 操作。它不可能是 O(1),因为无论您选择什么 aN+b,我都可以找到足够大的输入大小 N,这样您的算法需要的操作不止 aN+b。在任何计算机上,在任何时区,外面都可能下雨。 O(1) 对 O(N) 对 (N^2) 没有任何物理影响。 将其更改为 O(1) 的原因是将算法本身更改为我在上面提供的单行代码,您只需添加两个数字并吐出结果,无论 N 是多少。假设 N=10 需要 4 次操作来完成数组查找、加法和变量赋值。如果您在 N=10000000 的同一台机器上再次运行它,它仍然在执行相同的 4 次操作。该算法所需的运算量不会随着 N 的增加而增加。这就是该算法为 O(1) 的原因。

这就是为什么像寻找 O(NlogN) 算法来对数组进行排序这样的问题是数学问题而不是纳米技术问题的原因。 Big-O 甚至不认为您拥有一台带电子设备的计算机。

希望这篇咆哮能给您提示您理解的内容,以便您可以更有效地学习以获得完整的理解。没有办法在这里的一篇文章中涵盖所有需要的内容。这对我来说是一次很好的灵魂探索,所以谢谢。

【讨论】:

  • 谢谢。我赞成你的回答。但是,让我稍后再详细阅读。希望其他人也能分享他们对这个话题的看法。
猜你喜欢
  • 1970-01-01
  • 2019-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-09
  • 2016-04-01
  • 2015-05-25
相关资源
最近更新 更多