【问题标题】:What is a plain English explanation of "Big O" notation?“Big O”符号的简单英文解释是什么?
【发布时间】:2010-10-03 23:43:44
【问题描述】:

我希望尽可能少的正式定义和简单的数学。

【问题讨论】:

  • 总结:算法复杂度的上限。另请参阅类似的问题Big O, how do you calculate/approximate it? 以获得很好的解释。
  • 其他答案都很好,只需一个细节即可理解:O(log n) 或类似的意思,它取决于输入的“长度”或“大小”,而不是价值本身。这可能很难理解,但非常重要。例如,当您的算法在每次迭代中将事物一分为二时,就会发生这种情况。
  • 在麻省理工学院“计算机科学与编程概论”课程youtube.com/watch?v=ewd7Lf2dr5Q987654322@的第8讲中有一个专门针对算法复杂性的讲义,它不是完全通俗易懂的英语,但通过示例给出了很好的解释很容易理解。
  • Big O 是对函数最坏情况性能的估计,假设算法将执行最大迭代次数。

标签: algorithm complexity-theory computer-science big-o time-complexity


【解决方案1】:

快速说明,我的回答几乎肯定会混淆 Big Oh notation(这是一个上限)和 Big Theta 符号“Θ”(这是一个两侧的边界)。但根据我的经验,这实际上是非学术环境中的典型讨论。对于造成的任何混淆,我们深表歉意。


BigOh 复杂度可以用这张图来可视化:

我可以为 Big Oh 符号给出的最简单定义是:

Big Oh 符号是算法复杂度的相对表示。

那句话中有一些重要的和故意选择的词:

  • 相对:您只能将苹果与苹果进行比较。您无法将进行算术乘法的算法与对整数列表进行排序的算法进行比较。但是比较两种算法来进行算术运算(一个乘法,一个加法)会告诉你一些有意义的事情;
  • 表示: BigOh(最简单的形式)将算法之间的比较简化为单个变量。该变量是根据观察或假设选择的。例如,排序算法通常基于比较操作(比较两个节点以确定它们的相对顺序)进行比较。这假设比较是昂贵的。但是,如果比较便宜但交换很昂贵怎么办?它改变了比较;和
  • 复杂性:如果我需要一秒钟来对 10,000 个元素进行排序,那么我需要多长时间才能对 100 万个元素进行排序?在这种情况下,复杂性是相对于其他事物的衡量标准。

阅读完其余部分后,请返回并重新阅读以上内容。

我能想到的 BigOh 最好的例子是做算术。取两个数字(123456 和 789012)。我们在学校学到的基本算术运算是:

  • 补充;
  • 减法;
  • 乘法;和
  • 师。

每一个都是一个操作或一个问题。解决这些问题的方法称为算法

加法是最简单的。您将数字排成一行(向右)并将数字添加到一列中,在结果中写入该加法的最后一个数字。该数字的“十”部分将转移到下一列。

假设这些数字相加是该算法中最昂贵的操作。理所当然地,要将这两个数字相加,我们必须将 6 个数字相加(并且可能带有第 7 个数字)。如果我们将两个 100 位数字相加,我们必须进行 100 次加法。如果我们将 两个 10,000 位数字相加,我们必须进行 10,000 次相加。

看到模式了吗? 复杂度(即操作数)与较大数字中的n 位数成正比。我们称之为 O(n)线性复杂度

减法是类似的(除了你可能需要借而不是进位)。

乘法不同。您将数字排成一行,取底部数字中的第一个数字,然后将其与顶部数字中的每个数字依次相乘,依此类推。因此,要将我们的两个 6 位数字相乘,我们必须进行 36 次乘法。我们可能还需要添加多达 10 或 11 列才能获得最终结果。

如果我们有两个 100 位数字,我们需要进行 10,000 次乘法和 200 次加法。对于两个 100 万位数字,我们需要进行 1 万亿 (1012) 次乘法和 200 万次加法。

当算法以 n-平方 缩放时,这是 O(n2)二次复杂度 .现在是介绍另一个重要概念的好时机:

我们只关心复杂性中最重要的部分。

精明的人可能已经意识到我们可以将操作数表示为:n2 + 2n。但正如您从我们的示例中看到的,每个数字都有两个百万位数,第二项 (2n) 变得微不足道(占该阶段总操作的 0.0002%)。

可以注意到,我们在这里假设了最坏的情况。在乘以 6 位数字时,如果其中一个有 4 位,另一个有 6 位,那么我们只有 24 次乘法。尽管如此,我们还是计算了那个“n”的最坏情况,即当两者都是 6 位数字时。因此,Big Oh 表示法是关于算法的最坏情况。

电话簿

我能想到的下一个最好的例子是电话簿,通常称为白页或类似名称,但它因国家/地区而异。但我说的是按姓氏,然后是首字母或名字,可能是地址,然后是电话号码的人。

现在,如果您让计算机在包含 1,000,000 个姓名的电话簿中查找“John Smith”的电话号码,您会怎么做?忽略你可以猜到 S 开始多远的事实(假设你不能),你会怎么做?

一个典型的实现可能是打开中间,取 500,000th 并将其与“Smith”进行比较。如果它恰好是“史密斯,约翰”,我们真的很幸运。更有可能的是,“John Smith”将出现在该名称之前或之后。如果是在我们之后将电话簿的后半部分分成两半并重复。如果在此之前,我们将电话簿的前半部分分成两半并重复。以此类推。

这称为二分搜索,无论您是否意识到,每天都会在编程中使用它。

因此,如果您想在包含一百万个名字的电话簿中找到一个名字,您实际上可以通过最多 20 次找到任何名字。在比较搜索算法时,我们认为这种比较是我们的“n”。

  • 对于包含 3 个姓名的电话簿,需要进行 2 次比较(最多)。
  • 7 个最多需要 3 个。
  • 15 个需要 4 个。
  • 1,000,000 需要 20。

这真是太棒了,不是吗?

在 BigOh 术语中,这是 O(log n)对数复杂度。现在有问题的对数可能是 ln(以 e 为底)、log10、log2 或其他一些底数。没关系,它仍然是 O(log n),就像 O(2n2) 和 O(100n2) 仍然都是 O(n2)。

此时值得解释一下,BigOh 可用于通过算法确定三种情况:

  • 最佳情况:在电话簿搜索中,最好的情况是我们在一次比较中找到姓名。这是 O(1)恒定复杂度
  • 预期情况:如上所述,这是 O(log n);和
  • 最坏情况:这也是 O(log n)。

通常我们不关心最好的情况。我们对预期和最坏的情况感兴趣。有时其中一个或另一个会更重要。

回到电话簿。

如果您有电话号码并想查找姓名怎么办?警方有一个反向电话簿,但公众拒绝进行此类查找。还是他们?从技术上讲,您可以反向查找普通电话簿中的号码。怎么样?

您从名字开始并比较数字。如果这是一场比赛,很好,如果不是,你继续下一场。您必须这样做,因为电话簿是无序(无论如何都是电话号码)。

所以要找到一个给定电话号码的名字(反向查找):

  • 最佳情况: O(1);
  • 预期情况: O(n)(对于 500,000);和
  • 最坏情况: O(n)(1,000,000)。

旅行推销员

这是计算机科学中相当著名的问题,值得一提。在这个问题中,你有 N 个城镇。这些城镇中的每一个都通过一定距离的道路与一个或多个其他城镇相连。旅行推销员问题是找到访问每个城镇的最短路线。

听起来很简单?再想一想。

如果你有 3 个城镇 A、B 和 C,所有对之间都有道路,那么你可以去:

  • A → B → C
  • A → C → B
  • B → C → A
  • B → A → C
  • C → A → B
  • C → B → A

嗯,实际上比这要少,因为其中一些是等价的(例如,A → B → C 和 C → B → A 是等价的,因为它们使用相同的道路,只是相反)。

实际上,有3种可能。

  • 把它带到 4 个城镇,你就有 (iirc) 12 种可能性。
  • 5 等于 60。
  • 6 变成 360。

这是一个称为阶乘的数学运算的函数。基本上:

  • 5! = 5 × 4 × 3 × 2 × 1 = 120
  • 6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
  • 7! = 7 × 6 × 5 × 4 × 3 × 2 × 1 = 5040
  • 25! = 25 × 24 × … × 2 × 1 = 15,511,210,043,330,985,984,000,000
  • 50! = 50 × 49 × … × 2 × 1 = 3.04140932 × 1064

所以旅行商问题的 BigOh 是 O(n!)阶乘或组合复杂度

当你到达 200 个城镇时,宇宙中已经没有足够的时间来解决传统计算机的问题了。

想一想。

多项式时间

我想快速提及的另一点是,任何具有 O(na) 复杂度的算法都被称为具有多项式复杂度 或可在 多项式时间内解决

O(n), O(n2) 等都是多项式时间。有些问题不能在多项式时间内解决。正因为如此,世界上使用了某些东西。 Public Key Cryptography 就是一个很好的例子。在计算上很难找到两个非常大的素数。如果不是,我们就无法使用我们使用的公钥系统。

无论如何,这就是我对 BigOh 的(希望是简单的英语)解释(修订版)。

【讨论】:

  • 而其他答案侧重于解释 O(1)、O(n^2) 等人之间的区别……您的答案详细说明了算法如何分类为 n^2 , nlog(n) 等 +1 以获得一个很好的答案,帮助我理解大 O 符号
  • 有人可能想补充一点,big-O 表示上限(由算法给出),big-Omega 给出下限(通常作为独立于特定算法的证明给出)和 big- Theta 表示达到该下限的“最佳”算法是已知的。
  • 如果您正在寻找最长的答案,这很好,但不适用于以简单方式最好地解释 Big-O 的答案。
  • -1:这是明显错误的:_“BigOh 是算法复杂度的相对表示”。不,BigOh 是一个渐近上界,并且完全独立于计算机科学而存在。 O(n) 是线性的。不,您将 Bi​​gOh 与 theta 混淆了。日志 n 是 O(n)。 1 是 O(n)。对这个答案(和 cmets)的支持数,这使得将 Theta 与 BigOh 混淆的基本错误非常令人尴尬......
  • “当你到达 200 个城镇时,宇宙中已经没有足够的时间来解决传统计算机的问题了。” 当宇宙即将结束时?
【解决方案2】:

它显示了算法如何根据输入大小进行缩放。

O(n2):称为二次复杂度

  • 1 项:1 次操作
  • 10 项:100 次操作
  • 100 项:10,000 次操作

请注意,项目数增加了 10 倍,但时间增加了 10 倍2。基本上,n=10 等 O(n2) 为我们提供了缩放因子 n2,即 102

O(n):称为线性复杂度

  • 1 项:1 秒
  • 10 项:10 秒
  • 100 项:100 秒

这次项目的数量增加了 10 倍,时间也是如此。 n=10,因此 O(n) 的比例因子为 10。

O(1):称为恒定复杂度

  • 1 项:1 次操作
  • 10 项:1 次操作
  • 100 项:1 次操作

项目的数量仍然增加了 10 倍,但 O(1) 的缩放因子始终为 1。

