关于算法:什么是“大O”符号的简单英文解释?

What is a plain English explanation of “Big O” notation?

我更喜欢尽可能少的正式定义和简单的数学。


请注意,这几乎肯定会混淆大O符号(它是上界)和theta符号(它是两侧界)。根据我的经验,这实际上是非学术环境下的典型讨论。对造成的任何混乱表示歉意。好的。

使用此图可以直观地看到大的O复杂性:好的。

Big O Analysis好的。

对于big-o符号,我能给出的最简单定义是:好的。

大O符号是算法复杂性的相对表示。好的。

这句话中有一些重要的、故意选择的词:好的。

  • relative: you can only compare apples to apples. You can't compare an algorithm to do arithmetic multiplication to an algorithm that sorts a list of integers. But a comparison of two algorithms to do arithmetic operations (one multiplication, one addition) will tell you something meaningful;
  • representation: Big-O (in its simplest form) reduces the comparison between algorithms to a single variable. That variable is chosen based on observations or assumptions. For example, sorting algorithms are typically compared based on comparison operations (comparing two nodes to determine their relative ordering). This assumes that comparison is expensive. But what if comparison is cheap but swapping is expensive? It changes the comparison; and
  • complexity: if it takes me one second to sort 10,000 elements how long will it take me to sort one million? Complexity in this instance is a relative measure to something else.

当你读完剩下的部分后,回来重新阅读上面的内容。好的。

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

  • addition;
  • subtraction;
  • multiplication; and
  • division.

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

添加是最简单的。您将数字对齐(在右边),并将数字添加到一列中,将最后一个数字写入结果中。这个数字的"十"部分结转到下一列。好的。

假设这些数字的相加是该算法中最昂贵的运算。这就是为什么要把这两个数字加在一起,我们必须把6个数字加在一起(可能还有一个7)。如果我们把两个100位数字加在一起,我们就要做100个加法。如果我们加上两个10000位数字,就必须加10000位。好的。

看到图案了吗?复杂性(即操作次数)与较大数字中的位数n成正比。我们称之为O(N)或线性复杂性。好的。

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

乘法是不同的。你把数字排成一行,取下一个数字中的第一个数字,然后把它依次乘以上一个数字中的每个数字,依此类推。所以要将两个6位数相乘,我们必须做36次相乘。我们可能需要做多达10或11列的添加来获得最终结果。好的。

如果我们有两个100位数的数字,我们需要做10000次乘法和200次加法。对于两个一百万位数的数字,我们需要做一万亿(1012)的乘法和两百万的加法。好的。

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

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

机敏的人可能已经意识到,我们可以把操作数表示为:n2+2n。但正如你从我们的例子中看到的,每个操作数为100万位,第二项(2n)变得无关紧要(占该阶段总操作数的0.0002%)。好的。

我们可以注意到这里假设了最坏的情况。当6位数相乘时,如果其中一个是4位数,另一个是6位数,那么我们只有24个乘法。不过,我们还是计算了"n"的最坏情况,即当两者都是6位数时。因此,big-o表示法是一种算法的最坏情况。好的。电话簿

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

现在,如果你在一本电话簿中指示一台计算机查找"约翰·史密斯"的电话号码,其中包含了1000000个姓名,你会怎么做?忽略了这样一个事实,即你可以猜到S的起点有多远(假设你做不到),你会怎么做?好的。

一个典型的实现可能是向中间开放,取第500000个,并将其与"smith"进行比较。如果碰巧是"史密斯,约翰",我们就真的很幸运了。更有可能的是,"约翰·史密斯"会出现在这个名字之前或之后。如果是在那之后,我们把电话簿的后半部分分成两半,然后重复。如果在那之前,我们把电话簿的前半部分分成两半,然后重复。等等。好的。

这被称为二进制搜索,无论你是否意识到它,它每天都在编程中使用。好的。

所以,如果你想在一本有一百万个名字的电话簿中找到一个名字,你可以通过这样做最多20次来找到任何一个名字。在比较搜索算法时,我们认为这个比较是我们的"n"。好的。

  • For a phone book of 3 names it takes 2 comparisons (at most).
  • For 7 it takes at most 3.
  • For 15 it takes 4.
  • For 1,000,000 it takes 20.

太好了,不是吗?好的。

在big-o术语中,这是o(log n)或对数复杂性。现在,所讨论的对数可以是ln(底数e)、log10、log2或其他底数。这并不重要,它仍然是O(log n),就像O(2n2)和O(100n2)仍然都是O(n2)。好的。

在这一点上,值得解释的是,big o可以用一个算法来确定三种情况:好的。

  • Best Case: In the telephone book search, the best case is that we find the name in one comparison. This is O(1) or constant complexity;
  • Expected Case: As discussed above this is O(log n); and
  • Worst Case: This is also O(log n).

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

回到电话簿。好的。

如果你有电话号码,想找个名字怎么办?警方有一个反向的电话簿,但这样的查询被公众拒绝。或者是它们?从技术上讲,你可以在普通电话簿中反向查找一个号码。怎么用?好的。

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

因此,要查找给定电话号码的名称(反向查找):好的。

  • Best Case: O(1);
  • Expected Case: O(n) (for 500,000); and
  • Worst Case: O(n) (for 1,000,000).

旅行推销员

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

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

如果你有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种可能性。好的。

  • Take this to 4 towns and you have (iirc) 12 possibilities.
  • With 5 it's 60.
  • 6 becomes 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

所以旅行推销员的大问题是O(N!)或因子或组合复杂性。好的。

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

想些什么。好的。多项式时间

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

O(n)、O(n2)等都是多项式时间。有些问题在多项式时间内不能解决。因为这个世界上有一些东西被使用。公钥加密是一个主要的例子。在计算上很难找到一个很大数的两个素因子。如果不是,我们就不能使用我们使用的公钥系统。好的。

不管怎样,这是我(希望是简单的英语)对大O(修订版)的解释。好的。好啊。


它显示了一个算法是如何伸缩的。

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

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

注意,项目数量增加了10倍,但时间增加了102倍。基本上,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秒
  • 10000件:5秒

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

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


当忽略常量因子和原点附近的东西时,big-o符号(也称为"渐进增长"符号)就是函数"看起来"的样子。我们用它来讨论事物的尺度。好的。

基础好的。

对于"足够大"的输入…好的。

  • f(x) ∈ O(upperbound)表示f的生长速度不快于upperbound
  • f(x) ∈ ?(justlikethis)的意思是f长得和justlikethis一模一样。
  • f(x) ∈ Ω(lowerbound)表示f的生长不慢于lowerbound的生长。

big-o符号不关心常数因子:函数9x2被称为"增长完全像"10x2。big-o渐近表示法也不关心非渐近性的东西("接近原点的东西"或"当问题大小很小时会发生什么"):函数10x2被称为"完全像"10x2 - x + 2。好的。

你为什么要忽略方程中较小的部分?因为当你考虑到越来越大的尺度时,它们完全被方程的大部分所矮化;它们的贡献变得矮化和不相关。(请参见示例部分。)好的。

换一种说法,当你到达无穷大的时候,它是关于比率的。如果将实际花费的时间除以O(...),则在大输入限制中会得到一个常数因子。直观地说,这是有意义的:如果你能将一个函数相乘得到另一个函数,那么函数就"像"一个函数。也就是说,当我们说…好的。

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

…这意味着对于"足够大"的问题大小n(如果我们忽略了靠近原点的东西),存在一些常量(例如2.5,完全组成),这样:好的。

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