O(log n):称为对数复杂度

  • 1 项:1 次操作
  • 10 项:2 次操作
  • 100 项:3 次操作
  • 1000 项:4 次操作
  • 10,000 项:5 次操作

计算次数仅增加输入值的对数。所以在这种情况下,假设每次计算需要 1 秒,输入 n 的日志就是所需的时间,因此 log n

这就是它的要点。他们减少了数学,所以它可能不完全是 n2 或他们所说的任何东西,但这将是缩放的主要因素。

【讨论】:

  • 这个定义到底是什么意思? (项目的数量仍然增加了 10 倍,但 O(1) 的比例因子始终为 1。)
  • 不是秒,操作。此外,您错过了阶乘和对数时间。
  • 这并不能很好地解释 O(n^2) 可以描述一个精确运行在 0.01*n^2 + 999999*n + 999999 的算法。重要的是要知道算法使用此比例进行比较,并且当 n“足够大”时比较有效。 Python 的 timsort 实际上对小型数组使用插入排序(最坏/平均情况 O(n^2)),因为它的开销很小。
  • 这个答案也混淆了大 O 表示法和 Theta 表示法。 n 的所有输入(通常简单地写为 1)返回 1 的函数实际上在 O(n^2) 中(即使它也在 O(1) 中)。类似地,只需要执行一个花费恒定时间的步骤的算法也被认为是 O(1) 算法,但也被认为是 O(n) 和 O(n^2) 算法。但也许数学家和计算机科学家不同意这个定义:-/.
  • 此答案中考虑的 O(log n) 对数复杂度以 10 为底。通常标准是以 2 为底进行计算。应牢记这一事实,不应混淆。也正如@ChrisCharabaruk 所提到的,复杂性表示操作数而不是秒数。
【解决方案3】:

Big-O 表示法(也称为“渐近增长”表示法)是当您忽略原点附近的常数因素和东西时,函数“看起来像”的样子。我们用它来谈论事物如何扩展


基础知识

对于“足够”大的输入...

  • f(x) ∈ O(upperbound) 表示 f“增长速度不超过”upperbound
  • f(x) ∈ Ɵ(justlikethis) 意思是 f “长得一模一样” justlikethis
  • f(x) ∈ Ω(lowerbound) 表示 f “增长速度不低于” lowerbound

big-O 表示法不关心常数因子:函数9x² 被称为“完全像”10x²。 big-O asymptotic 表示法也不关心 non-asymptotic 东西(“靠近原点的东西”或“当问题规模很小时会发生什么”):函数 @ 987654341@ 据说“长得一模一样”10x² - x + 2

为什么要忽略等式的较小部分?因为当你考虑越来越大的尺度时,它们与方程的大部分相比变得完全相形见绌;他们的贡献变得渺小和无关紧要。 (参见示例部分。)

换句话说,当你走向无穷大时,这一切都与比率有关。 如果你将实际花费的时间除以O(...),你将得到一个限制大输入限制的常数因子。直观地说,这是有道理的:如果你可以相乘,函数就会“像”一样缩放一个得到另一个。那就是我们说...

actualAlgorithmTime(N) ∈ O(bound(N))
                                       e.g. "time to mergesort N elements 
                                             is O(N log(N))"

...这意味着对于“足够大”的问题大小 N(如果我们忽略原点附近的东西),存在一些常数(例如 2.5,完全组成)这样:

actualAlgorithmTime(N)                 e.g. "mergesort_duration(N)       "
────────────────────── < constant            ───────────────────── < 2.5 
       bound(N)                                    N log(N)         

常数有多种选择;通常“最佳”选择被称为算法的“常数因子”......但我们经常忽略它,就像我们忽略非最大项一样(请参阅常数因子部分了解它们通常不重要的原因)。你也可以把上面的等式看作一个界限,说“在最坏的情况下,它所花费的时间永远不会比大约N*log(N) 差,在 2.5 的因子内(我们没有的常数因子”不太在乎)”。

一般来说,O(...) 是最有用的,因为我们经常关心最坏情况的行为。如果f(x) 代表“坏”的东西,比如处理器或内存使用情况,那么“f(x) ∈ O(upperbound)”意味着“upperbound 是处理器/内存使用情况的最坏情况”。


应用程序

作为一个纯粹的数学结构,大 O 表示法不限于谈论处理时间和内存。您可以使用它来讨论缩放有意义的任何事物的渐近线,例如:

  • 聚会上N 人之间可能握手的次数(Ɵ(N²),特别是N(N-1)/2,但重要的是它“像”
  • 看到一些病毒式营销的人的概率预期数量是时间的函数
  • 网站延迟如何随 CPU、GPU 或计算机集群中的处理单元数量而变化
  • CPU 芯片上的热量输出如何随晶体管数量、电压等变化。
  • 算法需要运行多长时间,作为输入大小的函数
  • 算法需要运行多少空间,作为输入大小的函数

示例

对于上面的握手示例,房间中的每个人都与其他人握手。在该示例中,#handshakes ∈ Ɵ(N²)。为什么?

稍微备份一下:握手的次数恰好是 n-choose-2 或 N*(N-1)/2(N 个人中的每个人都与其他 N-1 个人握手,但是这重复了握手,所以除以 2):

但是,对于非常多的人来说,线性项 N 相形见绌,实际上对比率的贡献为 0(在图表中:对角线上的空框占总框的比例随着参与者数量的增加而变小变大)。因此缩放行为是order N²,或者握手的次数“像 N² 一样增长”。

#handshakes(N)
────────────── ≈ 1/2
     N²

就好像图表对角线上的空框(N*(N-1)/2 个复选标记)甚至不存在(N2 个复选标记渐近)。

(临时题外话“plain English”:)如果您想证明这一点,您可以对比率执行一些简单的代数,将其拆分为多个项(lim 表示“考虑到的限制” , 没看过的就忽略吧,它只是“and N is really really big”的记号):

    N²/2 - N/2         (N²)/2   N/2         1/2
lim ────────── = lim ( ────── - ─── ) = lim ─── = 1/2
N→∞     N²       N→∞     N²     N²      N→∞  1
                               ┕━━━┙
             this is 0 in the limit of N→∞:
             graph it, or plug in a really large number for N

tl;dr:对于大值,握手的次数“看起来像”x² 如此之多,如果我们要写下比率 #handshakes/x²,我们不需要的事实 确切地说 x² 握手甚至不会在任意大的时间内显示在十进制中。

例如对于 x=100 万,比率 #handshakes/x²:0.499999...


建立直觉

这让我们可以做出类似...的陈述

“对于足够大的inputsize=N,不管常数因子是多少,如果我加倍输入尺寸...

  • ... 我将 O(N)(“线性时间”)算法所花费的时间加倍。”

N → (2N) = 2(N)

  • ...我将 O(N²)(“二次时间”)算法所花费的时间平方(四倍)。”(例如,100 倍大的问题需要 100²=10000 倍的时间...可能不可持续)

→ (2N)² = 4()

  • ...我将 O(N³)(“立方时间”)算法所花费的时间加倍(八倍)。” (例如,100 倍大的问题需要 100³=1000000 倍的时间...非常不可持续)

cN³ → c(2N)³ = 8(cN³)

  • ...我在 O(log(N))(“对数时间”)算法所花费的时间上加上一个固定的量。” (便宜!)

c log(N) → c log(2N) = (c log(2))+(c log(N)) = (固定数量)+ (c log(N))

  • ...我不会更改 O(1)(“恒定时间”)算法所花费的时间。” (最便宜的!)

c*1c*1

  • ...我“(基本上)加倍”了 O(N log(N)) 算法所花费的时间。” (相当常见)

c 2N log(2N) / c N log(N)(这里我们将 f(2n)/f(n) 相除,但我们可以像上面那样对表达式进行处理并分解出 cNlogN如上)
→ 2 log(2N)/log(N)
→ 2 (log(2) + log(N))/log(N)
→ 2*(1+(log2N)-1) (对于大 N 基本上是 2;最终小于 2.000001)
(或者,对于您的数据来说,log(N) 将始终低于 17,因此它是 O(17 N),它是线性的;但这既不严谨也不合理)

  • ... 我荒谬地增加了 O(2N)(“指数时间”)算法所花费的时间。” (你可以加倍(或三倍等)只需将问题增加一个单位即可)

2N → 22N = (4N)......... ...换一种说法...... 2N → 2N+1 = 2N 21 = 2 2N

[对于数学倾向,您可以将鼠标悬停在剧透上以获取较小的旁注]

(归功于https://stackoverflow.com/a/487292/711085

(从技术上讲,常数因素在一些更深奥的例子中可能很重要,但我已经在上面说了一些事情(例如在 log(N) 中)所以它不)

这些是程序员和应用计算机科学家用作参考点的基本成长顺序。他们总是看到这些。 (因此,虽然您在技术上可以认为“将输入加倍会使 O(√N) 算法慢 1.414 倍”,但最好将其视为“这比对数差,但比线性好”。)


常数因子

通常,我们并不关心具体的常数因子是什么,因为它们不会影响函数的增长方式。例如,两种算法可能都需要O(N) 时间才能完成,但其中一种可能比另一种慢两倍。我们通常不会太在意,除非因素非常大,因为优化是一件棘手的事情(When is optimisation premature?);此外,仅仅选择具有更好大 O 的算法通常会提高性能几个数量级。

一些渐近优越的算法(例如,非比较 O(N log(log(N))) 排序)可能具有如此大的常数因子(例如 100000*N log(log(N))),或者像 O(N log(log(N))) 这样的相对较大的开销,带有隐藏的 + 100*N,即即使在“大数据”上,它们也很少值得使用。


为什么有时 O(N) 是你能做的最好的,即为什么我们需要数据结构

O(N) 算法在某种意义上是“最好”的算法,如果您需要读取所有数据。 读取一堆数据的行为是O(N) 操作。将其加载到内存中通常是O(N)(如果您有硬件支持,则速度更快,或者如果您已经读取数据,则根本没有时间)。但是,如果您触摸甚至查看每条数据(甚至每条其他数据),您的算法将花费O(N) 时间来执行此查看。无论您的实际算法需要多长时间,它至少会是O(N),因为它花费了这段时间查看所有数据。

写作本身也是如此。所有打印出 N 东西的算法都需要 N 时间,因为输出至少有那么长(例如打印出所有排列(重新排列的方法)一组 N 扑克牌是阶乘:O(N!)(这就是为什么在这些情况下,好的程序将确保迭代使用 O(1) 内存,并且不会打印或存储每个中间步骤))。

这激发了数据结构的使用:一个数据结构只需要读取一次数据(通常是O(N)时间),外加一些任意数量的预处理(例如O(N)O(N log(N))O(N²)),我们尽量保持小。此后,修改数据结构(插入/删除/等)并对数据进行查询只需很少的时间,例如O(1)O(log(N))。然后您继续进行大量查询!一般来说,你愿意提前做的工作越多,你以后要做的工作就越少。

例如,假设您拥有数百万条路段的经纬度坐标,并且想要查找所有街道交叉口。

  • 简单的方法:如果您有一个街道交叉口的坐标,并且想要检查附近的街道,您将不得不每次遍历数百万个路段,并检查每个路段是否相邻。
  • 如果你只需要做一次,那么O(N)的幼稚方法只工作一次是没有问题的,但是如果你想做很多次(在这种情况下,N次,每个段一次),我们必须做 O(N²) 工作,或 1000000²=1000000000000 次操作。不好(现代计算机每秒可以执行大约十亿次操作)。
  • 如果我们使用称为哈希表(一种即时查找表,也称为哈希图或字典)的简单结构,我们会通过在O(N) 时间内预处理所有内容来支付少量成本。此后,通过它的键查找某个东西平均只需要恒定的时间(在这种情况下,我们的键是经纬度坐标,四舍五入成一个网格;我们搜索相邻的网格空间,其中只有 9 个,这是一个常数)。
  • 我们的任务从不可行的O(N²) 变成了可管理的O(N),我们所要做的就是支付少量成本来制作哈希表。
  • 类比:在这种特殊情况下的类比是一个拼图游戏:我们创建了一个利用数据的某些属性的数据结构。如果我们的路段就像拼图一样,我们通过匹配颜色和图案对它们进行分组。然后我们利用这一点来避免以后做额外的工作(比较相同颜色的拼图,而不是与其他所有拼图)。

故事的寓意:数据结构可以让我们加快操作速度。更重要的是,高级数据结构可以让您以非常聪明的方式组合、延迟甚至忽略操作。不同的问题会有不同的类比,但它们都涉及以利用我们关心的某种结构的方式组织数据,或者我们为了记账而人为地强加给它的结构。我们确实提前工作(基本上是计划和组织),现在重复的任务要容易得多!


实际示例:在编码时可视化增长顺序

从本质上讲,渐近符号与编程完全不同。渐近符号是一种数学框架,用于思考事物如何扩展并可以用于许多不同的领域。也就是说...这就是您渐近符号应用于编码的方式。

基础知识:每当我们与大小为 A 的集合中的每个元素(例如数组、集合、映射的所有键等)进行交互,或执行循环的 A 迭代时,这就是一个乘法因子大小 A。为什么我说“乘法因子”?--因为循环和函数(几乎按照定义)具有乘法运行时间:迭代次数,循环中完成的工作时间(或函数:次数你调用函数,函数中完成的时间)。 (如果我们不做任何花哨的事情,比如跳过循环或提前退出循环,或者根据参数更改函数中的控制流,这很常见。)这里有一些可视化技术的例子,以及附带的伪代码。

(这里,xs 代表恒定时间工作单元、处理器指令、解释器操作码等)

for(i=0; i<A; i++)        // A * ...
    some O(1) operation     // 1

--> A*1 --> O(A) time

visualization:

|<------ A ------->|
1 2 3 4 5 x x ... x

other languages, multiplying orders of growth:
  javascript, O(A) time and space
    someListOfSizeA.map((x,i) => [x,i])               
  python, O(rows*cols) time and space
    [[r*c for c in range(cols)] for r in range(rows)]

示例 2:

for every x in listOfSizeA:   // A * (...
    some O(1) operation         // 1
    some O(B) operation         // B
    for every y in listOfSizeC: // C * (...
        some O(1) operation       // 1))

--> O(A*(1 + B + C))
    O(A*(B+C))        (1 is dwarfed)

visualization:

|<------ A ------->|
1 x x x x x x ... x

2 x x x x x x ... x ^
3 x x x x x x ... x |
4 x x x x x x ... x |
5 x x x x x x ... x B  <-- A*B
x x x x x x x ... x |
................... |
x x x x x x x ... x v

x x x x x x x ... x ^
x x x x x x x ... x |
x x x x x x x ... x |
x x x x x x x ... x C  <-- A*C
x x x x x x x ... x |
................... |
x x x x x x x ... x v

示例 3:

function nSquaredFunction(n) {
    total = 0
    for i in 1..n:        // N *
        for j in 1..n:      // N *
            total += i*k      // 1
    return total
}
// O(n^2)

function nCubedFunction(a) {
    for i in 1..n:                // A *
        print(nSquaredFunction(a))  // A^2
}
// O(a^3)

如果我们做一些稍微复杂的事情,你仍然可以直观地想象发生了什么:

for x in range(A):
    for y in range(1..x):
        simpleOperation(x*y)

x x x x x x x x x x |
x x x x x x x x x   |
x x x x x x x x     |
x x x x x x x       |
x x x x x x         |
x x x x x           |
x x x x             |
x x x               |
x x                 |
x___________________|

在这里,您可以绘制的最小可识别轮廓很重要;三角形是二维形状(0.5 A^2),就像正方形是二维形状(A^2);这里的常数因子 2 保留在两者之间的渐近比中,但是,我们像所有因子一样忽略它......(我不在这里讨论这种技术的一些不幸的细微差别;它可能会误导你。)

当然这并不意味着循环和函数不好;相反,它们是现代编程语言的基石,我们喜欢它们。但是,我们可以看到,我们将循环、函数和条件与我们的数据(控制流等)编织在一起的方式模仿了我们程序的时间和空间使用!如果时间和空间使用成为问题,那就是当我们求助于聪明并找到一个我们没有考虑过的简单算法或数据结构时,以某种方式降低增长顺序。然而,这些可视化技术(尽管它们并不总是有效)可以让您天真地猜测最坏情况下的运行时间。

这是我们可以通过视觉识别的另一件事:

<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x
x x x x
x x
x

我们可以重新排列一下,看看它是 O(N):

<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x|x x x x x x x x|x x x x|x x|x

或者也许你对数据进行 log(N) 次传递,总时间为 O(N*log(N)):

   <----------------------------- N ----------------------------->
 ^  x x x x x x x x x x x x x x x x|x x x x x x x x x x x x x x x x
 |  x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x x x x
lgN x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x
 |  x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x
 v  x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x

无关但值得再次提及:如果我们执行哈希(例如字典/哈希表查找),那是 O(1) 的一个因素。这相当快。

[myDictionary.has(x) for x in listOfSizeA]
 \----- O(1) ------/    

--> A*1 --> O(A)

如果我们做一些非常复杂的事情,例如使用递归函数或分治算法,您可以使用Master Theorem(通常有效),或者在荒谬的情况下使用 Akra-Bazzi 定理(几乎总是有效)你在维基百科上查找你的算法的运行时间。

但是,程序员不会这样想,因为最终,算法直觉会成为第二天性。您将开始编写一些低效的代码并立即思考“我在做某事效率极低吗?”。如果答案是“是”并且您预见到它实际上很重要,那么您可以退后一步,想出各种技巧来让事情运行得更快(答案几乎总是“使用哈希表”,很少“使用树”,并且很少有更复杂的东西)。


摊销和平均案例复杂度

还有“摊销”和/或“平均情况”的概念(注意它们是不同的)。

平均情况:这只不过是对函数的期望值使用大 O 表示法,而不是函数本身。在通常情况下,您认为所有输入的可能性相同,平均情况只是运行时间的平均值。例如对于快速排序,即使对于一些非常糟糕的输入,最坏的情况是O(N^2),但平均情况是通常的O(N log(N))(真正糟糕的输入数量非常少,以至于我们没有注意到它们一般情况下)。

摊销的最坏情况:某些数据结构的最坏情况复杂度可能很大,但保证如果您执行许多此类操作,您所做的平均工作量会更好比最坏的情况。例如,您可能有一个通常需要常数O(1) 时间的数据结构。但是,偶尔它会“打嗝”并花费O(N) 时间进行一次随机操作,因为它可能需要做一些簿记或垃圾收集或其他事情......但它向您保证,如果它打嗝,它不会打嗝再次进行 N 次以上的操作。最坏情况下的成本仍然是每次操作的O(N),但多次运行的摊销成本是每次操作的O(N)/N = O(1)。由于大型操作很少见,因此可以将大量的临时工作视为与其余工作融为一体的恒定因素。我们说这项工作在足够多的调用中“摊销”了,它会逐渐消失。

摊销分析的类比:

你开车。有时候,你需要花 10 分钟去 加油站,然后花 1 分钟时间给油箱加油。 如果你每次开车去任何地方都这样做(花 10 几分钟开车到加油站,花几秒钟加油 一加仑的一小部分),这将是非常低效的。但是如果你填 每隔几天就上一次油箱,开车到 加油站在足够多的行程中“摊销”, 你可以忽略它,假装你所有的行程都可能延长了 5%。

平均情况与摊销最坏情况之间的比较:

  • 平均情况:我们对输入做出一些假设;即如果我们的输入有不同的概率,那么我们的输出/运行时将有不同的概率(我们取平均值)。通常,我们假设我们的输入都是等可能的(均匀概率),但如果现实世界的输入不符合我们的“平均输入”假设,则平均输出/运行时计算可能毫无意义。但是,如果您预期均匀随机输入,那么考虑一下这一点很有用!
  • 摊销的最坏情况:如果您使用摊销的最坏情况数据结构,则性能保证在摊销的最坏情况内......最终(即使输入是由无所不知的邪恶恶魔选择的,并且正试图把你搞砸)。通常,我们使用它来分析性能可能非常“不稳定”的算法,这些算法可能会出现意想不到的大问题,但随着时间的推移,它们的性能与其他算法一样好。 (但是,除非您的数据结构对愿意拖延的大量未完成工作有上限,否则邪恶的攻击者可能会迫使您一次性完成最大数量的拖延工作。

不过,如果您是关于攻击者的 reasonably worried,那么除了摊销和平均情况之外,还有许多其他算法攻击向量需要担心。)

平均情况和摊销都是非常有用的工具,可用于思考和设计时考虑到可扩展性。

(如果对此子主题感兴趣,请参阅Difference between average case and amortized analysis。)


多维大O

大多数时候,人们没有意识到有不止一个变量在起作用。例如,在字符串搜索算法中,您的算法可能需要时间O([length of text] + [length of query]),即它在两个变量(如O(N+M))中是线性的。其他更幼稚的算法可能是O([length of text]*[length of query])O(N*M)。忽略多个变量是我在算法分析中看到的最常见的疏忽之一,并且会在设计算法时妨碍您。


整个故事

请记住,big-O 并不是故事的全部。您可以通过使用缓存来显着加速某些算法,使其无需缓存,通过使用 RAM 而不是磁盘来避免瓶颈、使用并行化或提前完成工作——这些技术通常是独立的增长顺序的“big-O”表示法,尽管您经常会在并行算法的 big-O 表示法中看到内核数量。

另外请记住,由于程序的隐藏约束,您可能并不真正关心渐近行为。您可能正在使用有限数量的值,例如:

  • 如果您要对 5 个元素进行排序,您不想使用快速的O(N log(N)) 快速排序;您想使用插入排序,它恰好在小输入上表现良好。这些情况经常出现在分治算法中,您可以将问题分解为越来越小的子问题,例如递归排序、快速傅立叶变换或矩阵乘法。
  • 如果某些值由于某些隐藏的事实而得到有效限制(例如,人名的平均限制在 40 个字母左右,而人类年龄的限制在 150 左右)。您还可以对输入施加限制,以有效地使术语保持不变。

在实践中,即使在渐近性能相同或相似的算法中,它们的相对优劣实际上也可能受其他因素驱动,例如:其他性能因素(快速排序和归并排序都是O(N log(N)),但快速排序利用了CPU 缓存);非性能考虑,例如易于实施;图书馆是否可用,图书馆的声誉和维护情况如何。

程序在 500MHz 计算机上的运行速度也会比 2GHz 计算机上的慢。我们并没有真正将其视为资源界限的一部分,因为我们认为缩放是根据机器资源(例如每个时钟周期)而不是实际秒。但是,也有类似的事情会“秘密地”影响性能,例如您是否在仿真下运行,或者编译器是否优化了代码。这些可能会使一些基本操作花费更长的时间(甚至相对于彼此),甚至会渐近地加速或减慢一些操作(甚至相对于彼此)。不同实现和/或环境之间的影响可能很小或很大。你会切换语言或机器来勉强完成那一点额外的工作吗?这取决于其他一百个原因(必要性、技能、同事、程序员的生产力、您时间的金钱价值、熟悉度、变通方法、为什么不组装或 GPU 等...),这可能比性能更重要。

上述问题,例如选择使用哪种编程语言的影响,几乎从未被视为常量因素的一部分(也不应该如此);但是应该注意它们,因为有时(尽管很少)它们可能会影响事物。例如在 cpython 中,本机优先级队列实现是渐近非最优的(O(log(N)) 而不是 O(1) 供您选择插入或 find-min);你使用其他实现吗?可能不会,因为 C 实现可能更快,并且其他地方可能还有其他类似的问题。有权衡;有时它们很重要,有时它们不重要。


edit:“简单的英语”解释到此结束。)

数学附录

为了完整起见,big-O 表示法的精确定义如下:f(x) ∈ O(g(x)) 表示“f 由 const*g 渐近上界”:忽略 x 的某个有限值以下的所有内容,存在一个常数,使得|f(x)| ≤ const * |g(x)|。 (其他符号如下:就像O 表示≤,Ω 表示≥。还有小写变体:o 表示ω 表示>。)f(x) ∈ Ɵ(g(x)) 表示f(x) ∈ O(g(x))f(x) ∈ Ω(g(x))(g 的上限和下限):存在一些常数,使得 f 始终位于 const1*g(x)const2*g(x) 之间的“带”中。这是你能做出的最强的渐近语句,大致相当于==。 (抱歉,为了清楚起见,我选择将绝对值符号的提及推迟到现在;特别是因为我从未见过在计算机科学环境中出现负值。)

人们会经常使用= O(...),这可能是更正确的“comp-sci”表示法,并且完全可以使用; “f = O(...)”读作“f is order ... / f is xxx-bounded by ...”并被认为是“f 是一些渐近线为 ...”的表达式。我被教导使用更严格的∈ O(...) 表示“是”的一个元素(仍然像以前一样阅读)。在这种特殊情况下,O(N²) 包含像 {2 N², 3 N², 1/2 N², 2 N² + log(N), - N² + N^1.9, ...} 这样的元素并且是无限大的,但它仍然是一个集合。

O 和 Ω 不是对称的(n = O(n²),但 n² 不是 O(n)),但Ɵ 是对称的,并且(因为这些关系都是传递和自反的)Ɵ 因此是对称的并且传递性和自反性,因此将所有函数的集合划分为等价类。等价类是我们认为相同的一组事物。也就是说,给定您能想到的任何函数,您都可以找到该类的规范/唯一“渐近代表”(通常取极限……我认为);就像您可以将所有整数分组为奇数或偶数一样,您可以通过基本上忽略较小的术语将所有带有 Ɵ 的函数分组为 x-ish、log(x)^2-ish 等...更复杂的函数,它们是独立的类)。