常数有很多种选择;通常"最佳"选择被称为算法的"常数因子"…但我们经常忽略它,就像忽略非最大项一样(参见常量因子一节了解它们通常不重要的原因)。你也可以把上面的方程看作是一个界限,说"在最坏的情况下,它所花费的时间永远不会比在2.5倍(我们不太关心的一个常数因子)的系数范围内的N*log(N)差得多"。好的。

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

应用好的。

作为一个纯粹的数学结构,big-o符号并不局限于讨论处理时间和内存。您可以使用它来讨论任何具有缩放意义的事物的渐进性,例如:好的。

  • 一方当事人(?(N2)特别是N(N-1)/2之间可能握手的次数,但重要的是它"像"N2那样缩放)
  • 概率预期的将病毒式营销视为时间函数的人数
  • 网站延迟如何随着CPU、GPU或计算机群集中处理单元的数量而扩展
  • CPU上的热输出如何随着晶体管计数、电压等的变化而消亡?
  • 作为输入大小的函数,一个算法需要运行多少时间
  • 作为输入大小的函数,一个算法需要运行多少空间

例子好的。

对于上面的握手示例,房间中的每个人都会握手。在这个例子中,#handshakes ∈ ?(N2)。为什么?好的。

后退一点:握手的次数正好是n-choose-2或N*(N-1)/2(n人中的每个人都会和n-1其他人握手,但这两次握手的次数除以2):好的。

everyone handshakes everyone else. Image credit and license per wikipedia/wikimedia commonsadjacency matrix号好的。

然而,对于非常多的人来说,线性术语N相形见绌,并有效地为比率贡献了0(在图表中:随着参与者数量的增加,对角线上的空盒子在总盒子上的比例会变小)。因此,缩放行为为order N2,或者握手次数"像n2一样增长"。好的。

1
2
3
#handshakes(N)
────────────── ≈ 1/2
     N2

就好像图表对角线上的空框(n*(n-1)/2个复选标记)根本不存在(n2个复选标记渐进)。好的。

(暂时背离"纯英语":)如果你想证明这一点,你可以用比例来做一些简单的代数,把它分解成多个项(lim的意思是"在极限中考虑",如果你没有看到它,就忽略它,它只是表示"n真的很大"):好的。

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

tl;dr:对于大的值来说,握手的次数"看起来像"x2太多了,以至于如果我们要记下握手/x2的比率,那么在任意大的时间内,我们不需要确切的x2握手的事实甚至不会出现在小数中。好的。

e.g. for x=1million, ratio #handshakes/x2: 0.499999...

Ok.

建筑直觉好的。

这让我们做出如下陈述…好的。

"For large enough inputsize=N, no matter what the constant factor is, if I double the input size...

Ok.

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

    n→(2n)=2(n)好的。< /块引用>

  • …一个O(n2)("二次时间")算法所用的时间是二次方(四倍)。(例如,一个问题100x大,它的长度是1002=10000x…可能不可持续)

    n2→(2n)2=4(n2)好的。< /块引用>

  • …我将O(n3)("立方时间")算法所用的时间加倍(八倍)。(例如,一个100倍大的问题需要1003=1000000倍长…非常不可持续)

    CN3→C(2N)3=8(CN3)好的。< /块引用>

  • …我在O(对数(n))(对数时间)算法所花费的时间中添加了一个固定的量。

    C日志(n)→C日志(2n)=(C日志(2))+(C日志(n))=(固定金额)+(C日志(n))好的。< /块引用>

  • …我不会改变O(1)("恒定时间")算法所用的时间。

    C*1~C*1好的。< /块引用>

  • …i"(基本上)是"O(n log(n))算法所用时间的两倍。"(相当常见)

    它小于0(n1.000001),你可以称之为基本线性好的。< /块引用>

  • …我荒谬地增加了O(2n)("指数时间")算法所花费的时间。"(你只需将问题增加一个单位,就可以使时间加倍(或三倍,等等)

    2n→22n=(4n)……换一种方式……2N→2N+1=2N21=2 2N好的。< /块引用>

[对于数学倾斜,您可以将鼠标移到扰流器上以获取次要的旁注]好的。

(请登录https://stackoverflow.com/a/487292/711085)好的。

(从技术上讲,常数可能在一些更为深奥的例子中很重要,但我在上面提到过(例如,在log(n)),所以它不重要)好的。

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

常数因子好的。

通常我们不关心具体的常数因子是什么,因为它们不会影响函数的增长方式。例如,两种算法都需要O(N)时间才能完成,但一种算法的速度可能是另一种算法的两倍。我们通常不会太在意,除非因素非常大,因为优化是棘手的业务(什么时候优化还为时过早?)另外,仅仅选择一个大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(N)次),加上一些任意数量的预处理(例如O(N)O(N log(N))O(N2),我们尽量保持较小的数据量。此后,修改数据结构(插入/删除等)和查询数据所花的时间非常少,如O(1)O(log(N))。然后继续进行大量查询!总的来说,你愿意提前做的工作越多,你以后做的工作就越少。好的。

例如,假设您拥有数百万个路段的经纬度坐标,并希望找到所有街道交叉口。好的。

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

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

实践示例:在编码时可视化增长顺序好的。

渐近符号的核心是与程序设计完全分离的。渐近符号是一个数学框架,用来思考事物的规模,可以在许多不同的领域中使用。说…这就是如何将渐近符号应用于编码。好的。

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

(这里,xs表示恒定的工作时间单位、处理器指令、解释程序操作码等)好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

如果我们做一些稍微复杂的事情,您可能仍然能够从视觉上想象发生了什么:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.5a^2),就像正方形是二维形状(a^2);这里的常量因子保持在两个因子之间的渐近比率,但是我们像所有因子一样忽略它…(这项技术有一些不幸的细微差别,我在这里没有提到,它会误导你。)好的。

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

还有一件事我们可以从视觉上识别出来:好的。

1
2
3
4
5
6
7
<----------------------------- 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):好的。

1
2
3
<----------------------------- 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

或者,您可能会记录(n)次数据,总时间为o(n*log(n)):好的。

1
2
3
4
5
6
   <----------------------------- 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)的一个因子。速度很快。好的。

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

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

如果我们做一些非常复杂的事情,例如使用递归函数或分而治之算法,You can use the master theorem(normally works),or in荒谬cases the akra bazzi theorem(most always works)You look up the running time of your algorithm on wikipedia.好的。

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

摊余和平均案例复杂性好的。

还有"摊余"和/或"平均情况"的概念(请注意,它们是不同的)。好的。

平均情况:这只不过是对函数的预期值使用big-o符号,而不是函数本身。在通常情况下,如果您认为所有输入都具有相同的可能性,那么平均情况只是运行时间的平均值。例如,对于QuickSort,即使最坏的情况是O(N^2),对于一些非常糟糕的输入,平均情况是通常的O(N log(N))(真正糟糕的输入数量非常少,所以很少,在一般情况下我们不会注意到它们)。好的。

摊余最坏情况:一些数据结构可能具有很大的最坏情况复杂度,但要保证,如果您执行其中许多操作,您所做的平均工作量将比最坏情况更好。例如,您可能有一个数据结构,通常采用常量O(1)时间。但是,偶尔会"打嗝",一次随机操作会占用O(N)时间,因为可能需要做一些簿记或垃圾收集之类的工作……但它向你保证,如果它打嗝了,就不会再打嗝了。最坏情况下的成本仍然是每次运营的O(N),但在许多运营中的摊余成本是O(N)/N=O(1)。由于大型作业非常罕见,因此可以认为大量的偶尔作业与其他作业作为一个常量混合在一起。我们说,这项工作是"分摊"在足够多的调用上,它会渐进地消失。好的。

The analogy for amortized analysis:

Ok.