= 表示法可能是更常见的一种,甚至被世界著名的计算机科学家在论文中使用。此外,通常情况下,在随意的环境中,人们会说O(...),而他们的意思是Ɵ(...);这在技术上是正确的,因为Ɵ(exactlyThis) 的集合是O(noGreaterThanThis) 的一个子集......而且它更容易输入。 ;-)

【讨论】:

  • 一个很好的数学答案,但 OP 要求一个简单的英文答案。理解答案不需要这种级别的数学描述,尽管对于特别有数学头脑的人来说,它可能比“简单的英语”更容易理解。然而,OP 要求后者。
  • 大概除了 OP 以外的其他人可能对这个问题的答案感兴趣。这不是网站的指导原则吗?
  • 虽然我可能明白为什么人们可能会略读我的回答并认为它太数学了(尤其是“数学是新的普通英语”讽刺言论,因为已删除),但最初的问题是关于 big- O 是关于函数的,所以我试图明确并以一种补充简单英语直觉的方式来讨论函数。这里的数学通常可以用高中数学背景掩盖或理解。我确实觉得人们可能会在最后查看数学附录,并假设这是答案的一部分,而它只是为了看看真正的数学是什么样的。
  • 这是一个绝妙的答案; IMO 比得票最多的那个要好得多。所需的“数学”并没有超出理解“O”后面括号中表达式所需的范围,这是任何使用任何示例的合理解释都无法避免的。
  • "f(x) ∈ O(upperbound) 表示 f“增长不快于”upperbound”这三个简单的措辞,但对 big Oh、Theta 和 Omega 的数学正确解释是黄金。他用简单的英语向我描述了如果不编写复杂的数学表达式,5 个不同的来源似乎无法翻译给我的观点。谢啦! :)
【解决方案4】:

编辑:请注意,这几乎肯定会将Big O notation(这是一个上限)与 Theta 表示法(既是上限又是下限)混淆。以我的经验,这实际上是非学术环境中的典型讨论。对于造成的任何混淆,我们深表歉意。

一句话:随着你的工作规模越来越大,完成它需要多长时间?

显然,这只是使用“大小”作为输入,“所用时间”作为输出——如果你想谈论内存使用等,同样的想法也适用。

这是一个例子,我们有 N 件 T 恤要烘干。我们将假设让它们处于干燥位置非常快(即人类互动可以忽略不计)。当然,现实生活中并非如此......

  • 在室外使用晾衣绳:假设您有一个无限大的后院,洗涤物在 O(1) 时间内干燥。不管你有多少,它都会得到同样的阳光和新鲜空气,所以大小不会影响干燥时间。

  • 使用滚筒式干衣机:您在每个负载中放置 10 件衬衫,然后它们在一个小时后完成。 (忽略此处的实际数字——它们无关紧要。)因此,烘干 50 件衬衫所花费的时间是烘干 10 件衬衫所花费的大约 5 倍。

  • 把所有东西都放在一个晾晒的柜子里:如果我们把所有东西都放在一大堆里,让一般的温暖来做,中间的衬衫需要很长时间才能变干。我不想猜测细节,但我怀疑这至少是 O(N^2) — 随着洗涤负荷的增加,干燥时间增加得更快。

“大 O”表示法的一个重要方面是它没有说明对于给定大小哪种算法更快。采用哈希表(字符串键,整数值)与对数组(字符串,整数)。基于字符串在哈希表中查找键或数组中的元素是否更快? (即对于数组,“找到字符串部分与给定键匹配的第一个元素。”)哈希表通常是摊销的(~=“平均”)O(1)——一旦它们被设置,它应该需要大约在 100 个条目表中查找条目与在 1,000,000 个条目表中查找条目的时间相同。在数组中查找元素(基于内容而不是索引)是线性的,即 O(N) — 平均而言,您将不得不查看一半的条目。

这会使哈希表比查找数组更快吗?不必要。如果您有一个非常小的条目集合,那么数组可能会更快 - 您可以在计算您正在查看的字符串的哈希码所需的时间内检查所有字符串。然而,随着数据集越来越大,哈希表最终会击败数组。

【讨论】:

  • 哈希表需要运行算法来计算实际数组的索引(取决于实现)。一个数组只有 O(1) 因为它只是一个地址。但这与问题无关,只是一个观察:)
  • jon 的解释与我认为的问题有很大关系。这正是人们可以向某个妈妈解释的方式,她最终会理解我认为:) 我喜欢衣服的例子(特别是最后一个,它解释了复杂性的指数增长)
  • Filip:我不是在谈论按索引寻址数组,而是在谈论在数组中找到匹配的条目。您能否重新阅读答案,看看是否仍然不清楚?
  • @Filip Ekberg 我认为您正在考虑一个直接地址表,其中每个索引直接映射到一个键,因此是 O(1),但是我相信 Jon 正在谈论一个未排序的键数组您必须线性搜索的 /val 对。
  • @RBT:不,这不是二进制查找。它可以立即得到正确的散列bucket,仅仅基于从散列码到桶索引的转换。之后,在桶中找到正确的哈希码可能是线性的,也可能是二分查找……但到那时,你只占字典总大小的很小一部分。
【解决方案5】:

Big O 描述了函数增长行为的上限,例如,当输入变大时,程序的运行时间。

例子:

  • O(n):如果我将输入大小加倍,则运行时间加倍

  • O(n2):如果输入大小翻倍,则运行时翻四倍

  • O(log n):如果输入大小翻倍,则运行时间增加一

  • O(2n):如果输入大小增加一,运行时间加倍

输入大小通常是表示输入所需的空间(以位为单位)。

【讨论】:

  • 不正确!例如 O(n):如果我将输入大小加倍,则运行时将乘以有限非零常数。我的意思是 O(n) = O(n + n)
  • 我说的是 f(n) = O(g(n)) 中的 f,而不是你所理解的 g。
  • 我投了赞成票,但我觉得最后一句话并没有多大贡献。在讨论或衡量 Big(O) 时,我们并不经常谈论“位”。
  • 您应该为 O(n log n) 添加一个示例。
  • 这不是很清楚,本质上它的行为比 O(n) 差一点。因此,如果 n 翻倍,则运行时间乘以一个大于 2 的因子。
【解决方案6】:

程序员最常使用大 O 表示法作为计算(算法)完成所需时间的近似度量,表示为输入集大小的函数。

Big O 有助于比较两种算法随着输入数量的增加而扩展的效果。

更准确地说,Big O notation 用于表示函数的渐近行为。这意味着函数在接近无穷大时的行为方式。

在许多情况下,算法的“O”将属于以下情况之一:

  • O(1) - 无论输入集的大小如何,完成时间都是相同的。一个示例是通过索引访问数组元素。
  • O(Log N) - 完成时间大致与 log2(n) 一致。例如,1024 个项目大约是 32 个项目的两倍,因为 Log2(1024) = 10 和 Log2(32) = 5。一个例子是在 binary search tree (BST) 中查找一个项目。
  • O(N) - 完成时间与输入集的大小成线性关系。换句话说,如果您将输入集中的项目数加倍,则该算法大约需要两倍的时间。一个例子是计算链表中的项目数。
  • O(N Log N) - 完成时间增加的项目数乘以 Log2(N) 的结果。这方面的一个例子是heap sortquick sort
  • O(N^2) - 完成时间大致等于项目数的平方。 bubble sort 就是一个例子。
  • O(N!) - 完成时间是输入集的阶乘。这方面的一个例子是traveling salesman problem brute-force solution

随着输入大小向无穷大增加,Big O 会忽略对函数的增长曲线没有有意义贡献的因素。这意味着简单地忽略添加到函数或乘以函数的常量。

【讨论】:

  • cdiggins,如果我有 O(N/2) 复杂度怎么办,它应该是 O(N) 还是 O(N/2),例如,如果我将循环半个字符串会有什么复杂度。
  • @Melad 这是一个将常数 (0.5) 乘以函数的示例。这被忽略了,因为它被认为对非常大的 N 值具有有意义的影响。
【解决方案7】:

Big O 只是一种以常见方式“表达”自己的方式,“运行我的代码需要多少时间/空间?”。

你可能经常会看到O(n)、O(n2)、O(nlogn)等等,这些都是展示的方式;算法如何变化?

O(n) 表示大 O 是 n,现在你可能会想,“n 是什么!?”那么“n”是元素的数量。成像您要在数组中搜索项目。您必须查看每个元素并作为“您是正确的元素/项目吗?”在最坏的情况下,该项目位于最后一个索引,这意味着它花费的时间与列表中的项目一样多,所以为了通用,我们说“哦,嘿,n 是一个公平的给定数量的值!” .

那么您可能会理解“n2”的含义,但更具体地说,请考虑一下您有一个简单、最简单的排序算法;冒泡排序。该算法需要针对每个项目查看整个列表。

我的清单

  1. 1
  2. 6
  3. 3

这里的流程是:

  • 比较 1 和 6,哪个最大? Ok 6 位置正确,继续前进!
  • 比较 6 和 3,哦,3 少了!让我们动起来,好吧,列表改变了,我们需要从头开始!

这是 O n2 因为,您需要查看列表中的所有项目有“n”个项目。对于每个项目,你再看一遍所有项目,为了比较,这也是“n”,所以对于每个项目,你看“n”次意味着 n*n = n2

我希望这就像你想要的那样简单。

但请记住,Big O 只是一种以时间和空间的方式表达自己的方式。

【讨论】:

  • 对于 logN,我们认为 for 循环从 0 运行到 N/2,那么 O(log log N) 呢?我的意思是程序看起来如何?请原谅我的纯数学技能
【解决方案8】:

Big O 描述了算法的基本缩放特性。

Big O 没有告诉您有关给定算法的很多信息。它切入到骨子里,只提供有关算法缩放性质的信息,特别是算法的资源使用(思考时间或内存)如何根据“输入大小”进行缩放。

考虑一下蒸汽机和火箭之间的区别。它们不仅是同一事物的不同品种(例如,普锐斯发动机与兰博基尼发动机),而且它们的核心是截然不同的推进系统。蒸汽机可能比玩具火箭快,但没有蒸汽活塞发动机能够达到轨道运载火箭的速度。这是因为这些系统在达到给定速度(“输入大小”)所需的燃料(“资源使用”)关系方面具有不同的缩放特性。

为什么这如此重要?因为软件处理的问题的大小可能相差多达一万亿倍。考虑一下。前往月球所需的速度与人类步行速度之间的比率小于 10,000:1,与软件可能面临的输入大小范围相比,这绝对是微不足道的。而且由于软件在输入大小方面可能会面临一个天文数字范围,因此算法的大 O 复杂性(它是基本的缩放性质)可能会胜过任何实现细节。

考虑规范排序示例。冒泡排序是 O(n2),而合并排序是 O(n log n)。假设您有两个排序应用程序,使用冒泡排序的应用程序 A 和使用合并排序的应用程序 B,假设对于大约 30 个元素的输入大小,应用程序 A 在排序时比应用程序 B 快 1,000 倍。如果您永远不必对超过 30 个元素进行排序,那么显然您应该更喜欢应用程序 A,因为在这些输入大小下它要快得多。但是,如果您发现您可能需要对一千万个项目进行排序,那么您会期望在这种情况下,应用程序 B 实际上最终会比应用程序 A 快数千倍,这完全取决于每种算法的扩展方式。