You drive a car. Occasionally, you need to spend 10 minutes going to
the gas station and then spend 1 minute refilling the tank with gas.
If you did this every time you went anywhere with your car (spend 10
minutes driving to the gas station, spend a few seconds filling up a
fraction of a gallon), it would be very inefficient. But if you fill
up the tank once every few days, the 11 minutes spent driving to the
gas station is"amortized" over a sufficiently large number of trips,
that you can ignore it and pretend all your trips were maybe 5% longer.

Ok.

平均情况与摊余最坏情况的比较:好的。

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

但是,如果您合理地担心攻击者,除了摊销和平均情况外,还有许多其他算法攻击向量需要担心。)好的。

平均案例和摊销都是非常有用的工具,可以考虑按比例进行设计。好的。

(如果对此子主题感兴趣,请参阅平均案例和摊余分析之间的差异。)好的。

多维大O好的。

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

整个故事好的。

记住,大O并不是整个故事。通过使用高速缓存,可以大大加快一些算法的速度,使它们高速缓存时不被注意,通过使用RAM而不是磁盘来避免瓶颈,使用并行化,或者提前工作--这些技术通常独立于"big-o"表示法的增长顺序,尽管您经常会看到big-o表示法中核心的数量。关于并行算法。好的。

还要记住,由于程序的隐藏约束,您可能并不真正关心渐进行为。您可能正在处理有界的值数,例如:好的。

  • 如果您要对类似5个元素的内容进行排序,则不希望使用快速的O(N log(N))Quicksort;您希望使用插入排序,这恰好在小输入上表现良好。这些情况通常出现在分而治之的算法中,将问题分解成更小的子问题,如递归排序、快速傅立叶变换或矩阵乘法。
  • 如果某些值由于某些隐藏的事实而被有效地限定(例如,平均人的姓名被柔和地限定在40个字母左右,而人的年龄被柔和地限定在150左右)。您还可以对输入设置限制,以有效地使条件保持不变。

实际上,即使在具有相同或相似渐进性能的算法中,它们的相对优势实际上也可能由其他因素驱动,例如:其他性能因素(Quicksort和MergeSort都是O(N log(N)),但Quicksort利用了CPU缓存);非性能因素,例如易于实现;是否图书馆是可用的,以及图书馆的声誉和维护情况。好的。

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

上述问题,如编程语言,几乎从未被视为常量因素的一部分(也不应该被视为常量因素);然而,人们应该意识到它们,因为有时(尽管很少)它们可能会影响事物。例如,在cpython中,本地优先级队列实现是渐进非最优的(对于插入或查找min的选择,O(log(N))而不是O(1));是否使用其他实现?可能不会,因为C实现可能更快,其他地方可能还有其他类似的问题。存在着权衡;有时它们很重要,有时却不重要。好的。

(编辑:"纯英语"解释到此结束。)好的。

数学附录好的。

对于完备性,大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是顺序……/f是以……为界的xxx,被认为是"f是一个渐进式为……"的表达式。我被教导使用更严格的∈ O(...)的意思是"是的一个元素"(仍然如前所述)。O(N2)实际上是一个等价类,也就是说,它是一组我们认为相同的东西。在这种特殊情况下,O(N2)包含诸如2 N23 N21/2 N22 N2 + log(N)- N2 + N^1.9等元素,并且是无限大的,但它仍然是一个集合。=符号可能是更常见的符号,甚至被世界著名的计算机科学家在论文中使用。此外,在偶然的情况下,人们通常会说O(...),当他们的意思是?(...)时;这在技术上是正确的,因为?(exactlyThis)O(noGreaterThanThis)的一个子集…而且打字更容易。;-)好的。好啊。


编辑:请注意,这几乎肯定会混淆大O符号(它是上界)和theta符号(它既是上界又是下界)。根据我的经验,这实际上是非学术环境下的典型讨论。对造成的任何混乱表示歉意。

一句话:随着工作规模的增加,完成工作需要多长时间?

显然,这只是使用"大小"作为输入,"所用时间"作为输出&mdash;如果您想谈论内存使用等,同样的想法也适用。

这是一个例子,我们有N件T恤,我们想晾干。我们会假设让它们处于干燥状态是非常快的(也就是说,人类的相互作用可以忽略不计)。当然,现实生活并非如此……

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

  • 使用滚筒式烘干机:你把10件衬衫放在每件衣服里,一个小时后就完成了。(忽略此处的实际数字,它们无关紧要。)因此,烘干50件衬衫所需时间大约是烘干10件衬衫所需时间的5倍。

  • 把所有的东西都放在一个通风橱里:如果我们把所有的东西都放在一个大堆里,让一般的温暖来做,中间的衬衫要花很长时间才能干。我不想猜测细节,但我怀疑这至少是O(n^2)&mdash;随着洗涤量的增加,干燥时间增加得更快。

"大O"符号的一个重要方面是,它没有说明哪种算法在给定的大小下更快。取一个哈希表(字符串键,整数值)和一组对(字符串,整数值)。根据字符串,在哈希表中查找键或在数组中查找元素是否更快?(即,对于数组,"查找字符串部分与给定键匹配的第一个元素")哈希表通常被摊销(~="平均")o(1)&mdash;一旦设置了哈希表,在100个条目表中查找条目的时间应该与在1000000个条目表中查找条目的时间相同。在数组中查找元素(基于内容而不是索引)是线性的,即O(N)&mdash;平均而言,您必须查看一半的条目。

这是否使哈希表的查找速度比数组快?不一定。如果您有一个非常小的条目集合,一个数组可能会更快,您可以在计算所查看的哈希代码所需的时间内检查所有字符串。然而,随着数据集变大,哈希表最终将击败数组。


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

实例:

  • O(N):如果我将输入大小增加一倍,运行时将增加一倍

  • o(n2):如果输入大小加倍运行时四倍

  • o(log n):如果输入大小翻倍,则运行时增加1。

  • o(2n):如果输入大小增加1,则运行时将加倍

输入大小通常是表示输入所需的位空间。


大O标记法是最常用的措施是在程序员知识近似长a计算(算法)将以完整的表达作为一个功能的输入集的大小。

大O是一个有用的知识。要想把双尺度算法的输入是一个为数增加。

更精确的大O符号是用来表示的渐近行为的功能。这是因为它的功能如何behaves均值的方法无限。

在许多情况下,在"O"想为一个算法的情况下,下面的案例:

  • O(1)时间完成他们的大小是一样的输入集。例子是在访问数组元素的索引。
  • O(log n)时间完成约增加线与log2(n)。例如1024项需要约两次只要32项,因为log2(1024)= 10 = 5和log2(32)。在一项调查中,在A的例子二进制搜索树(BST)。
  • O(n)时间完成这linearly和尺度大小的输入集。换句话说,如果你双数项集的算法需要输入两次,约为长。在一个例子中的项目计数数a联列表。
  • O(n日志n)时间完成的项目数增加了时代的log2(n)的结果。本例是一个快速排序和堆排序。
  • O(N ^ 2)的时间是约完全平等友好广场的项目数。本例是一个冒泡排序。
  • O(n!)时间因素是在完成输入。在这个例子的问题是旅行salesman蛮力解决方案。

大O盘的因素是不以有意义的方式在一个生长曲线函数作为输入一个无限大小的增加。这意味着添加到常数是由简单的功能倍增或是被忽略。


大O只是用一种常见的方式"表达"自己,"运行代码需要多少时间/空间?".

你可能经常看到O(n),O(n2),O(nlogn)等等,所有这些都只是展示的方法;算法是如何改变的?

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

因此,你可能理解"n2"的含义,但更具体地说,你可以利用一个简单的,最简单的排序算法:冒泡排序。这个算法需要查看每个项目的整个列表。