【讨论】:

    【解决方案9】:

    这是我在解释 Big-O 的常见变体时倾向于使用的简单的英语寓言

    在所有情况下,优先选择列表中较高的算法而不是列表中较低的算法。但是,迁移到更昂贵的复杂性类别的成本差异很大。

    O(1):

    没有增长。无论问题有多大,您都可以在相同的时间内解决它。这有点类似于广播,在给定的距离内广播需要相同的能量,而不管广播范围内的人数。

    O(log n):

    这种复杂性与 O(1) 相同,只是稍微差一点。出于所有实际目的,您可以将其视为非常大的常数缩放。处理 1000 件和 10 亿件物品之间的工作差异仅为 6 倍。

    O(n):

    解决问题的成本与问题的大小成正比。如果您的问题规模翻倍,那么解决方案的成本就会翻倍。由于大多数问题都必须以某种方式扫描到计算机中,例如数据输入、磁盘读取或网络流量,因此这通常是一个负担得起的缩放因子。

    O(n log n):

    这种复杂性与 O(n) 非常相似。出于所有实际目的,两者是等价的。这种复杂程度通常仍被认为是可扩展的。通过调整假设,一些 O(n log n) 算法可以转换为 O(n) 算法。例如,限制键的大小将排序从 O(n log n) 减少到 O(n em>).

    O(n2):

    长成正方形,其中n是正方形边长。这与“网络效应”的增长率相同,网络中的每个人都可能认识网络中的其他人。增长是昂贵的。大多数可扩展的解决方案如果不做大量的练习,就无法使用这种复杂程度的算法。这通常适用于所有其他多项式复杂性 - O(nk) - 以及。

    O(2n):

    不缩放。你没有希望解决任何非平凡的问题。有助于了解应该避免什么,以及帮助专家找到 O(nk) 中的近似算法。

    【讨论】:

    • 您能否考虑对 O(1) 进行不同的类比?我的工程师想讨论一下由于障碍物导致的射频阻抗。
    【解决方案10】:

    Big O 衡量算法使用的时间/空间相对于其输入的大小。

    如果算法是 O(n),那么时间/空间将以与其输入相同的速率增加。

    如果算法是 O(n2),那么时间/空间会以其输入平方的速率增加。

    等等。

    【讨论】:

    • 这与空间无关。这是关于复杂性,这意味着时间。
    • 我一直相信它可能与时间或空间有关。但不能同时兼顾两者。
    • 复杂性绝对可以与空间有关。看看这个:en.wikipedia.org/wiki/PSPACE
    • 这个答案是这里最“简单”的一个。以前的实际上假设读者知道足以理解它们,但作者并没有意识到这一点。他们认为他们的简单明了,绝对不是。用漂亮的格式编写大量文本并制作对非 CS 人员来说很难的花哨的人工示例并不简单,这对主要是 CS 人员的 stackoverflowers 很有吸引力。用简单的英语解释 CS 术语根本不需要代码和数学。 +1 为这个答案,虽然它仍然不够好。
    • 这个答案产生了假设 f=O(g) 意味着 f 和 g 成比例的(常见)错误。
    【解决方案11】:

    很难衡量软件程序的速度,当我们尝试时,答案可能非常复杂,并且充满了异常和特殊情况。这是一个大问题,因为当我们想要将两个不同的程序相互比较以找出哪个“最快”时,所有这些异常和特殊情况都会分散注意力且无济于事。

    由于所有这些无益的复杂性,人们试图使用尽可能最小和最不复杂(数学)的表达式来描述软件程序的速度。这些表达式是非常粗略的近似:尽管如果运气好的话,它们将捕捉到软件是快还是慢的“本质”。

    因为它们是近似值,所以我们在表达式中使用字母“O”(Big Oh)作为约定,向读者表明我们过于简单化了。 (并确保没有人错误地认为表达式在任何方面都是准确的)。

    如果您将“哦”理解为“大约”或“大约”的意思,您就不会错得太远。 (我认为 Big-Oh 的选择可能是一种幽默的尝试)。

    这些“Big-Oh”表达式试图做的唯一一件事是描述随着我们增加软件必须处理的数据量,软件会减慢多少。如果我们需要处理的数据量增加一倍,软件是否需要两倍的时间才能完成它的工作?十倍的时间?在实践中,您会遇到并且需要担心的大哦表达式的数量非常有限:

    好的:

    • O(1) 常数:无论输入多大,程序运行的时间都是一样的。
    • O(log n) 对数:程序运行时间只是缓慢增加,即使输入的大小大幅增加。

    坏处:

    • O(n) 线性:程序运行时间与输入的大小成正比。
    • O(n^k) 多项式: - 随着输入大小的增加,处理时间越来越快 - 作为多项式函数。

    ...和丑陋的:

    • O(k^n) 指数程序运行时间增加非常快,即使问题规模适度增加 - 仅使用指数算法处理小型数据集才实用。
    • O(n!) Factorial 程序运行时间将超出您所能承受的时间,除了最小和最看似微不足道的数据集。

    【讨论】:

    • 我也听说过 Linearithmic 这个词 - O(n log n) 这会被认为是好的。
    【解决方案12】:

    Big O 的简单英文解释是什么?尽可能少的正式定义和简单的数学。

    需要 Big-O 表示法的简单英文解释:

    当我们编程时,我们正在尝试解决一个问题。我们编写的代码称为算法。大 O 表示法允许我们以标准化的方式比较算法的最坏情况性能。硬件规格随时间而变化,硬件的改进可以减少算法运行所需的时间。但是更换硬件并不意味着我们的算法会随着时间的推移变得更好或有所改进,因为我们的算法还是一样的。因此,为了让我们能够比较不同的算法,以确定一个是否更好,我们使用大 O 表示法。

    什么大 O 表示法的简单英文解释:

    并非所有算法都在相同的时间内运行,并且可能会根据输入中的项目数而有所不同,我们将其称为 n。基于此,我们考虑最坏情况分析,或随着 n 越来越大的运行时间的上限。我们必须知道 n 是什么,因为许多大 O 符号都引用它。

    【讨论】:

      【解决方案13】:

      好的,我的 2cents。

      Big-O,是程序消耗资源的增长率,w.r.t.问题实例大小

      资源:可能是总 CPU 时间,可能是最大 RAM 空间。默认是指 CPU 时间。

      说问题是“求和”,

      int Sum(int*arr,int size){
            int sum=0;
            while(size-->0) 
               sum+=arr[size]; 
      
            return sum;
      }
      

      问题实例= {5,10,15} ==> 问题实例大小= 3,循环中迭代= 3

      问题实例= {5,10,15,20,25} ==> 问题实例大小 = 5 次循环迭代 = 5

      对于大小为“n”的输入,程序以数组中“n”次迭代的速度增长。因此 Big-O 是 N 表示为 O(n)

      说问题是“寻找组合”,

          void Combination(int*arr,int size)
          { int outer=size,inner=size;
            while(outer -->0) {
              inner=size;
              while(inner -->0)
                cout<<arr[outer]<<"-"<<arr[inner]<<endl;
            }
          }
      

      问题实例= {5,10,15} ==> 问题实例大小 = 3,总迭代次数 = 3*3 = 9

      问题实例= {5,10,15,20,25} ==> 问题实例大小 = 5, 总迭代次数= 5*5 =25

      对于大小为“n”的输入,程序以数组中“n*n”次迭代的速度增长。因此 Big-O 是 N2 表示为 O(n2)

      【讨论】:

      • while (size--&gt;0)希望this不要再问了。
      【解决方案14】:

      一个简单直接的答案可以是:

      大 O 表示该算法可能出现的最差时间/空间。该算法永远不会占用超过该限制的更多空间/时间。大 O 代表极端情况下的时间/空间复杂度。

      【讨论】:

        【解决方案15】:

        大 O 表示法是一种根据空间或运行时间来描述算法上限的方法。 n 是问题中元素的数量(即数组的大小、树中的节点数等)。我们有兴趣描述随着 n 变大的运行时间。

        当我们说某个算法是 O(f(n)) 时,我们是说该算法的运行时间(或所需空间)总是低于某个常数时间 f(n)。

        说二分查找的运行时间为 O(logn) 就是说存在一个常数 c,你可以将它乘以 log(n) 总是大于二分查找的运行时间。在这种情况下,您将始终有一些 log(n) 比较的常数因子。

        换句话说,其中 g(n) 是算法的运行时间,我们说 g(n) = O(f(n)) 当 g(n) k,其中 c 和 k 是一些常数。

        【讨论】:

        【解决方案16】:

        "什么是 Big O 的简单英文解释? 尽可能定义简单的数学。"

        这样一个简单而简短的问题似乎至少应该得到一个同样简短的答案,就像学生在辅导期间可能会收到的一样。

        大 O 表示法只是告诉算法可以在多长时间内运行*, 就仅输入数据量而言**。

        (*在一种美妙的、无单位的时间感中!)
        (**这很重要,因为人们会always want more,无论他们是今天还是明天)

        那么,如果 Big O 表示法就是这样做的,那么它有什么了不起的呢?

        • 实际上,大 O 分析非常有用和重要,因为大 O 将重点直接放在算法的自身复杂性上,而完全忽略 任何仅仅是比例常数的东西——比如 JavaScript 引擎、CPU 的速度、你的互联网连接,以及所有那些很快就会变得像 Model T 一样可笑的过时的东西。 Big O 只关注对生活在现在或未来的人们同样重要的绩效。

        • Big O 表示法还直接聚焦计算机编程/工程最重要的原则,这一事实激励所有优秀的程序员不断思考和梦想:实现超越缓慢前进的唯一途径技术是发明一种更好的算法

        【讨论】:

        • 作为一名真正的博士,被要求在没有数学的情况下解释数学对我来说始终是一个个人挑战。相信这样的事情实际上是可能的数学家和教师。作为一名程序员,我希望没有人会介意我发现在没有数学的情况下回答这个特定问题是一个完全无法抗拒的挑战。
        【解决方案17】:

        算法示例(Java):

        public boolean search(/* for */Integer K,/* in */List</* of */Integer> L)
        {
            for(/* each */Integer i:/* in */L)
            {
                if(i == K)
                {
                    return true;
                }
            }
            
            return false;
        }
        

        算法说明:

        • 此算法逐项搜索列表,寻找键,

        • 迭代列表中的每一项,如果是键则返回True,

        • 如果循环结束但没有找到密钥,则返回 False。

        Big-O 表示法表示复杂度的上限(时间、空间、..)

        要找到时间复杂度上的大 O:

        • 计算最坏情况需要多少时间(关于输入大小):

        • 最坏情况:列表中不存在该键。

        • 时间(最坏情况)= 4n+1

        • 时间:O(4n+1) = O(n) |在 Big-O 中,常量被忽略

        • O(n) ~ 线性

        还有 Big-Omega,代表 Best-Case 的复杂性:

        • Best-Case:关键是第一项。

        • 时间(最佳情况)= 4

        • 时间:Ω(4) = O(1) ~ Instant\Constant

        【讨论】:

        • 你的常数 4 是从哪里来的?
        • @Rod迭代器初始化,迭代器比较,迭代器读取,键比较..我觉得C会更好
        【解决方案18】:

        大 O

        f(x) = O(g(x)) 当x转到a时(例如a = +∞)表示有一个函数k 这样:

        1. f(x) = k(x)g(x)

        2. k 有界于 a 的某个邻域(如果 a = +∞,这意味着存在数字 N 和 M,使得对于每个 x > N,|k(x) |

        换句话说,用简单的英语:f(x) = O(g(x)), x → a, 表示在a的邻域内, f 分解成 g 和一些有界函数的乘积。

        小号

        顺便说一下,这里是为了比较小o的定义。

        f(x) = o(g(x)) 当 x 转到 a 时,意味着有一个函数 k 满足:

        1. f(x) = k(x)g(x)

        2. k(x) 在 x 变为 a 时变为 0。

        例子

        • 当 x → 0 时,sin x = O(x)。

        • sin x = O(1) 当 x → +∞,

        • x2 + x = O(x) 当 x → 0,

        • x2 + x = O(x2) 当 x → +∞,

        • 当 x → +∞时,ln(x) = o(x) = O(x)。

        注意! 带等号“=”的符号使用“假等式”:o(g(x)) = O(g(x)) 是真的,但假的即 O(g(x)) = o(g(x))。类似地,可以写成“ln(x) = o(x) when x → +∞”,但公式​​“o(x) = ln(x)”就没有意义了。

        更多示例

        • O(1) = O(n) = O(n2) 当 n → +∞ 时(但不是相反,等式是“假的”),

        • O(n) + O(n2) = O(n2) 当 n → +∞

        • O(O(n2)) = O(n2) 当 n → +∞

        • O(n2)O(n3) = O(n5) 当 n → +∞


        这是维基百科的文章:https://en.wikipedia.org/wiki/Big_O_notation

        【讨论】:

        • 您在说明“大 O”和“小 o”时没有解释它们是什么,介绍了许多数学概念而没有说明它们为何重要,并且在这种情况下,维基百科的链接可能对这种问题。
        • @AditSaxena “不解释它们是什么”是什么意思?我准确地解释了它们是什么。也就是说,“big O”和“small o”本身什么都不是,只有像“f(x) = O(g(x))”这样的公式才有意义,我解释过(用简单的英语,但没有定义当然是微积分课程中所有必要的东西)。有时“O(f(x))”被视为所有函数“g(x)”的类(实际上是集合),使得“g(x) = O(f(x))”,但这是一个额外的步骤,这对于理解基础知识不是必需的。
        • 嗯,好吧,有些词不是简单的英语,但这是不可避免的,除非我必须包括数学分析中所有必要的定义。
        • 嗨#Alexey,请看一下接受的答案:它很长,但结构良好且格式正确。它从一个简单的定义开始,不需要数学背景。在这样做的同时,他引入了 3 个“技术”词,他立即解释了这些词(相对、表示、复杂性)。在深入研究该领域的同时逐步进行。
        • Big O 用于理解算法的渐近行为,原因与它用于理解函数的渐近行为相同(渐近行为是接近无穷大的行为)。它是一种方便的表示法,用于将复杂函数(算法占用的实际时间或空间)与接近无穷大或其他任何东西的简单函数(任何简单的,通常是幂函数)进行比较。我只解释了它是什么(给出了定义)。如何用大 O 计算是另一回事,也许我会添加一些例子,因为你有兴趣。
        【解决方案19】:

        大 O 表示法是一种描述算法在给定任意数量的输入参数(我们将其称为“n”)时运行速度的方式。它在计算机科学中很有用,因为不同的机器以不同的速度运行,仅仅说一个算法需要 5 秒并不能告诉你太多,因为虽然你可能正在运行一个具有 4.5 Ghz 八核处理器的系统,但我可能正在运行一个有 15 年历史的 800 Mhz 系统,无论算法如何,它都可能需要更长的时间。因此,我们不是根据时间来指定算法运行的速度,而是根据输入参数的数量或“n”来说明它的运行速度。通过这种方式描述算法,我们可以比较算法的速度,而不必考虑计算机本身的速度。

        【讨论】:

          【解决方案20】:

          不确定我是否会进一步为该主题做出贡献,但仍然认为我会分享:我曾经发现 this blog post 在 Big O 上有一些非常有用(尽管非常基本)的解释和示例:

          通过示例,这有助于将基本的基础知识带入我的龟甲状头骨,所以我认为这是一个非常好的 10 分钟阅读,可以让你朝着正确的方向前进。

          【讨论】:

          • @William ...人们往往会死于老年、物种灭绝、行星变得贫瘠等等。
          【解决方案21】:

          你想知道关于大 O 的一切吗?我也是。

          所以说到大 O,我将使用其中只有一个节拍的词。一字一音。小字很快。你知道这些词,我也知道。我们将使用一个声音的词。他们很小。我相信您会知道我们将使用的所有单词!

          现在,让我们谈谈工作吧。大多数时候,我不喜欢工作。你喜欢工作吗?您可能会这样做,但我确定我不会。

          我不喜欢上班。我不喜欢把时间花在工作上。如果我有我的方式,我只想玩,做有趣的事情。你和我有同感吗?

          现在,我有时不得不去上班。这很可悲,但却是真实的。所以,当我在工作时,我有一个规则:我尽量少做一些工作。尽可能接近没有工作。那我去玩吧!

          所以这里有一个大新闻:大 O 可以帮助我不工作!如果我知道大 O,我可以玩更多的时间。少工作,多玩!这就是大 O 帮助我做的事情。

          现在我有一些工作。我有这个清单:一、二、三、四、五、六。我必须在此列表中添加所有内容。

          哇,我讨厌工作。但是哦,好吧,我必须这样做。所以我来了。

          一加二等于三……加三等于六……四等于……我不知道。我迷路了。在我的脑海里做这件事对我来说太难了。我不太喜欢这种工作。

          所以我们不要做这项工作。让你和我想想这有多难。我需要做多少工作才能添加六个数字?

          好吧,让我们看看。我必须加一和二,然后再加上三,然后再加上四……总而言之,我数了六。我必须做六次加法才能解决这个问题。

          大 O 来了,告诉我们这个数学有多难。

          Big O 说:我们必须做六次加法来解决这个问题。加一,每件事从一到六。六小块工作……每一点工作都是一个加法。

          好吧,我现在不做添加它们的工作。但我知道那会有多难。这将是六个添加。

          哦不,现在我有更多的工作。嘘。这种东西谁做的?!

          现在他们让我从一加到十!我为什么要这么做?我不想加一到六。从一加到十……嗯……那就更难了!

          会更难吗?我还需要做多少工作?我需要更多还是更少的步骤?

          好吧,我想我必须做十次加法……从一到十,每件事加一次。十大于六。从一到十,而不是从一到六,我必须做更多的工作!

          我现在不想添加。我只是想想想添加这么多可能有多难。而且,我希望能尽快上场。

          要从 1 增加到 6,这是一些工作。但是你看,从 1 加到 10 是不是更多的工作?

          Big O 是你的朋友,也是我的朋友。 Big O 帮助我们思考我们必须做多少工作,这样我们就可以计划。而且,如果我们是大O的朋友,他可以帮助我们选择不那么难的工作!

          现在我们必须做新的工作。不好了。我一点也不喜欢这种工作。

          新作是:将所有事物从一加到n。

          等等!什么是n?我错过了吗?如果你不告诉我 n 是什么,我怎么能从一加到 n?

          好吧,我不知道 n 是什么。我没有被告知。你是吗?不?那好吧。所以我们不能做这项工作。呼。

          虽然我们现在不会做这项工作,但如果我们知道 n,我们可以猜测它会有多难。我们必须把 n 个东西加起来,对吧?当然!

          现在大 O 来了,他会告诉我们这项工作有多难。他说:将所有事物从一加到 N,一一相加,是 O(n)。要添加所有这些东西,[我知道我必须添加 n 次。][1] 那是大 O!他告诉我们做某种工作有多难。

          对我来说,我认为大 O 就像一个大而缓慢的老板。他想着工作,但他不去做。他可能会说,“那工作很快。”或者,他可能会说,“这项工作是如此缓慢而艰巨!”但他不做这项工作。他只是看着作品,然后告诉我们可能需要多长时间。

          我非常关心大 O。为什么?我不喜欢工作!没有人喜欢工作。这就是为什么我们都喜欢大 O!他告诉我们可以多快地工作。他帮助我们思考工作的艰辛。

          呃哦,还有更多的工作。现在,我们不要做这项工作。但是,让我们制定一个计划,一步一步地去做。

          他们给了我们一副十张牌。它们都混在一起了:七、四、二、六……一点也不直。现在……我们的工作是对它们进行分类。

          呃。听起来工作量很大!

          我们如何对这副牌进行排序?我有一个计划。

          我会逐对地查看每一对牌,从头到尾逐一检查。如果一对中的第一张牌很大,而该对中的下一张牌很小,我就交换它们。否则,我去下一对,依此类推……很快,套牌就完成了。

          当套牌完成后,我问:我在那一关中交换了牌吗?如果是这样,我必须从头再来一次。

          在某些时候,在某些时候,不会有交换,我们的套牌就会完成。辛苦了!

          那么,按照这些规则对卡片进行排序需要做多少工作?

          我有十张牌。而且,大多数时候——也就是说,如果我运气不好的话——我必须把整副牌最多翻十次,每次翻牌最多十次。

          大O,帮帮我!

          大 O 进来并说:对于一副 n 牌,以这种方式排序将在 O(N 平方) 时间内完成。

          他为什么说 n 平方?

          嗯,你知道 n 的平方是 n 乘以 n。现在,我明白了:检查了 n 张牌,最多可以通过甲板 n 次。那是两个循环,每个循环有 n 个步骤。这是 n 平方很多工作要做。肯定有很多工作!

          现在当大 O 说它需要 O(n 平方) 的工作时,他并不是说 n 平方加法,在鼻子上。在某些情况下,它可能会少一些。但在最坏的情况下,对牌组进行分类将需要接近 n 平方步的工作。

          现在大 O 是我们的朋友。

          Big O 指出了这一点:随着 n 变大,当我们对卡片进行分类时,这项工作会比过去的只是添加这些东西的工作困难得多。我们怎么知道的?

          好吧,如果 n 变得非常大,我们不在乎我们可以添加到 n 或 n 平方。

          对于大 n,n 的平方比 n 大。

          Big O 告诉我们,排序比添加更难。对于大 n,O(n squared) 大于 O(n)。这意味着:如果 n 变得非常大,那么对包含 n 个事物的混合数据进行排序必须花费更多时间,而不是仅仅添加 n 个混合事物。

          Big O 并没有为我们解决工作。 Big O 告诉我们工作有多辛苦。

          我有一副纸牌。我确实对它们进行了排序。你帮了忙。谢谢。

          有没有更快速的卡片分类方法?大O可以帮助我们吗?

          是的,还有更快捷的方法!学习需要一些时间,但它确实有效……而且效果非常快。您也可以尝试,但要花时间完成每一步,不要失去自己的位置。

          在这种对牌组进行排序的新方法中,我们不再像之前那样检查成对的牌。以下是您对该套牌进行排序的新规则:

          一个:我在我们现在正在处理的牌组中选择一张牌。如果你愿意,可以给我选一个。 (我们第一次这样做时,“我们现在处理的套牌的一部分”当然是整个套牌。)

          二:我在你选择的那张牌上展开套牌。这是什么展开;我怎么张开?好吧,我从起始牌一张一张地往下走,我寻找一张比张开牌更高的牌。

          三:我从最后一张牌往​​上走,我寻找一张比张开牌低的牌。

          找到这两张牌后,我将它们交换,然后继续寻找更多要交换的牌。也就是说,我回到第二步,在你选择的牌上再张开一些。

          在某个时候,这个循环(从 2 到 3)将结束。当搜索的两半在张开卡上相遇时,它就结束了。然后,我们刚刚用你在第一步中选择的牌张开了牌组。现在,所有靠近开始的牌都比张开牌低;接近尾端的牌比八字牌高。很酷的把戏!

          四个(这是有趣的部分):我现在有两个小套牌,一个比 splay 卡低,一个比 splay 高。现在我去第一步,在每个小甲板上!也就是说,我从第一个小甲板上的第一步开始,当这项工作完成后,我从下一个小甲板上的第一步开始。

          我把甲板分成几部分,对每一部分进行分类,越来越小,有时我没有更多的工作要做。现在这可能看起来很慢,有所有的规则。但相信我,它一点也不慢。这比第一种排序方法要少得多!

          这种类型叫什么?它被称为快速排序!这种排序是由一个叫C. A. R. Hoare 的人制作的,他称之为快速排序。现在,快速排序一直在使用!

          快速排序将大牌分解成小牌。也就是说,它把大任务分解成小任务。

          嗯。我想这里面可能有一个规则。为了使大任务变小,请将它们分解。

          这种排序很快。多快?大 O 告诉我们:在平均情况下,这种排序需要 O(n log n) 的工作来完成。

          它比第一种快还是快?大O,请帮忙!

          第一个排序是 O(n squared)。但是快速排序是 O(n log n)。您知道对于大 n,n log n 小于 n 的平方,对吧?好吧,这就是我们知道快速排序速度快的原因!

          如果您必须对牌组进行分类,最好的方法是什么?好吧,你可以随心所欲,但我会选择快速排序。

          为什么要选择快速排序?我当然不喜欢工作!我希望尽快完成工作。

          我怎么知道快速排序的工作量更少?我知道 O(n log n) 小于 O(n squared)。 O 更小,因此快速排序的工作量更少!

          现在你认识了我的朋友 Big O。他帮助我们减少工作量。而且,如果您了解大 O,您也可以少做一些工作!

          这些都是你跟我学的!你真聪明!非常感谢!

          现在工作已经完成,我们去玩吧!


          [1]:有一种方法可以作弊,一次将所有的东西从一加到n。一个名叫高斯的孩子在八岁时发现了这一点。不过我没那么聪明,所以don't ask me how he did it

          【讨论】:

            【解决方案22】:

            我有更简单的方法来理解时间复杂度 计算时间复杂度的最常用指标是大 O 表示法。这消除了所有常数因素,以便当 N 接近无穷大时,可以相对于 N 估计运行时间。一般来说,你可以这样想:

            statement;
            

            是恒定的。语句的运行时间不会相对于N改变

            for ( i = 0; i < N; i++ )
              statement;
            

            是线性的。循环的运行时间与N成正比。当N加倍时,运行时间也会增加。

            for ( i = 0; i < N; i++ ) 
            {
            for ( j = 0; j < N; j++ )
              statement;
            }
            

            是二次的。两个循环的运行时间与N的平方成正比。当N翻倍时,运行时间增加N * N。

            while ( low <= high ) 
            {
             mid = ( low + high ) / 2;
             if ( target < list[mid] )
             high = mid - 1;
             else if ( target > list[mid] )
              low = mid + 1;
            else break;
            }
            

            是对数的。算法的运行时间与N可以除以2的次数成正比。这是因为算法每次迭代都会将工作区域分成两半。

            void quicksort ( int list[], int left, int right )
            {
              int pivot = partition ( list, left, right );
              quicksort ( list, left, pivot - 1 );
              quicksort ( list, pivot + 1, right );
            }
            

            是 N * log ( N )。运行时间由N个对数循环(迭代或递归)组成,因此该算法是线性和对数的结合。

            一般来说,对一维中的每个项目做某事是线性的,对二维中的每个项目做某事是二次的,将工作区域分成两半是对数的。还有其他大 O 度量,例如三次、指数和平方根,但它们并不常见。大 O 表示法被描述为 O ( ) 其中是度量。快速排序算法将被描述为 O ( N * log ( N ) )。

            注意:这些都没有考虑到最佳、平均和最坏情况的衡量标准。每个都有自己的大 O 符号。另请注意,这是一个非常简单的解释。 Big O 是最常见的,但也比我展示的更复杂。还有其他符号,例如大 omega、小 o 和大 theta。您可能不会在算法分析课程之外遇到它们。

            • 查看更多信息:Here

            【讨论】:

              【解决方案23】:

              假设您从亚马逊订购了哈利波特:完整的 8 部电影合集 [蓝光],并同时在线下载相同的电影合集。您想测试哪种方法更快。交付需要将近一天的时间,下载大约提前 30 分钟完成。伟大的!所以这是一场激烈的比赛。

              如果我订购了《指环王》、《暮光之城》、《黑暗骑士三部曲》等多部蓝光电影并同时在线下载所有电影怎么办?这一次,交付仍然需要一天才能完成,但在线下载需要3天才能完成。 对于在线购物,购买商品(输入)的数量不影响交货时间。输出是恒定的。我们称之为 O(1)

              对于在线下载,下载时间与电影文件大小(输入)成正比。我们称之为 O(n)

              从实验中,我们知道在线购物比在线下载的规模更大。理解大 O 表示法非常重要,因为它可以帮助您分析算法的可扩展性效率

              注意:大 O 表示法表示算法的最坏情况。假设 O(1)O(n) 是上述示例的最坏情况。

              参考http://carlcheo.com/compsci

              【讨论】:

                【解决方案24】:

                假设我们正在讨论一种算法 A,它应该对大小为 n 的数据集执行一些操作。

                那么O( &lt;some expression X involving n&gt; )的意思,简单的英语:

                如果你在执行 A 时运气不好,可能需要 X(n) 次操作才能完成 完成。

                碰巧有一些函数(将它们视为X(n)实现)往往会经常出现。这些都是众所周知的并且很容易比较(例如:1Log NNN^2N! 等)

                在谈论 A 和其他算法时,通过比较这些,很容易根据算法可能(最坏情况)需要的操作数对算法进行排名完全的。

                一般来说,我们的目标是找到或构造一个算法A,使其具有一个函数X(n),该函数返回尽可能小的数字。

                【讨论】:

                  【解决方案25】:

                  如果你有一个合适的无限概念,那么有一个非常简短的描述:

                  大 O 表示法告诉您解决无限大问题的成本。

                  还有

                  常数因子可以忽略不计

                  如果您升级到可以以两倍速度运行您的算法的计算机,大 O 表示法不会注意到这一点。常数因子的改进太小了,以至于在大 O 表示法所使用的范围内甚至都无法注意到。请注意,这是大 O 符号设计的有意部分。

                  尽管可以检测到任何比常数因子“更大”的东西。

                  如果有兴趣进行大小“大”到足以被视为近似无穷大的计算,那么大 O 表示法大约是解决问题的成本。


                  如果以上没有意义,那么您的脑海中就没有一个兼容的直观的无限概念,您可能应该忽略以上所有内容;我所知道的使这些想法严谨的唯一方法,或者如果它们在直觉上还没有用,那么解释它们的唯一方法是首先教你大 O 符号或类似的东西。 (不过,一旦你将来很好地理解了大 O 符号,可能值得重新审视这些想法)

                  【讨论】:

                    【解决方案26】:

                    什么是“Big O”符号的简单英文解释?

                    非常快速的说明:

                    “Big O”中的 O 指的是“Order”(或准确地说是“order of”)
                    所以你可以从字面上理解它是用来订购一些东西来比较它们的。

                    • “Big O”做了两件事:

                      1. 估计您的计算机完成一项任务所采用的方法的步骤数。
                      2. 促进与其他人比较的过程以确定它是否好?
                      3. "Big O' 以标准化的Notations 实现了以上两个。
                    • 有七种最常用的符号

                      1. O(1),表示您的计算机通过1 步骤完成了一项任务,非常棒,已订购 1 号
                      2. O(logN),表示你的电脑完成了logN步骤的任务,很好,第2号订单
                      3. O(N),用N步骤完成任务,公平,订单号3
                      4. O(NlogN),以O(NlogN)步骤结束任务,不好,订单号4
                      5. O(N^2),用N^2 步骤完成任务,这很糟糕,订单号 5
                      6. O(2^N),用2^N步骤完成任务,太可怕了,6号订单
                      7. O(N!),用N! 步骤完成任务,太糟糕了,7 号订单

                    假设你得到了符号O(N^2),你不仅清楚该方法需要N*N步来完成一项任务,而且你从它的排名中看出它不如O(NlogN)

                    请注意行尾的顺序,以便您更好地理解。如果考虑所有可能性,则超过 7 个符号。

                    在 CS 中,完成任务的一组步骤称为算法。
                    在术语中,大 O 表示法用于描述算法的性能或复杂性。

                    此外,大 O 建立最坏情况或测量上限步骤。
                    最好的情况可以参考 Big-Ω (Big-Omega)。

                    Big-Ω (Big-Omega) notation (article) | Khan Academy

                    • 总结
                      “Big O”描述算法的性能并对其进行评估。

                      或正式地解决它,“Big O”对算法进行分类并标准化比较过程。

                    【讨论】:

                      【解决方案27】:

                      定义:- 大 O 表示法是一种表示如果数据输入增加,算法性能将如何执行的表示法。

                      当我们谈论算法时,算法的输入、输出和处理有 3 个重要支柱。 Big O 是符号表示法,表示如果数据输入增加,算法处理的性能会以什么速率变化。

                      我建议您观看这段 youtube 视频,该视频通过代码示例深入解释 Big O Notation

                      因此,例如假设一个算法需要 5 条记录,而处理这些记录所需的时间是 27 秒。现在,如果我们将记录增加到 10 条,算法需要 105 秒。

                      简单来说,所花费的时间是记录数的平方。我们可以用 O(n ^ 2) 来表示。这种符号表示称为大 O 表示法。

                      现在请注意,输入中的单位可以是任何单位,可以是字节、记录位数、性能可以用任何单位来衡量,例如秒、分钟、天等。所以它不是确切的单位,而是关系。

                      例如看下面的函数“Function1”,它接受一个集合并对第一条记录进行处理。现在,无论您放置 1000 、 10000 或 100000 条记录,此功能的性能都是相同的。所以我们可以用 O(1) 来表示它。

                      void Function1(List<string> data)
                      {
                      string str = data[0];
                      }
                      

                      现在看下面的函数“Function2()”。在这种情况下,处理时间将随着记录数量的增加而增加。我们可以使用 O(n) 来表示该算法的性能。

                      void Function2(List<string> data)
                              {
                                  foreach(string str in data)
                                  {
                                      if (str == "shiv")
                                      {
                                          return;
                                      }
                                  }
                              }
                      

                      当我们看到任何算法的大 O 表示法时,我们可以将它们分为三类性能:-

                      1. 日志和常量类别:- 任何开发人员都希望看到他们在此类别中的算法性能。
                      2. 线性:- 开发人员不希望看到此类别中的算法,直到其最后一个选项或仅剩的选项。
                      3. 指数:- 这是我们不希望看到我们的算法并且需要返工的地方。

                      因此,通过查看大 O 表示法,我们对算法的好坏区域进行了分类。

                      我建议您观看这个 10 分钟的视频,它通过示例代码讨论 Big O

                      https://www.youtube.com/watch?v=k6kxtzICG_g

                      【讨论】:

                        【解决方案28】:

                        查看它的最简单方法(用简单的英语)

                        我们试图了解输入参数的数量如何影响算法的运行时间。如果你的应用程序的运行时间与输入参数的数量成正比,那么就说它在 n 的大 O 中。

                        上述说法是一个好的开始,但并不完全正确。

                        更准确的解释(数学)

                        假设

                        n=输入参数个数

                        T(n)= 将算法的运行时间表示为 n 的函数的实际函数

                        c= 一个常数

                        f(n)= 一个近似函数,将算法的运行时间表示为 n 的函数

                        那么就大O而言,只要满足以下条件,近似f(n)就被认为足够好。

                        lim     T(n) ≤ c×f(n)
                        n→∞
                        

                        方程读作 当 n 接近无穷大时,n 的 T 小于或等于 n 的 c 乘以 f。

                        用大 O 表示法写成

                        T(n)∈O(n)
                        

                        这被读作 T of n 在 n 的大 O 中。

                        返回英文

                        根据上面的数学定义,如果您说您的算法是 n 的大 O,则意味着它是 n(输入参数的数量)或更快的函数。如果你的算法是 n 的大 O,那么它也自动是 n 平方的大 O。

                        n 的大 O 意味着我的算法运行速度至少和这个一样快。您不能查看算法的大 O 表示法并说它很慢。你只能说它很快。

                        请查看this,查看来自加州大学伯克利分校的 Big O 视频教程。这实际上是一个简单的概念。如果你听到 Shewchuck 教授(又名神级老师)解释它,你会说“哦,就是这样!”。

                        【讨论】:

                        • 视频链接已失效 :(
                        • 查找 CS 61B 第 19 讲:渐近分析
                        【解决方案29】:

                        我找到了一个关于大 O 符号的非常好的解释,特别是对于一个不太喜欢数学的人。

                        https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/

                        计算机科学中使用大 O 表示法来描述性能 或算法的复杂性。 Big O 专门描述了 最坏的情况,可以用来描述执行时间 所需的空间或使用的空间(例如在内存中或磁盘上) 算法。

                        任何阅读过编程珍珠或任何其他计算机科学的人 书籍和没有数学基础会碰壁 当他们到达提到 O(N log N) 或其他看似 疯狂的语法。希望这篇文章能帮助你获得 了解 Big O 和对数的基础知识。

                        首先是程序员,其次是数学家(或者可能是第三或 第四)我发现彻底了解 Big O 的最佳方法是 在代码中生成一些示例。所以,下面是一些常见的命令 增长,并尽可能提供描述和示例。

                        O(1)

                        O(1) 描述了一个总是同时执行的算法 (或空间)与输入数据集的大小无关。

                        bool IsFirstElementNull(IList<string> elements) {
                            return elements[0] == null;
                        }
                        

                        O(N)

                        O(N) 描述了一种算法,其性能将线性增长,并且 与输入数据集的大小成正比。这个例子 下面还演示了 Big O 如何支持最坏情况下的性能 设想;在任何迭代过程中都可以找到匹配的字符串 for 循环,函数会提前返回,但大 O 表示法会 总是假设算法将执行的上限 最大迭代次数。

                        bool ContainsValue(IList<string> elements, string value) {
                            foreach (var element in elements)
                            {
                                if (element == value) return true;
                            }
                        
                            return false;
                        } 
                        

                        O(N2)

                        O(N2) 表示一种算法,其性能直接 与输入数据集大小的平方成正比。这是 与涉及数据嵌套迭代的算法常见 放。更深的嵌套迭代将导致 O(N3)、O(N4) 等。

                        bool ContainsDuplicates(IList<string> elements) {
                            for (var outer = 0; outer < elements.Count; outer++)
                            {
                                for (var inner = 0; inner < elements.Count; inner++)
                                {
                                    // Don't compare with self
                                    if (outer == inner) continue;
                        
                                    if (elements[outer] == elements[inner]) return true;
                                }
                            }
                        
                            return false;
                        }
                        

                        O(2N)

                        O(2N) 表示一个算法,它的增长每增加一倍 输入数据集。 O(2N) 函数的增长曲线是 指数 - 开始非常浅,然后迅速上升。一个 O(2N) 函数的示例是斐波那契的递归计算 数字:

                        int Fibonacci(int number) {
                            if (number <= 1) return number;
                        
                            return Fibonacci(number - 2) + Fibonacci(number - 1);
                        }
                        

                        对数

                        对数解释起来有点棘手,所以我将使用一个常见的 示例:

                        二分搜索是一种用于搜索已排序数据集的技术。有用 通过选择数据集的中间元素,本质上是 中位数,并将其与目标值进行比较。如果值匹配它 将返回成功。如果目标值高于 探针元素,它将占据数据集的上半部分,并且 对它执行相同的操作。同样,如果目标值 低于它将执行的探测元件的值 对下半部分的操作。它将继续减半数据 在每次迭代中设置,直到找到该值或直到它可以 不再拆分数据集。

                        这种类型的算法被描述为 O(log N)。迭代减半 二分搜索示例中描述的数据集的数量会产生增长 在开始时达到峰值并随着尺寸逐渐变平的曲线 的数据集增加,例如包含 10 个项目的输入数据集 完成需要一秒钟,包含 100 个项目的数据集需要 2 秒,一个包含 1000 个项目的数据集需要 3 秒。将输入数据集的大小加倍几乎没有影响 它随着算法的单次迭代数据集的增长而增长 将减半,因此与输入数据集的一半相提并论 尺寸。这使得像二进制搜索这样的算法非常有效 在处理大型数据集时。

                        【讨论】:

                          【解决方案30】:

                          这是一个非常简单的解释,但我希望它涵盖了最重要的细节。

                          假设您处理问题的算法取决于一些“因素”,例如让我们将其设为 N 和 X。

                          根据 N 和 X,您的算法将需要一些操作,例如在最坏的情况下是 3(N^2) + log(X) 操作。

                          由于 Big-O 不太关心常数因子(也称为 3),因此您的算法的 Big-O 是 O(N^2 + log(X))。它基本上翻译了“您的算法在最坏情况下所需的操作量与此相关”。

                          【讨论】:

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