我的名单

  • 这里的流程是:

    • 比较1和6,哪个最大?好的,6在正确的位置,向前移动!
    • 比较6和3,哦,3更少!让我们开始吧,好了,名单变了,我们需要从现在开始!

    这是O n2,因为您需要查看列表中的所有项目,其中有"n"个项目。对于每个项目,您将再次查看所有项目,为了进行比较,这也是"n",因此对于每个项目,您将看到"n"倍,表示n*n=n2

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

    但请记住,大O只是一种以时间和空间的方式体验自己的方式。


    big o描述了算法的基本伸缩性。

    关于一个给定的算法,BigO没有告诉你很多信息。它切掉了重点,只提供了有关算法伸缩性的信息,特别是算法的资源使用(思考时间或内存)如何根据"输入大小"进行伸缩。

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

    为什么这么重要?因为软件处理的问题的大小可能会因多达一万亿个因素而不同。考虑一下。到月球旅行所需的速度与人类行走速度之比小于10000:1,这与软件可能面临的输入尺寸范围相比是非常微小的。而且,由于软件可能面临输入大小的天文范围,因此算法的大O复杂性是潜在的,因此它的基本伸缩性优于任何实现细节。

    考虑规范排序示例。气泡排序是O(n2),而合并排序是O(n log n)。假设您有两个排序应用程序,应用程序A使用气泡排序,应用程序B使用合并排序,并且假设对于大约30个元素的输入大小,应用程序A的排序速度比应用程序B快1000倍。如果您从不需要对超过30个元素进行排序,那么很明显您应该更喜欢应用程序A,因为它在这些输入大小下更快。但是,如果您发现可能需要对一千万个项目进行排序,那么您期望的是,在这种情况下,应用程序B实际上比应用程序A快数千倍,这完全是由于每个算法的伸缩方式。


    这是我在解释大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)。

    O(N2):

    以正方形的形式增长,其中n是正方形边的长度。这与"网络效应"的增长率相同,网络中的每个人都可能认识网络中的其他人。增长是昂贵的。大多数可扩展的解决方案不能使用这种复杂程度的算法,而不进行重要的体操。这通常也适用于所有其他多项式复杂度-O(nk)。

    O(2n):

    不缩放。你没有希望解决任何非琐碎大小的问题。有助于知道要避免什么,专家可以找到O(nk)中的近似算法。


    大O是一个衡量算法相对于其输入大小使用多少时间/空间的指标。

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

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

    等等。


    What is a plain English explanation of Big O? With as little formal definition as possible and simple mathematics.

    关于大O符号需要的简单英语解释:

    当我们编程时,我们正试图解决一个问题。我们编写的代码称为算法。大O符号允许我们以标准化的方式比较我们的算法的糟糕情况性能。硬件规格随时间变化,硬件的改进可以减少运行算法所需的时间。但是替换硬件并不意味着我们的算法会随着时间的推移而更好或改进,因为我们的算法仍然是相同的。所以为了让我们比较不同的算法,判断一个算法是否更好,我们使用大O符号。

    一个简单的英语解释什么是大O符号:

    并非所有的算法都在相同的时间内运行,并且可以根据输入中的项数而变化,我们称之为n。基于此,我们考虑更糟的情况分析,或者运行时的上限,因为n越来越大。我们必须知道N是什么,因为许多大O符号都引用了它。


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

    由于所有这些无用的复杂性,人们试图用尽可能最小和最不复杂(数学)的表达式来描述软件程序的速度。这些表达式是非常粗糙的近似值:尽管幸运的是,它们将捕获一个软件是快还是慢的"本质"。

    因为它们是近似值,所以我们在表达式中使用字母"o"(big oh),作为一种惯例,向读者表示我们正在进行一个严重的过于简单化。(并确保没有人错误地认为这个表达在任何方面都是准确的)。

    如果你按照"或近似"的顺序把"哦"读作"意思",你就不会错得太多。(我认为选择"大哦"可能是一种幽默的尝试)。

    这些"big-oh"表达式唯一要做的就是描述当我们增加软件必须处理的数据量时,软件的速度会减慢多少。如果我们将需要处理的数据量增加一倍,软件完成工作是否需要两倍的时间?十倍长?在实践中,您将遇到和需要担心的大量"哦"表达式非常有限:

    好:

    • O(1)常量:无论输入有多大,程序运行都需要相同的时间。
    • O(log n)对数:程序运行时间增长缓慢,即使输入的大小增长很大。

    坏处:

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

    …丑陋的:

    • O(k^n)指数化程序运行时间增长很快,问题的规模甚至适度增加——只有用指数算法处理小数据集才是可行的。
    • O(n!)factorial程序运行时间将比您所能承受的等待时间长,除了最小和最微不足道的表面数据集。


    好的,我的2分钱小费。

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

    资源:可以完全的CPU时间,内存空间是最大的。是指的是一个默认的CPU时间。

    说,问题是,"一个新的

    1
    2
    3
    4
    5
    6
    7
    int Sum(int*arr,int size){
          int sum=0;
          while(size-->0)
             sum+=arr[size];

          return sum;
    }

    问题实例={}==>伐问题实例的大小= 3 = 3,在循环迭代

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

    of size for输入"N"的程序是在"N"的成长速度在数组迭代。从一个大O表示为O(n)

    说,问题是,"一个结合"

    1
    2
    3
    4
    5
    6
    7
    8
        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;
          }
        }

    问题实例={}==>伐问题实例的大小= 3,总迭代=3×3 = 9

    问题5,10,15,20,25实例= { } = >问题实例的总尺寸=5,=5×5=25迭代

    of size for输入"N"的程序是在高速成长的"n"在数组迭代。因此,大O是N2 O(n2)的表达。


    简单明了的答案可以是:

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


    大O符号是一个被描述的方式上对算法在运行时间和空间条件。一个数N的元素,在漫游的问题(即数组的大小,数量的节点在树的,我们有兴趣在描述等)的运行时间为N变大。

    当我们说一些算法是O(f(n))说,"我们的运行时间(或空间的需要)到那个算法总是低于一些常数,f(n)的时代。

    有一个说的二进制搜索的运行时间为O(logn)是说有一些常数c值你可以乘log(n)到那个永远是更大的比二进制搜索的运行时间。在这个案例,你总是有一些常数因子日志(n)的比较。

    换句话说,g(n)运行时间的算法是,我们说G(n)= O(f(n))当g(n)<=C×f(n),当n>k,C和K是在一些常数。


    "What is a plain English explanation of Big O? With as little formal
    definition as possible and simple mathematics."

    这样一个漂亮的简单而简短的问题至少应该得到一个同样简短的答案,就像一个学生在辅导时可能得到的答案一样。

    Big O notation simply tells how much time* an algorithm can run within,
    in terms of only the amount of input data**.

    (*在一个美妙的、没有单位的时间感中!)(**这才是最重要的,因为无论是今天还是明天,人们总是想要更多)

    好吧,大O符号有什么了不起的,如果这就是它的作用?

    • 实际上,big o分析非常有用,也非常重要,因为big o将重点直接放在算法本身的复杂性上,完全忽略了任何仅是比例常数的东西,如javascript引擎、CPU的速度、您的互联网连接以及所有那些很快变得可笑的东西。作为一个过时的T.BigO模型,只关注那些对现在或未来的人们同样重要的表现。

    • 大O符号也直接将焦点放在计算机编程/工程最重要的原则上,这一事实激励所有优秀的程序员不断思考和梦想:除了技术缓慢前进之外,实现结果的唯一方法就是发明更好的算法。


    算法示例(Java):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // given a list of integers L, and an integer K
    public boolean simple_search(List<Integer> L, Integer K)
    {
        // for each integer i in list L
        for (Integer i : L)
        {
            // if i is equal to K
            if (i == K)
            {
                return true;
            }
        }

        return false;
    }

    算法说明:

    • 这个算法搜索一个列表,一个项目一个项目,寻找一个键,

    • 对列表中的每个项进行迭代,如果它是键,则返回true,

    • 如果循环在未找到键的情况下完成,则返回false。

    big-o表示复杂度(时间、空间等)的上限。

    要找到大O准时复杂性:

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

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

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

    • 时间:o(4n+1)=o(n)在big-o中,常数被忽略

    • O(n)~线性

    还有一个巨大的欧米茄,它代表了最佳案例的复杂性:

    • 最佳情况:关键是第一项。

    • 时间(最佳情况)=4

    • 时间:Ω(4)=O(1)~瞬间常数


    大O

    f(x)=o(g(x))当x到a(例如a=+∞)时,意味着有一个函数k,这样:

  • f(x)=k(x)g(x)

  • k是在a的某个邻域内有界的(如果a=+∞,这意味着有个数n和m,因此对于x>n,k(x)

  • 换句话说,在纯英语中:f(x)=o(g(x)),x→a表示在a的一个邻域中,f分解为g和某个有界函数的乘积。

    小O

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

    f(x)=o(g(x))当x表示存在函数k时:

  • f(x)=k(x)g(x)

  • 当x到a时,k(x)变为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))为假。同样,当x→+∞时,可以写"ln(x)=o(x)",但公式"o(x)=ln(x)"没有意义。

    更多例子

    • o(1)=o(n)=o(n2)当n→+∞(但不是反过来,等式是"假"),

    • 当n→+时,o(n)+o(n2)=o(n2)

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

    • 当n→+时,o(n2)o(n3)=o(n5)

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


    大O符号是一种描述算法在给定任意数量的输入参数(我们称之为"n")下运行速度的方法。它在计算机科学中很有用,因为不同的机器以不同的速度运行,简单地说一个算法需要5秒钟并不能告诉你多少,因为当你运行一个4.5GHz的OCTO核心处理器的系统时,我可能运行一个15年前的800MHz系统,不管算法如何,这可能需要更长的时间。因此,我们不指定一个算法在时间上的运行速度,而是根据输入参数的数量,或者"n"来表示它的运行速度。通过这样描述算法,我们可以比较算法的速度,而不必考虑计算机本身的速度。


    不确定我是否对这个主题做了进一步的贡献,但我仍然认为我会分享:我曾经发现这个博客文章在Big O上有一些非常有用的(尽管非常基本)解释和示例:

    通过例子,这有助于我的龟壳式头骨的基础,所以我认为这是一个相当下降10分钟阅读,以使你朝正确的方向。


    你想知道所有关于大O的事情吗?我也是。好的。

    所以说大O,我会用只有一个节拍的词。每个单词一个声音。小词很快。你知道这些词,我也知道。我们会用一个声音的词。它们很小。我相信你会知道我们将使用的所有词语!好的。

    现在,让我们谈谈工作。大多数时候,我不喜欢工作。你喜欢工作吗?也许你会这样做,但我确信我不会。好的。

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

    现在有时候,我真的要去工作了。这是可悲的,但却是真实的。所以,当我在工作的时候,我有一条规则:我尽量少工作。几乎没有工作。然后我去玩!好的。

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

    现在我有一些工作。我有这个单子:一,二,三,四,五,六。我必须把所有的东西都加入这个名单。好的。

    哇,我讨厌工作。但哦,好吧,我必须这么做。所以我走了。好的。

    一加二等于三…加三等于六…四是…我不知道。我迷路了。这对我来说太难了。我不太喜欢这种工作。好的。

    所以我们不要做这项工作。让我们想想这有多难。我要做多少工作才能加上六个数字?好的。

    好吧,让我们看看。我必须加一加二,然后再加三,然后再加四……总之,我加六。为了解决这个问题,我必须做六次加法。好的。

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

    大O说:我们必须做六个补充来解决这个问题。一加一,从一到六。六小部分工作…每一位工作都是一个加法。好的。

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

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

    现在他们要我从1加到10!我为什么要这样做?我不想再加一比六。从1加到10……好吧……那就更难了!好的。

    会有多困难?我还要做多少工作?我需要多少步?好的。

    好吧,我想我得做十个加法……从一到十的每件事加一个。十大于六。我要从1到10再加上1到6,就得多工作!好的。

    我现在不想添加。我只想想想增加这么多可能有多困难。我希望能尽快上场。好的。

    加上从1到6,这是一些工作。但是你看到了吗,从1到10再加上,这是更多的工作?好的。

    大O是你和我的朋友。大O帮助我们思考我们需要做多少工作,这样我们才能计划。而且,如果我们是大O的朋友,他可以帮助我们选择不那么难的工作!好的。

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

    新的工作是:将所有的东西从一个添加到n。好的。

    等待!什么是N?我错过了吗?如果你不告诉我n是什么,我怎么从1加到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是我们的朋友。好的。

    大O指出:当N变大时,当我们分类卡片时,工作变得比旧的要困难得多,只需添加这些东西。我们怎么知道的?好的。

    好吧,如果n变大了,我们不关心n或n平方加什么。好的。

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

    大O告诉我们排序比添加东西更难。o(n平方)大于o(n),这意味着:如果n变大了,对n的混合组进行排序必须花费更多的时间,而不仅仅是添加n个混合组。好的。

    大O不能解决我们的工作。大O告诉我们工作有多辛苦。好的。

    我有一副牌。我把它们分类了。你帮了忙。谢谢。好的。

    有更快速的分类方法吗?大O能帮我们吗?好的。

    是的,有一条更快的路!学习需要一些时间,但它是有效的…而且工作很快。你也可以尝试一下,但是每走一步都要慢慢来,不要失去你的位置。好的。

    用这种新的分类方法,我们不会像前一段时间那样检查成对的卡片。以下是您对这一组进行排序的新规则:好的。

    一:我在我们现在工作的那部分选择了一张牌。如果你愿意的话,你可以给我选一个。(我们第一次这样做,"我们现在工作的那部分"当然是整个甲板。)好的。

    第二:我在你选的那张牌上放牌。这是什么伸展;我该如何伸展?好吧,我从开始牌开始,一张一张地往下走,我要找一张比八字牌高的牌。好的。

    三:我从最后一张牌开始,寻找一张比八字牌低的牌。好的。

    一旦我找到这两张卡,我就交换它们,然后继续寻找更多的卡来交换。也就是说,我回到第二步,在你选择的卡片上再加一些。好的。

    在某个时刻,这个循环(从2到3)将结束。当搜索的两部分都在splay卡上相遇时,它就结束了。然后,我们就用您在第一步中选择的卡将牌组展开。现在,所有靠近开始的牌都比伸展牌低;靠近结束的牌比伸展牌高。酷把戏!好的。

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

    我把甲板分成几部分,把每一部分分类,越来越小,有时我就没有更多的工作要做了。现在,按照所有的规则,这看起来可能很慢。但相信我,这一点都不慢。这比第一种分类方法要少得多!好的。

    这种叫什么?这叫做快速排序!这是由一个叫C.A.R.霍尔的人做的,他称之为"快速排序"。现在,快速排序总是被使用!好的。

    快速分拣把大甲板分成小甲板。也就是说,它把大任务分成小任务。好的。

    嗯。我想可能有一条规则。要使大任务变小,就把它们分开。好的。

    这种速度很快。多快?大O告诉我们:在一般情况下,这种类型需要做O(n log n)工作。好的。

    它比第一种快多少?大O,请帮忙!好的。

    第一类是O(n平方)。但快速排序是O(n log n)。你知道n对数n小于n的平方,对于大n,对吗?好吧,这就是我们如何知道快速排序是快速的!好的。

    如果你要整理一个甲板,最好的方法是什么?好吧,你可以做你想做的,但我会选择快速排序。好的。

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

    我怎么知道快速分拣工作少?我知道O(n对数n)小于O(n平方)。O型的比较小,所以快速排序的工作就少了!好的。

    现在你认识我的朋友,大O。他帮助我们少做工作。如果你知道大O,你也可以做更少的工作!好的。

    你和我一起学的!你真聪明!非常感谢!好的。

    既然工作完成了,我们去玩吧!好的。

    【1】:有一种方法可以同时欺骗和添加从1到n的所有内容。有个叫高斯的孩子在八岁的时候发现了这个。不过我没那么聪明,所以别问我他是怎么做到的。好的。好啊。


    假设你从亚马逊订购了《哈利波特:完成8部电影集》(Blu-ray),同时在线下载同一部电影集。您要测试哪个方法更快。交付需要将近一天的时间,下载大约提前30分钟完成。伟大的!所以这是一场激烈的比赛。

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

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

    通过实验,我们知道网上购物比网上下载更具规模。理解大O符号非常重要,因为它可以帮助您分析算法的可伸缩性和效率。

    注:大O符号表示算法的最坏情况。让我们假设O(1)和O(n)是上述示例的最坏情况。

    参考:http://carlcheo.com/compsci


    我有更简单的方法来理解时间的复杂性计算时间复杂性最常用的度量是大O符号。这将删除所有常量因子,以便在n接近无穷大时,可以相对于n估计运行时间。一般来说,你可以这样想:

    1
    statement;

    是常数。语句的运行时间相对于n不会改变

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

    是线性的。循环的运行时间与n成正比。n加倍时,运行时间也成正比。

    1
    2
    3
    4
    5
    for ( i = 0; i < N; i++ )
    {
    for ( j = 0; j < N; j++ )
      statement;
    }

    是二次的。两个回路的运行时间与n的平方成正比,当n加倍时,运行时间增加n*n。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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的次数成正比。这是因为算法在每次迭代中将工作区划分为两部分。

    1
    2
    3
    4
    5
    6
    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符号。还要注意,这是一个非常简单的解释。大O是最常见的,但我展示的更复杂。还有其他符号,如大欧米伽、小欧米伽和大西塔。在算法分析课程之外,您可能不会遇到它们。

    • 更多信息请访问:这里

    假设我们讨论的是算法A,它应该对大小为n的数据集做些什么。

    那么,O( )的意思是,用简单的英语来说:

    If you're unlucky when executing A, it might take X(n) operations to
    complete.

    事实上,有些函数(将它们视为x(n)的实现)经常发生。这些是众所周知的,并且很容易比较(例如:1Log NNN^2N!等)。

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

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


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

    Big O notation tells you the cost of solving an infinitely large problem.

    此外

    Constant factors are negligible

    如果你升级到一台能以两倍的速度运行你的算法的计算机,BigO符号不会注意到这一点。常量因子的改进太小,以至于在大O符号所适用的尺度上都无法被注意到。请注意,这是大O符号设计的有意部分。

    然而,尽管可以检测到比常数因子大的任何东西。

    当有兴趣做"大"到可以被视为近似无穷大的计算时,大O符号大约是解决问题的成本。

    如果上面没有意义,那么你的头脑中就没有一个兼容的直觉无限概念,你可能会忽略上面的所有内容;我知道的唯一方法就是让这些想法变得严格,或者解释它们,如果它们还没有直观的用处,那就是先教你大O符号或者类似的东西。(尽管,一旦你很好地理解了大O符号的未来,重新审视这些想法可能是值得的)


    What is a plain English explanation of"Big O" notation?

    非常快注意:

    "大O"中的O表示"顺序"(或确切地说"顺序")。所以你可以从字面上理解它是用来排序比较的。

    • "大O"有两个功能:

    • 估计计算机应用此方法完成任务的步骤数。
    • 促进过程与其他人进行比较,以确定其是否良好?
    • "大O"用标准化的Notations实现了上述两个目标。
    • 有七种最常用的符号

    • O(1),意味着你的电脑完成了一项任务,用1步骤,非常好,订购了1号。
    • o(logn),表示您的计算机完成了一项带有logN个步骤的任务,它是好的,订购了2号。
    • o(n),用N步骤完成任务,公平,第3号命令。
    • o(nlogn),以O(NlogN)步结束任务,不好,订单号4
    • o(n^2),用N^2步骤完成一项任务,很糟糕,5号命令。
    • o(2^n),用2^N步骤完成任务,太可怕了,6号命令。
    • O(n)!用N!步骤完成任务,太可怕了,7号命令

    enter image description here

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

    请注意行末的订单,以便您更好地理解。如果考虑到所有可能的情况,有7个以上的符号。

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

    此外,big o建立最坏情况或测量上限步骤。你可以参考大Ω(大欧米茄)的最佳情况。

    大欧米加符号(文章)汗学院

    • 总结"大O"描述了算法的性能并对其进行了评估。

      或者正式地解决这个问题,"大O"对算法进行了分类,并对比较过程进行了标准化。


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

    我们试图了解输入参数的数量如何影响算法的运行时间。如果应用程序的运行时间与输入参数的数量成比例,那么就称其为n的大o。

    上述说法是一个良好的开端,但并非完全正确。

    更准确的解释(数学)

    假设

    n=输入参数数量

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

    C=常数

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

    那么,对于大O来说,只要下面的条件成立,近似f(n)就足够好了。

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

    该方程被理解为当n接近无穷大时,n的t小于或等于n的c乘以f。

    在大O符号中,这写为

    1
    T(n)∈O(n)

    这是因为n的t在n的大o中。

    回到英语

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

    N的大O意味着我的算法运行速度至少和这个一样快。你不能看你的算法的大O符号,说它慢。你只能说它很快。

    从加州大学伯克利分校(UCBerkley)的BigO视频教程中可以看到这一点。它实际上是一个简单的概念。如果你听到休查克教授(又名神级教师)解释的话,你会说:"哦,就是这样!".


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

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

    Big O notation is used in Computer Science to describe the performance
    or complexity of an algorithm. Big O specifically describes the
    worst-case scenario, and can be used to describe the execution time
    required or the space used (e.g. in memory or on disk) by an
    algorithm.

    Anyone who's read Programming Pearls or any other Computer Science
    books and doesn’t have a grounding in Mathematics will have hit a wall
    when they reached chapters that mention O(N log N) or other seemingly
    crazy syntax. Hopefully this article will help you gain an
    understanding of the basics of Big O and Logarithms.

    As a programmer first and a mathematician second (or maybe third or
    fourth) I found the best way to understand Big O thoroughly was to
    produce some examples in code. So, below are some common orders of
    growth along with descriptions and examples where possible.

    O(1)

    O(1) describes an algorithm that will always execute in the same time
    (or space) regardless of the size of the input data set.

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

    O(N)

    O(N) describes an algorithm whose performance will grow linearly and
    in direct proportion to the size of the input data set. The example
    below also demonstrates how Big O favours the worst-case performance
    scenario; a matching string could be found during any iteration of the
    for loop and the function would return early, but Big O notation will
    always assume the upper limit where the algorithm will perform the
    maximum number of iterations.

    1
    2
    3
    4
    5
    6
    7
    8
    bool ContainsValue(IList<string> elements, string value) {
        foreach (var element in elements)
        {
            if (element == value) return true;
        }

        return false;
    }

    O(N2)

    O(N2) represents an algorithm whose performance is directly
    proportional to the square of the size of the input data set. This is
    common with algorithms that involve nested iterations over the data
    set. Deeper nested iterations will result in O(N3), O(N4) etc.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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) denotes an algorithm whose growth doubles with each additon to
    the input data set. The growth curve of an O(2N) function is
    exponential - starting off very shallow, then rising meteorically. An
    example of an O(2N) function is the recursive calculation of Fibonacci
    numbers:

    1
    2
    3
    4
    5
    int Fibonacci(int number) {
        if (number <= 1) return number;

        return Fibonacci(number - 2) + Fibonacci(number - 1);
    }

    Logarithms

    Logarithms are slightly trickier to explain so I'll use a common
    example:

    Binary search is a technique used to search sorted data sets. It works
    by selecting the middle element of the data set, essentially the
    median, and compares it against a target value. If the values match it
    will return success. If the target value is higher than the value of
    the probe element it will take the upper half of the data set and
    perform the same operation against it. Likewise, if the target value
    is lower than the value of the probe element it will perform the
    operation against the lower half. It will continue to halve the data
    set with each iteration until the value has been found or until it can
    no longer split the data set.

    This type of algorithm is described as O(log N). The iterative halving
    of data sets described in the binary search example produces a growth
    curve that peaks at the beginning and slowly flattens out as the size
    of the data sets increase e.g. an input data set containing 10 items
    takes one second to complete, a data set containing 100 items takes
    two seconds, and a data set containing 1000 items will take three
    seconds. Doubling the size of the input data set has little effect on
    its growth as after a single iteration of the algorithm the data set
    will be halved and therefore on a par with an input data set half the
    size. This makes algorithms like binary search extremely efficient
    when dealing with large data sets.


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

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

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

    因为big-o不太关心常量因子(aka 3),所以您的算法的big-o是O(N^2 + log(X))。它基本上转换为"您的算法在最坏情况下所需的操作量"。


    定义:大O notation的notation说,技术的算法的性能将执行,如果输入的数据会增加。 P / < >

    当我们谈论算法有重要的pillars 3的输入,输出和处理的算法。大O也symbolic notation说,如果输入的数据也将在increased所率的性能或处理的算法。 P / < >

    我encourage你看到这个视频的YouTube,explains大O notation在depth与examples code。 P / < >

    Algorithm basic pillars P / < >

    所以例如,为此承担的算法需要5和记录的时间需要为处理同样也27秒。现在,如果我们增加记录到10的算法需要105秒。 P / < >

    在简单的话,时间也与广场之数的记录。我们可以denote这= O(N ^ 2)。这也symbolic representation运动型大O notation。 P / < >

    现在请你注意的单位可以在任何inputs它可以字节,位号的记录性能,可以在任何measured单位像第二次,分钟,天和等等。所以它的不精确,但单位而同步的关系。 P / < >

    Big O symbols P / < >

    例如在看下面的功能"function1",需要收集和处理并在第一的记录。现在对这个功能的性能将得到同样的irrespective你把1000 10000 100000或记录。所以,我们可以通过它denote O(1)。 P / < >

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

    现在看到下面的功能"function2()"。在这个案例的处理时间会增加与数的记录。我们可以denote这个算法的性能使用O(N)。 P / < >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void Function2(List<string> data)
            {
                foreach(string str in data)
                {
                    if (str =="shiv")
                    {
                        return;
                    }
                }
            }

    当我们看到的大O notation为简单的算法,我们可以在他们classify到三种吸附性能: P / < >

  • 日志和常数类别:- developer会爱看他们的算法的性能在这一类。
  • 线性口袋- developer会不想看到算法在这一类,直到它的最后一个选项或离开的唯一选项。
  • exponential:"这是我们在做的不是想看到我们的算法和rework也是需要的。
  • 所以我看着大O notation我们很好的和坏的categorize区为算法。 P / < >

    Bog O classification P / < >

    我会推荐你去看这10分钟的视频,discusses大O与样本代码 P / < >

    www.youtube.com https:/ / / /看吗?V = k6kxtzicg _克 P / < >


    序言

    算法:解决问题的程序/公式

    如何分析算法?如何比较算法?

    例如:你和一个朋友被要求创建一个函数来求0到n的和。你得到f(x),你的朋友得到g(x)。两个函数的结果相同,但算法不同。为了客观地比较算法的效率,我们使用大O符号。

    big-o表示法:描述当输入任意变大时,运行时相对于输入的增长速度。

    3个关键要点:

  • 比较运行时增长的速度,而不是比较精确的运行时(取决于硬件)
  • 只关心运行时相对于输入的增长(n)
  • 当n任意变大时,集中于当n变大时增长最快的项,也就是渐近分析。
  • 空间复杂性:除了时间复杂性,我们还关心空间复杂性(一个算法使用了多少内存/空间)。我们不检查操作时间,而是检查内存分配的大小。


    如果我想向6岁的孩子解释这一点,我将开始绘制一些函数f(x)=x和f(x)=x^2,例如,并询问孩子哪个函数将是页面顶部的上一个函数。然后我们继续画图,看X^2获胜。""谁赢"实际上是x趋于无穷大时增长更快的函数。所以"函数x在x^2的大o中"意味着当x趋于无穷大时x比x^2增长得慢。当x趋于0时也可以这样做。如果我们把x从0到1的这两个函数画出来,x将是一个上函数,所以"x^2在x的大o中,因为x趋向于0"。当孩子长大后,我补充说,真正大的o可以是一个增长不快的函数,但与给定函数的增长方式相同。此外,常数被丢弃。所以2X是X的大O。


    在简单的英语中,big o就像<=(小于或等于)。当我们对两个函数f和g说,f=o(g)意味着f<=g。

    然而,这并不意味着对于任何n f(n)<=g(n)。实际上,它的意思是f在增长方面小于或等于g。这意味着如果c是常数,在点f(n)<=c*g(n)之后。在一个点之后,意味着所有n>=n0,其中n0是另一个常数。


    大O表示任何函数的上界。我们通常用它来表示一个函数的上界,这个函数告诉一个算法的运行时间。

    例如:f(n)=2(n^2)+3n是一个表示假设算法运行时间的函数,big-o表示法基本上给出了该函数的上限,即o(n^2)。

    这个符号基本上告诉我们,对于任何输入"n",运行时间都不会大于big-o符号表示的值。

    此外,同意上述所有详细答案。希望这有帮助!!


    大O表示一类函数。

    它描述了函数对于大输入值的增长速度。

    对于给定的函数f,o(f)描述了所有函数g(n),对于这些函数,可以找到n0和常量c,从而使n大于等于n0的g(n)的所有值小于或等于c*f(n)

    用数学术语来说,o(f)是一组函数。也就是说,从某个值n0开始,所有函数的增长速度都变慢或和f一样快。

    如果f(n)=n,则

    g(n)=3n在o(f)中,因为常数不重要。h(n)=n+1000在o(f)中,因为对于所有小于1000的值,它可能都较大,但对于大o,只有巨大的输入才重要。

    然而,i(n)=n^2不在o(f)中,因为二次函数比线性函数增长更快。


    它代表了从长远来看算法的速度。

    用字面上的比喻来说,你不在乎跑步者冲刺100米短跑,甚至5公里跑的速度有多快。你更关心马拉松运动员,最好是超马拉松运动员(除此之外,与跑步的类比会失效,你必须回到"长跑"的隐喻意义上)。

    你可以在这里安全地停止阅读。

    我加入这个答案是因为我对其余答案的数学和技术感到惊讶。第一句话中的"长期"概念与任意耗时的计算任务有关。与人类能力有限的运行不同,计算任务可能需要数百万年才能完成某些算法。

    那些数学对数和多项式呢?结果表明,算法本质上与这些数学术语有关。如果你在测量街区里所有孩子的身高,你需要和孩子一样多的时间。这本质上与n^1或仅n的概念有关,其中n只不过是块上孩子的数量。在超马拉松的例子中,你测量的是你所在城市所有孩子的身高,但是你必须忽略旅行时间,并假设他们在一条线上都对你有用(否则我们会跳过当前的解释)。

    假设你正试图按照从最短高度到最长高度的顺序排列由孩子们组成的列表。如果只是你周围的孩子,你可能只是盯着他们看,然后拿出一份订购单。这是"冲刺"的类比,我们真的不在乎计算机科学中的冲刺,因为当你能看到一些东西时,为什么要使用计算机?

    但是如果你把你所在城市,或者更好的是,你所在国家的所有孩子的身高都列出来,那么你就会发现你如何做到这一点,本质上与数学日志和n^2有关。通过你的清单找到最矮的孩子,把他的名字写在一个单独的笔记本上,然后从原来的笔记本上划掉,这与数学n^2有着本质的联系。如果你想把一半的笔记本,另一半,然后把结果结合起来,你就会得到一个与对数有内在联系的方法。

    最后,假设你必须先去商店买一卷卷尺。这是一个在短跑中起重要作用的例子,比如测量街区上的孩子,但是当你测量城市里所有的孩子时,你可以安全地忽略这个成本。这是与所说的低阶多项式项的数学落差的内在联系。

    我希望我已经解释过,big-o符号仅仅是关于长期的,数学本质上与计算方式有关,数学术语的下降和其他简化是以一种相当普通的方式与长期有关。

    一旦你意识到这一点,你会发现大O是非常容易的,因为所有的高中数学都很容易辍学。唯一困难的部分是分析一个算法来识别数学术语,但是通过一些实践,您可以在分析过程中开始删除术语,并安全地忽略算法的块,只关注与大O相关的部分,也就是说,您应该能够关注大多数情况。

    快乐的大O-ing,这是我最喜欢的关于计算机科学的事情——发现有些事情比我想象的要容易得多,然后能够在谷歌采访中炫耀,当没有经验的人会被恐吓的时候,哈哈。


    What is a plain English explanation of"Big O" notation?

    我想强调的是,"大O"符号的驱动动机是一回事,当算法的输入量太大时,描述算法度量的方程的某些部分(即常数、系数、项)变得如此微不足道,以至于我们忽略了它们。方程中忽略某些部分后仍然存在的部分被称为算法的"大O"符号。

    因此,如果输入大小不太大,"大O"符号(上界)的概念将不重要。

    Les说你想量化以下算法的性能

    1
    2
    3
    4
    5
    6
    7
    int sumArray (int[] nums){
        int sum=0;   // taking initialization and assignments n=1
        for(int i=0;nums.length;i++){
            sum += nums[i]; // taking initialization and assignments n=1
        }
        return sum;
    }

    在上面的算法中,假设您发现T(n)如下(时间复杂度):

    1
    T(n) = 2*n + 2

    要找到它的"大O"符号,我们需要考虑非常大的输入大小:

    1
    2
    3
    n= 1,000,000   -> T(1,000,000) = 2,000,002
    n=1,000,000,000  -> T(1,000,000,000) = 2,000,000,002
    n=10,000,000,000  -> T(10,000,000,000) = 20,000,000,002

    让我们为另一个函数F(n) = n提供类似的输入。

    1
    2
    3
    n= 1,000,000   -> F(1,000,000) = 1,000,000
    n=1,000,000,000  -> F(1,000,000,000) = 1,000,000,000
    n=10,000,000,000  -> F(10,000,000,000) = 10,000,000,000

    正如你所看到的,当输入大小变得太大时,T(n)大约等于或接近F(n),所以常数2和系数2变得太小,现在出现了大o符号的概念,

    1
    2
    O(T(n)) = F(n)
    O(T(n)) = n

    我们说T(n)的大O是n,记法是O(T(n)) = n,是T(n)的上界,因为n太大了。同样的步骤也适用于其他算法。


    大O-经济观点。

    我最喜欢用英语来形容这个概念,就是当任务变大时,你为它付出的代价。

    把它看成是经常性成本,而不是一开始就要支付的固定成本。固定成本在总体上可以忽略不计,因为成本只会增长,而且会累积起来。我们要测量它们的生长速度,以及它们在我们提供给设定规模的问题的原材料方面的累积速度。

    但是,如果初始设置成本很高,并且您只生产少量的产品,那么您需要看看这些初始成本——它们也被称为常量。

    因为,从长远来看,这些常量并不重要,所以这种语言允许我们讨论运行它的基础设施之外的任务。所以,工厂可以在任何地方,工人可以是任何人——都是肉汁。但是,工厂的规模和工人的数量将是我们长期可以改变的事情,随着您的投入和产出的增长。

    因此,这就成了一个大致的近似值,即运行某个项目需要花费多少钱。因为时间和空间是这里的经济量(即它们是有限的),所以它们都可以用这种语言来表示。

    技术说明:一些时间复杂性的例子-o(n)通常意味着,如果问题的大小为"n",我至少要看到所有东西。O(log n)通常意味着我将问题的大小减半,然后检查并重复,直到任务完成。O(n^2)意味着我需要看成对的东西(比如在n人之间的聚会上握手)。


    tldr:大O explains性能的一个算法在mathematical词汇表。 P / < >

    slower tend算法的运行在N 电力部X或多少,取决于它在depth of,whereas更快ones像二进制搜索的运行在O(log n)的,这使它运行更快的年代数据集得到的大。大O可以explained与其他词汇表使用或不使用N N,甚至太(即:O(1))。 P / < >

    人可以calculate大O看着现在的中心线的算法。 P / < >

    与小的或大的unsorted datasets O可以并不奇怪,N log N complexity像二进制搜索算法可以为smaller缓慢或unsorted集J,为简单的例子运行的线性口袋搜索与二进制搜索,以在看我的javascript的例子: P / < >

    codepen.io https:/ / / / / / xelwqn serdarsenay笔吗?editors =(1011算法写下面) P / < >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    function lineerSearch() {
      init();
      var t = timer('lineerSearch benchmark');
      var input = this.event.target.value;
      for(var i = 0;i<unsortedhaystack.length - 1;i++) {
        if (unsortedhaystack[i] === input) {
          document.getElementById('result').innerHTML = 'result is..."' + unsortedhaystack[i] + '", on index: ' + i + ' of the unsorted array. Found' + ' within ' + i + ' iterations';
          console.log(document.getElementById('result').innerHTML);
          t.stop();
          return unsortedhaystack[i];
        }
      }
    }

    function binarySearch () {
      init();
      sortHaystack();
      var t = timer('binarySearch benchmark');
      var firstIndex = 0;
      var lastIndex = haystack.length-1;
      var input = this.event.target.value;

      //currently point in the half of the array
      var currentIndex = (haystack.length-1)/2 | 0;
      var iterations = 0;

      while (firstIndex <= lastIndex) {
        currentIndex = (firstIndex + lastIndex)/2 | 0;
        iterations++;
        if (haystack[currentIndex]  < input) {
          firstIndex = currentIndex + 1;
          //console.log(currentIndex +" added, fI:"+firstIndex+", lI:"+lastIndex);
        } else if (haystack[currentIndex] > input) {
          lastIndex = currentIndex - 1;
          //console.log(currentIndex +" substracted, fI:"+firstIndex+", lI:"+lastIndex);
        } else {
          document.getElementById('result').innerHTML = 'result is..."' + haystack[currentIndex] + '", on index: ' + currentIndex + ' of the sorted array. Found' + ' within ' + iterations + ' iterations';
          console.log(document.getElementById('result').innerHTML);
          t.stop();
          return true;
        }
      }
    }