我希望尽可能少用正式的定义和简单的数学方法。
#1楼
不确定我是否会进一步为该主题做出贡献,但仍想与我分享:我曾经发现此博客文章对Big O给出了一些非常有用的(虽然很基本的)解释和示例:
通过示例,这有助于将裸露的基础知识带入我的to壳般的头骨,所以我认为阅读这本书要花10分钟,以使您朝正确的方向前进。
#2楼
Big O的简单英语解释是什么? 用尽可能少的形式定义和简单的数学方法。
需要大O标记的简单英语解释:
当我们编程时,我们正在尝试解决问题。 我们编写的代码称为算法。 大O表示法使我们能够以标准化方式比较算法最差情况的性能。 硬件规格随时间变化,硬件的改进可以减少算法运行所需的时间。 但是更换硬件并不意味着我们的算法会随着时间的推移而有所改善或改进,因为我们的算法仍然相同。 因此,为了使我们能够比较不同的算法,以确定一个算法是否更好,我们使用Big O表示法。
什么大O符号的纯英文的解释是:
并非所有算法都在相同的时间内运行,并且可能会根据输入中的项数(我们称为n)而有所不同。 基于此,我们考虑最坏情况的分析,或者随着n越来越大而增加运行时间。 我们必须知道n是什么,因为许多Big O符号都引用了n。
#3楼
大O
当x到达a(例如a = +∞)时, f (x)= O( g (x))表示存在一个函数k ,使得:
f (x)= k (x) g (x)
k限制在a的某个邻域内(如果a = +∞,则意味着存在数字N和M,使得对于每个x> N,| k (x)| <M)。
换句话说,用简单的英语来说: 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)
当x→+∞时,sin x = O(1),
当x→0时x 2 + x = O(x)
当x→+∞时x 2 + x = O(x 2 )
当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)”将毫无意义。
更多例子
当n→+∞时,O(1)= O(n)= O(n 2 )(但反之,相等是“伪”),
当n→+∞时O(n)+ O(n 2 )= O(n 2 )
当n→+∞时O(O(n 2 ))= O(n 2 )
当n→+∞时O(n 2 )O(n 3 )= O(n 5 )
这是Wikipedia文章: https : //en.wikipedia.org/wiki/Big_O_notation
#4楼
算法示例(Java):
// 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表示复杂度的上限(时间,空间,..)
要找到关于时间复杂性的Big-O:
计算最坏情况需要花费多少时间(关于输入大小):
最坏情况:密钥在列表中不存在。
时间(最坏情况)= 4n + 1
时间:O(4n + 1)= O(n)| 在Big-O中,常量被忽略
O(n)〜线性
还有Big-Omega,它代表了Best-Case的复杂性:
最佳情况:关键是第一项。
时间(最佳情况)= 4
时间:Ω(4)= O(1)〜瞬时\\恒定
#5楼
衡量软件程序的速度非常困难,并且当我们尝试时,答案可能非常复杂,并且充满了异常和特殊情况。 这是一个大问题,因为当我们想将两个不同的程序相互比较以找出哪个是“最快的”时,所有这些例外和特殊情况都会使人分心并且毫无用处。
由于所有这些无用的复杂性,人们试图使用最小和最小复杂度(数学)表达式来描述软件程序的速度。 这些表达式是非常粗略的近似值:尽管有些运气,但它们可以捕捉到某个软件是快速还是慢速的“本质”。
由于它们是近似值,因此在表达式中使用字母“ O”(大哦)作为惯例,以向读者表明我们在进行过度简化。 (并确保没有人错误地认为该表达式无论如何都是正确的)。
如果您将“哦”读为“大约”或“大约”的意思,那您就不会做错太多。 (我认为选择Big-Oh可能是一种幽默的尝试)。
这些“ Big-Oh”表达式试图做的唯一事情是描述随着我们增加软件必须处理的数据量而使软件速度降低多少。 如果我们将需要处理的数据量增加一倍,那么该软件是否需要两倍的时间才能完成工作? 十倍长? 实际上,您会遇到非常有限的big-Oh表达式,并且需要担心:
优点:
-
O(1)
常数 :无论输入多大,程序都需要花费相同的时间来运行。 -
O(log n)
对数 :即使输入大小增加很多,程序运行时间也只会缓慢增加。
坏处:
-
O(n)
线性 :程序运行时间与输入大小成比例增加。 -
O(n^k)
多项式 :-处理时间增长得越来越快-作为多项式函数-随着输入大小的增加。
...和丑陋的:
-
O(k^n)
指数程序运行时非常迅速地增加,甚至问题的大小适度增加-仅适用于使用指数算法处理小的数据集。 -
O(n!)
阶乘该程序的运行时间将比您等待不起最小且最琐碎的数据集所需的时间更长。
#6楼
“ Big O的简单英语解释是什么?用尽可能少的正式定义和简单的数学方法。 ”
这样一个漂亮而简单的简短问题似乎至少应该得到同样简短的答案,就像学生在补习期间可能会收到的一样。
Big O表示法仅根据输入数据的数量 **告诉您算法可以在多少时间内运行。
(*在无单位的美好时光中!)
(**这很重要,因为无论今天还是明天,人们总是想要更多 )
好吧,如果这样做的话,Big O标记的奇妙之处是什么?
实际上,Big O分析是如此有用且重要,因为Big O将焦点直接放在算法自身的复杂度上,并且完全忽略了仅仅是比例常数的任何内容,例如JavaScript引擎,CPU的速度,您的Internet连接以及所有这些很快变得像T型车一样可笑的过时的东西。 Big O仅以对现在或将来的人们同样重要的方式关注绩效。
Big O表示法也直接聚焦于计算机编程/工程学最重要的原理,这一事实激励着所有优秀的程序员继续思考和梦想:获得超越技术进步的成果的唯一途径是发明更好的技术。算法 。
#7楼
假设我们正在谈论算法A ,该算法应该对大小为n的数据集执行某些操作。
然后, O( <some expression X involving n> )
用简单的英语表示:
如果执行A时不走运,则可能需要多达X(n)个操作才能完成。
碰巧,有一定的函数(把它们想象成X(n)的实现 ),往往会经常发生。 这些是众所周知的,很容易比较(示例: 1
, Log N
, N
, N^2
, N!
等。)
通过在讨论A和其他算法时比较这些算法,可以轻松地根据算法可能需要 (最坏情况)完成的操作数对算法进行排名。
通常,我们的目标是找到或构建算法A ,使其具有一个函数X(n)
,该函数返回尽可能少的数字。
#8楼
一个简单直接的答案可以是:
大O表示该算法的最坏可能的时间/空间。 该算法绝不会占用超过该限制的更多空间/时间。 大O表示极端情况下的时间/空间复杂度。
#9楼
这是我在解释Big-O的常见变体时倾向于使用的简单的英语动物
在所有情况下,优先选择列表上方的算法而不是列表下方的算法。 但是,转移到更昂贵的复杂性类别的成本差异很大。
O(1):
没有增长。 不管问题有多大,您都可以在相同的时间内解决它。 这有点类似于广播,无论广播范围内的人数多少,在给定距离上广播都需要消耗相同数量的能量。
O(log n ):
这种复杂性与O(1)相同,只不过差一点点。 出于所有实际目的,您可以将其视为非常大的恒定比例。 处理1000个项目和10亿个项目之间的工作差异只有六分之一。
O( n ):
解决问题的成本与问题的大小成正比。 如果您的问题规模增加了一倍,那么解决方案的成本就会增加一倍。 由于大多数问题都必须以某种方式扫描到计算机中,例如数据输入,磁盘读取或网络流量,因此通常这是一个负担得起的缩放因子。
O( n log n ):
这种复杂性与O( n )非常相似。 出于所有实际目的,两者是等效的。 这种复杂程度通常仍被认为是可扩展的。 通过调整假设,可以将某些O( n log n )算法转换为O( n )算法。 例如,限制键的大小可减少从O( n log n )到O( n )的排序。
O( n 2 ):
生长为正方形,其中n是正方形边的长度。 这与“网络效应”的增长率相同,在网络效应中,网络中的每个人都可能认识网络中的其他每个人。 增长是昂贵的。 如果不进行大量的体操练习,大多数可伸缩的解决方案就无法使用具有这种复杂性级别的算法。 这通常也适用于所有其他多项式复杂度-O( n k ) -。
O(2 n ):
不缩放。 您没有解决任何重要问题的希望。 对于了解要避免的内容以及专家找到O( n k )中的近似算法很有用。
#10楼
大O表示法是一种描述在给定数量的输入参数(称为“ n”)的情况下算法运行速度的方法。 它在计算机科学中很有用,因为不同的机器以不同的速度运行,并且简单地说一个算法需要5秒钟并不能告诉您太多信息,因为当您运行的系统带有4.5 GHz八核八核处理器时,我可能正在运行一个15年的800 Mhz系统,无论使用哪种算法,都可能需要更长的时间。 因此,我们没有指定算法按时间运行的速度,而是说按输入参数的数量(即“ n”)运行的速度。 通过以这种方式描述算法,我们可以比较算法的速度,而不必考虑计算机本身的速度。
#11楼
我有一种更简单的方式来了解时间复杂度,他用于计算时间复杂度的最常用指标是Big 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))。
注意:这些都没有考虑最佳,平均和最坏情况下的度量。 每个都有自己的Big O表示法。 另请注意,这是一个非常简单的解释。 大O是最常见的,但是我已经展示过它更加复杂。 还有其他符号,例如大欧米茄,小o和大theta。 在算法分析课程之外,您可能不会遇到它们。
- 查看更多: 这里
#12楼
如果您头脑中有一个合适的无穷大概念,则有一个非常简短的描述:
大O表示法告诉您解决无限大问题的成本。
还有
恒定因素可以忽略不计
如果您升级到能够以两倍快的速度运行算法的计算机,则大的O表示法不会注意到这一点。 恒定因子的改进太小,以至于无法在大O表示法适用的规模上注意到。 请注意,这是大O符号设计的有意部分。
尽管可以检测到“大于”恒定因子的任何值。
当有兴趣进行大小“大”到足以被视为近似无穷大的计算时,则大的O标记大约是解决问题的成本。
如果上述没有意义,那么您的脑海中就没有兼容的直觉概念,您可能应该忽略上述所有内容; 我知道要使这些想法变得严格,或者如果它们在直观上还不实用的话,对它们进行解释的唯一方法是首先教给您大的O表示法或类似的东西。 (尽管,一旦您将来很好地理解了大的O表示法,可能有必要重新审视这些想法)
#13楼
最简单的查看方式(用英语)
我们正在尝试查看输入参数的数量如何影响算法的运行时间。 如果您的应用程序的运行时间与输入参数的数量成正比,则称其为n的BigO。
以上陈述是一个好的开始,但并非完全正确。
更准确的解释(数学的)
假设
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)
这被读为n的T在n的大O中。
返回英文
根据上面的数学定义,如果您说算法是n的Big O,则表示它是n(输入参数的数量) 或更快速的函数。 如果您的算法是n的Big O,那么它也自动是n平方的BigO。
n的大O表示我的算法至少运行速度与此相同。 您无法查看算法的Big O符号并说出它的速度慢。 您只能说得很快。
请查看此内容 ,以获得来自UC Berkley的Big O视频教程。 这实际上是一个简单的概念。 如果您听到Shewchuck教授(又名神级老师)对此进行了解释,您会说:“哦,就是这样!”。
#14楼
大O表示法是一种根据空间或运行时间来描述算法上限的方法。 n是问题中元素的数量(即数组的大小,树中节点的数量等)。我们有兴趣描述n变大时的运行时间。
当我们说某种算法是O(f(n))时,我们说的是该算法的运行时间(或所需空间)始终小于某些恒定时间f(n)。
说二进制搜索的运行时间为O(logn)就是说存在一个常数c,您可以将log(n)乘以常数c,该常数将始终大于二进制搜索的运行时间。 在这种情况下,您总是会有一些常数的log(n)比较。
换句话说,其中g(n)是算法的运行时间,我们说g(n)= O(f(n)),当g(n)<= c * f(n)时,n> k,其中c和k是一些常数。
#15楼
这是一个非常简化的解释,但我希望它涵盖了最重要的细节。
假设您处理问题的算法取决于某些“因素”,例如,让它成为N和X。
根据N和X,您的算法将需要一些运算,例如,在最坏的情况下,它是3(N^2) + log(X)
运算。
由于Big-O不太关心常数因子(aka 3),因此算法的Big-O为O(N^2 + log(X))
。 它基本上可以翻译为“算法在最坏情况下所需的运算量”。
#16楼
假设您从亚马逊订购了《哈利·波特:完整的8电影集》 [Blu-ray],并同时在线下载了同一部电影集。 您想测试哪种方法更快。 交付将花费几乎一天的时间,下载大约提前30分钟完成。 大! 所以这是一场激烈的比赛。
如果我订购了几部蓝光电影,例如《指环王》,《暮光之城》,《黑暗骑士三部曲》等,并同时在线下载所有电影,该怎么办? 这次,交付仍需要一天的时间才能完成,但是在线下载需要3天才能完成。 对于在线购物,购买物品(输入)的数量不影响交货时间。 输出是恒定的。 我们称其为O(1) 。
对于在线下载,下载时间与电影文件大小(输入)成正比。 我们称其为O(n) 。
通过实验,我们知道在线购物的规模要比在线下载更好。 理解大O表示法非常重要,因为它可以帮助您分析算法的可伸缩性和效率 。
注意:大O表示法表示算法的最坏情况 。 假设O(1)和O(n)是以上示例的最坏情况。
参考 : http : //carlcheo.com/compsci
#17楼
您想知道所有有关大O的知识吗? 我也是。
因此,谈到大O,我将使用其中只有一个节拍的单词。 每个字一个声音。 小字很快。 您知道这些单词,我也知道。我们将使用一种声音的单词。 他们很小。 我相信您会知道我们将使用的所有词语!
现在,让您和我谈谈工作。 大多数时候,我不喜欢工作。 你喜欢工作吗? 您可能会这样做,但是我确定我不会。
我不喜欢上班。 我不喜欢花时间在工作上。 如果可以,我只想玩,做一些有趣的事情。 你有和我一样的感觉吗?
现在有时候我必须去上班。 这是可悲的,但却是事实。 因此,在工作时,我有一条规则:我尝试减少工作量。 我几乎没有工作。 然后我去玩!
所以这是个大新闻:大O可以帮助我不做工作! 如果我知道大O,我可以更多时间玩游戏。更少的工作,更多的游戏! 这就是大O帮我做的。
现在我有一些工作。 我有此列表:1、2、3、4、5、6。 我必须在此列表中添加所有内容。
哇,我讨厌工作。 但是,哦,我必须这样做。 所以我走了。
一加二等于三……加三等于六……而四等于……我不知道。 我迷路了。 我脑子里很难做。 我不太喜欢这种工作。
所以,我们不做这项工作。 让您和我一起思考这有多难。 加六个数字,我需要做多少工作?
好吧,走着瞧。 我必须添加一个和两个,然后将其添加到三个,然后将其添加到四个……总共,我算了六个添加。 我必须做六个加法来解决这个问题。
大O来了,告诉我们这个数学有多难。
大O说:我们必须做六个加法来解决这个问题。 一加,每件事从一到六。 六个小工作……每一工作都是一个加法。
好吧,我现在不做添加工作。 但是我知道这会有多困难。 这将是六个添加。
哦,不,现在我还有更多工作要做。 嘘。 谁制造这种东西?
现在他们要我加一到十! 我为什么要这么做? 我不想加一到六。 要从一增加到十……好吧……那将更加困难!
会有多困难? 我还要做多少工作? 我需要更多或更少的步骤吗?
好吧,我想我将不得不做十个加法...每件事从一到十。 十等于六。 我必须付出更多的努力才能从一增加到十,而不是一到六个!
我现在不想添加。 我只想考虑添加那么多有多困难。 而且,我希望能尽快玩。
要从一增加到六,这是一些工作。 但是,您看到从一增加到十,还有更多工作要做吗?
大O是你的朋友,也是我的朋友。 大O帮助我们思考我们需要做多少工作,以便我们可以计划。 而且,如果我们是大O的朋友,他可以帮助我们选择不那么困难的工作!
现在我们必须做新工作。 不好了。 我一点都不喜欢这个工作。
新工作是:将所有事物从一加到n。
等待! 什么是n? 我想念吗? 如果您不告诉我n是什么,我怎么能从n加到n?
好吧,我不知道n是什么。 没有告诉我。 是你吗 没有? 那好吧。 所以我们不能做工作。 ew。
但是,尽管我们现在不做这项工作,但是如果知道n,我们可以猜测会有多困难。 我们必须累加n个东西,对不对? 当然!
现在是大O,他将告诉我们这项工作有多么艰辛。 他说:将所有事物从一加一到N就是O(n)。 要添加所有这些内容,[我知道我必须加n次。] [1]真是个大王! 他告诉我们做某种工作有多困难。
对我而言,我认为大O就像一个又大又慢的老板人。 他考虑工作,但他没有做。 他可能会说:“那工作很快。” 或者,他可能会说:“工作是如此缓慢而艰辛!” 但是他不做这项工作。 他只看工作,然后告诉我们可能要花多少时间。
我非常喜欢大O。为什么? 我不喜欢工作! 没有人喜欢工作。 这就是为什么我们都喜欢大O! 他告诉我们我们可以工作多快。 他帮助我们想到了艰苦的工作。
嗯,还有更多工作。 现在,让我们不做这项工作。 但是,让我们逐步制定一个计划。
他们给了我们十张纸牌。 他们混在一起:七个,四个,两个,六个……根本不是直线。 现在...我们的工作是对它们进行排序。
恩 这听起来像很多工作!
我们如何分类这个牌组? 我有个计划。
我将从头到尾逐一查看每副牌。 如果一对中的第一张大,而另一对中的第二张小,我将其交换。 否则,我转到下一对,依此类推……不久,甲板就完成了。
甲板结束后,我问:那张卡中是否交换了卡片? 如果是这样,我必须从头再做一次。
在某个时候,某个时候,将不会进行任何交换,而我们的工作将完成。 这么多工作!
那么,按照这些规则对卡片进行排序将需要做多少工作?
我有十张卡。 而且,在大多数情况下-如果我运气不佳-我必须遍历整个卡座多达十次,每次通过卡座最多要交换十张卡。
大O,救救我!
大O进来说:对于一副n张牌,以这种方式排序将在O(N平方)时间内完成。
他为什么说n平方?
好吧,你知道n的平方是n乘以n。 现在,我明白了:检查了n张卡,最多可能经过了甲板n次。 那是两个循环,每个循环有n个步骤。 那是很多工作要做的平方。 当然,很多工作!
现在,当大O表示需要O(n平方)的工作时,他并不意味着n平方相加。 在某些情况下,可能会少一些。 但在最坏的情况下,对平台进行分类将需要接近n个平方的工作步骤。
现在在这里,大O是我们的朋友。
Big O指出了这一点:随着n变大,当我们对卡片进行排序时,该工作将比旧的“仅添加这些东西”的工作困难得多。 我们怎么知道呢?
好吧,如果n真的变大,我们不在乎我们可能会增加n或n平方的值。
对于大n,n平方比n大。
大O告诉我们,对事物进行排序比对事物进行添加更为困难。 对于大n,O(n平方)大于O(n)。 这意味着:如果n变大,则对n个混合对象进行排序必须比添加n个混合对象花费更多的时间。
大O不能解决我们的工作。 大O告诉我们工作有多艰辛。
我有一副纸牌。 我对它们进行了排序。 你帮了 谢谢。
有没有更快速的方法来分类卡片? 大澳可以帮助我们吗?
是的,有一种更快的方法! 它需要一些时间来学习,但是它可以工作……而且工作非常快。 您也可以尝试,但要花一些时间在每一步上,不要失去自己的位置。
在这种分类卡片组的新方式中,我们不会像前一段时间那样检查成对的牌。 这是排序此套牌的新规则:
一:我在我们现在正在使用的套牌中选择一张牌。 如果愿意,可以为我选择一个。 (当然,这是我们第一次进行此操作,“现在我们正在处理的部分甲板”是整个甲板。)
二:我张开了你选择的那张牌的底牌。 这是什么八卦 我如何展开? 好吧,我从起始卡开始一个接一个地寻找,并且寻找比八字卡更高的卡。
三:我从末尾卡开始,然后寻找比八字卡低的卡。
找到这两张卡后,我将其交换,然后继续寻找更多可交换的卡。 也就是说,我返回到第二步,并在您选择的卡上展开。
在某个时候,这个循环(从2到3)将结束。 当搜索的两半都在相纸卡上相遇时,它结束。 然后,我们用您在第一步中选择的卡张开了甲板。 现在,开始附近的所有牌都比八字牌低。 并且末尾的牌比八字牌高。 酷把戏!
四个(这是有趣的部分):我现在有两个小牌组,一个比八字卡低,另一个高。 现在,我要在每个小平台上执行第一步! 就是说,我从第一个小平台上的第一步开始,完成这项工作后,我从下一个小平台上的第一步开始。
我将甲板分解成几个部分,然后对每个部分进行分类,每个部分越来越小,有时我没有更多工作要做。 现在,按照所有规则,这似乎很慢。 但是请相信我,这一点都不慢。 它比第一种分类方法要少得多!
这种叫什么呢? 这就是所谓的快速排序! 这种排序是由一个叫CAR Hoare的人完成的 ,他称它为Quick Sort。 现在,快速排序一直被使用!
快速排序将大牌分解成小片。 也就是说,它可以将大任务分解成小任务。
嗯 我认为那里可能有一条规则。 要使大型任务变小,请将其分解。
这种排序非常快。 多快 Big O告诉我们:在这种情况下,这种工作需要O(n log n)来完成。
它比第一类快多少? 大O,请帮忙!
第一类是O(n平方)。 但是快速排序是O(n log n)。 您知道n log n小于n平方,对于大n吧? 好吧,这就是我们知道快速排序的速度!
如果您必须分类套牌,最好的方法是什么? 好吧,您可以做您想做的事,但是我会选择“快速排序”。
为什么选择快速排序? 我当然不喜欢工作! 我希望尽快完成工作。
我怎么知道快速排序的工作量少? 我知道O(n log n)小于O(n平方)。 O的尺寸较小,因此“快速排序”的工作量较小!
现在您认识了我的朋友BigO。他帮助我们减少了工作量。 而且,如果您知道大O,也可以减少工作量!
你从我身上学到了所有! 你真聪明! 非常感谢!
现在工作已经完成,让我们开始吧!
[1]:有一种方法可以一次性欺骗所有事物并将其从n添加到n。 一个叫高斯的孩子八岁时就发现了这一点。 我虽然不那么聪明,所以不要问我他是怎么做到的 。
#18楼
我发现了有关大O表示法的很好的解释,特别是对于一个数学不多的人。
https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/
Big O符号在计算机科学中用于描述算法的性能或复杂性。 Big O特别描述了最坏的情况,可用于描述算法所需的执行时间或使用的空间(例如,内存或磁盘)。
读过Programming Pearls或任何其他计算机科学书籍且没有数学基础的任何人,在到达提及O(N log N)或其他看似疯狂的语法的章节时都会碰壁。 希望本文能帮助您了解Big O和对数的基础。
作为程序员的第一位,然后是数学家的第二位(或者也许是第三位或第四位),我发现透彻理解Big O的最佳方法是编写一些示例代码。 因此,以下是一些常见的增长顺序以及可能的描述和示例。
O(1)
O(1)描述了一种算法,无论输入数据集的大小如何,该算法将始终在同一时间(或空间)执行。
bool IsFirstElementNull(IList<string> elements) { return elements[0] == null; }
上)
O(N)描述了一种算法,其性能将线性增长,并且与输入数据集的大小成正比。 下面的示例还演示了Big O如何支持最坏情况下的性能情况; 在for循环的任何迭代期间都可以找到匹配的字符串,并且该函数将尽早返回,但是Big O表示法将始终假定算法将执行最大迭代次数的上限。
bool ContainsValue(IList<string> elements, string value) { foreach (var element in elements) { if (element == value) return true; } return false; }
O(N 2 )
O(N 2 )表示一种算法,其性能与输入数据集的大小的平方成正比。 这在涉及对数据集进行嵌套迭代的算法中很常见。 更深层的嵌套迭代将导致O(N 3 ),O(N 4 )等。
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(2 N )
O(2 N )表示一种算法,每增加一个输入数据集,其增长量就会增加一倍。 O(2 N )函数的增长曲线是指数的-从非常浅的位置开始,然后在气象上上升。 O(2 N )函数的一个示例是斐波那契数的递归计算:
int Fibonacci(int number) { if (number <= 1) return number; return Fibonacci(number - 2) + Fibonacci(number - 1); }
对数
对数解释起来有些棘手,因此我将使用一个常见示例:
二进制搜索是一种用于搜索排序的数据集的技术。 它通过选择数据集的中间元素(实际上是中位数)进行工作,并将其与目标值进行比较。 如果值匹配,它将返回成功。 如果目标值高于探针元素的值,它将获取数据集的上半部分并对其执行相同的操作。 同样,如果目标值低于探针元素的值,它将对下半部分执行操作。 在每次迭代之前,它将继续将数据集减半,直到找到该值或无法再拆分数据集为止。
这种算法称为O(log N)。 二元搜索示例中描述的数据集的迭代减半生成一条增长曲线,该曲线在开始时达到峰值,并随着数据集大小的增加而逐渐变平,例如,包含10个项目的输入数据集需要一秒钟才能完成,一个数据集包含100个项目的数据将花费2秒,而包含1000个项目的数据集将花费3秒。 将输入数据集的大小加倍对其增长几乎没有影响,因为在算法的单次迭代后,数据集将减半,因此与输入数据集大小的一半相等。 这使得诸如二进制搜索之类的算法在处理大型数据集时极为有效。
#19楼
前言
算法 :解决问题的过程/公式
如何分析算法,以及如何相互比较算法?
例如:您和您的朋友被要求创建一个将0到N的数字求和的函数。您得出f(x),而您的朋友得出g(x)。 这两个函数具有相同的结果,但算法不同。 为了客观地比较算法的效率,我们使用Big-O表示法 。
Big-O表示法:描述当输入任意大时,运行时相对于输入的增长速度。
3个关键要点:
- 比较运行时的增长速度 不 比较确切的运行时 (取决于硬件)
- 只关心运行时间相对于输入的增长(n)
- 随着n任意变大,关注随着n变大(认为无穷大)而逐渐增长最快的项AKA 渐近分析
空间复杂度:除了时间复杂度外,我们还关心空间复杂度(算法使用多少内存/空间)。 我们不检查操作时间,而是检查内存分配的大小。
#20楼
Big O只是一种以一种常见的方式“表达”自己的方式,即“运行我的代码需要多少时间/空间?”。
您可能经常看到O(n),O(n 2 ),O(nlogn)等,所有这些只是显示方式; 算法如何变化?
O(n)表示Big O为n,现在您可能会想,“ n!”是什么? 那么“ n”是元素的数量。 要在阵列中搜索项目的映像。 您将不得不查看每个元素,并将其显示为“您是否正确的元素/项目?” 在最坏的情况下,该项目位于最后一个索引处,这意味着它花费的时间与列表中的所有项目一样多。因此,一般来说,我们说“哦,嘿,n是一个给定的值!” 。
因此,您可能会理解“ n 2 ”的含义,但更具体地讲,请您以为您拥有简单,最简单的排序算法; 泡泡糖 该算法需要仔细检查每个项目的整个列表。
我的清单
- 1个
- 6
- 3
这里的流程将是:
- 比较1和6,哪个最大? Ok 6位置正确,继续前进!
- 比较6和3,哦,3少! 让我们移动一下,确定列表已更改,我们需要从头开始!
这是O n 2,因为您需要查看列表中所有包含“ n”个项目的项目。 对于每个项目,您再次查看所有项目,以进行比较,这也是“ n”,因此对于每个项目,您查看“ n”次,这意味着n * n = n 2
我希望这是您想要的那样简单。
但是请记住,Big O只是一种以时间和空间方式表现自己的方式。
#21楼
编辑:快速说明,这几乎肯定会使Big O表示法 (这是一个上限)与Theta表示法 (这是一个上下限)混淆。 以我的经验,这实际上是非学术场合中讨论的典型内容。 造成任何混乱,我们深表歉意。
一句话:随着工作规模的扩大,完成工作需要多长时间?
显然,这仅使用“大小”作为输入,而使用“花费的时间”作为输出—如果您想谈论内存使用情况等,也可以使用相同的想法。
这是一个示例,其中我们有N件T恤要干燥。 我们假定将它们置于干燥位置非常快(即,与人的互动可以忽略不计)。 当然,现实生活中并非如此...
在室外使用清洗线:假设您有无限大的后院,则清洗会在O(1)时间内烘干。 无论您拥有多少,它都会得到相同的阳光和新鲜空气,因此大小不会影响干燥时间。
使用滚筒式干衣机:每次装载10件衬衫,一个小时后完成。 (忽略此处的实际数字,它们是无关紧要的。)因此,烘干50件衬衫所需的时间大约是烘干10件衬衫所需时间的5倍。
将所有物品放到通风的橱柜中:如果将所有物品放到一大堆中并让整体保暖,中衬衫要花很长时间才能变干。 我不想猜测细节,但我怀疑这至少是O(N ^ 2)-随着洗涤量的增加,干燥时间增加得更快。
“大O”表示法的一个重要方面是,它并未说明哪种算法在给定大小下会更快。 拿一个哈希表(字符串键,整数值)和一个对数组(字符串,整数)。 根据字符串在哈希表中查找键或在数组中查找元素是否更快? (即对于数组,“在字符串部分与给定键匹配的地方找到第一个元素。”)哈希表通常被摊销(〜=“平均”)O(1)—一旦建立,就应该花费大约同时在100条目表中查找条目的时间与在1,000,000条目表中查找条目的时间相同。 在数组中找到一个元素(基于内容而不是索引)是线性的,即O(N)-平均而言,您将不得不查看条目的一半。
这会使哈希表比查找数组快吗? 不必要。 如果条目的集合很小,则数组可能会更快-您可以在计算所要查找的哈希码所需的时间内检查所有字符串。 但是,随着数据集的增大,哈希表最终将击败该数组。
#22楼
快速说明,这几乎肯定会使Big O表示法 (上限)与Theta表示法“Θ”(两侧界线)混淆。 以我的经验,这实际上是非学术场合中讨论的典型内容。 造成任何混乱,我们深表歉意。
此图可以直观地显示O的复杂性:
我可以为Big-O表示法给出的最简单定义是:
Big-O表示法是算法复杂度的相对表示。
该句子中有一些重要的和故意选择的单词:
- 相对的:您只能将苹果与苹果进行比较。 您无法将进行算术乘法的算法与对整数列表进行排序的算法进行比较。 但是,对两种进行算术运算的算法进行比较(一次乘法,一次加法)将告诉您一些有意义的事情。
- 表示形式: Big-O(以最简单的形式)将算法之间的比较简化为单个变量。 该变量是根据观察或假设选择的。 例如,通常基于比较操作(对两个节点进行比较以确定它们的相对顺序)比较排序算法。 这假定比较是昂贵的。 但是,如果比较便宜但交换昂贵呢? 它改变了比较; 和
- 复杂性:如果我花一秒钟来排序10,000个元素,我要花多长时间来排序一百万个元素? 在这种情况下,复杂性是相对于其他事物的相对度量。
阅读完其余内容后,请重新阅读以上内容。
我能想到的Big-O最好的例子是算术。 取两个数字(123456和789012)。 我们在学校学到的基本算术运算是:
- 加成;
- 减法
- 乘法; 和
- 师。
这些都是操作或问题。 解决这些问题的方法称为算法 。
加法是最简单的。 您将数字排列在右边(右边),并将数字添加到写有该结果的最后一个数字的列中。 该数字的“十”部分将结转到下一列。
假设这些数字的加法是该算法中最昂贵的操作。 完全有理由将这两个数字相加,我们必须将6个数字相加(可能带有7位)。 如果我们将两个100位数相加,则必须相加100。 如果我们将两个 10,000位数字相加,则必须进行10,000次加法。
看到图案了吗? 复杂度 (即操作数)与较大数字中的位数n成正比。 我们称其为O(n)或线性复杂度 。
减法类似(除非您可能需要借用而不是进位)。
乘法是不同的。 您将数字排列起来,取下位数的第一个数字,然后与上位数的每个数字依次相乘,依此类推。 因此,要将我们的两个6位数相乘,我们必须进行36次相乘。 我们可能需要做多达10个或11列合计得到最终的结果了。
如果我们有两个100位数字,则需要进行10,000次乘法和200次加法。 对于两个一百万个数字,我们需要进行一万亿(10 12 )乘法和200万加法。
随着算法按n 平方缩放,这就是O(n 2 )或二次复杂度 。 现在是引入另一个重要概念的好时机:
我们只关心复杂性的最重要部分。
机敏的人可能已经意识到我们可以将操作数表示为:n 2 + 2n。 但是,正如您从我们的示例中看到的那样,每个例子中有两个百万位数字,第二项(2n)变得微不足道(占该阶段总操作数的0.0002%)。
可以注意到,我们在这里假设了最坏的情况。 在将6位数字相乘时,如果其中之一具有4位数字,而另一个具有6位数字,则我们只有24个乘法。 尽管如此,我们仍会计算该n的最坏情况,即当两者均为6位数字时。 因此,Big-O表示法是关于算法的最坏情况。
电话簿
我能想到的下一个最佳示例是电话簿,通常称为“白页”或类似内容,但因国家/地区而异。 但是我说的是按姓氏,名字首字母或名字,可能的地址和电话号码列出的人。
现在,如果您指示计算机在包含1,000,000个名字的电话簿中查找“ John Smith”的电话号码,您将怎么办? 忽略了一个事实,即您可以猜测S的开始距离(假设您不能),那么您会怎么做?
一个典型的实现方式可能是打开中间位置,占据第500,000 个位置并将其与“ Smith”进行比较。 如果碰巧是“史密斯,约翰”,我们真的很幸运。 “ John Smith”更可能在该名称之前或之后。 如果是后我们再划分电话簿的后半对半,重复动作。 如果在此之前,那么我们将电话簿的前半部分分成两半,然后重复。 等等。
这就是所谓的二进制搜索 ,并且每天都在使用的编程你是否意识到这一点。
因此,如果您想在一百万个电话簿中找到一个名字,则最多可以执行20次,实际上可以找到任何名字。 在比较搜索算法时,我们认为此比较是我们的“ n”。
- 对于3个名字的电话簿,最多需要进行2个比较。
- 对于7,最多需要3。
- 15需要4。
- …
- 1,000,000需要20。
那真是太好了,不是吗?
在大澳而言,这是O(log n)的或对数的复杂性 。 现在所讨论的对数可以是ln(底数e),log 10 ,log 2或其他一些底数。 就像O(2n 2 )和O(100n 2 )都是O(n 2 )一样,它仍然是O(log n)。
在这一点上,有必要解释一下,Big O可用于通过一种算法确定三种情况:
- 最佳情况:在电话簿搜索中,最佳情况是我们在一次比较中找到了姓名。 这是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→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×10 64
因此,旅行推销员问题的Big-O是O(n!)或阶乘或组合复杂度 。
到200个城镇时,宇宙中没有足够的时间来解决传统计算机的问题。
需要考虑的事情。
多项式时间
我想快速提及的另一点是,任何具有O(n a )复杂度的算法都被认为具有多项式复杂度或在多项式时间内可解。
O(n),O(n 2 )等都是多项式时间。 有些问题不能在多项式时间内解决。 因此,世界上使用了某些东西。 公钥密码术就是一个很好的例子。 在计算上很难找到数量很大的两个主要因素。 如果不是,我们将无法使用所使用的公钥系统。
无论如何,这就是我对Big O(修订本)的解释(希望是纯英语)。
#23楼
大O表示算法使用多少时间/空间(相对于其输入大小)。
如果算法为O(n),则时间/空间将以与其输入相同的速率增加。
如果算法为O(n 2 ),则时间/空间将以其输入平方的速率增加。
等等。
#24楼
大O描述了当输入变大时函数的增长行为(例如程序的运行时)的上限。
例子:
O(n):如果我将输入大小加倍,则运行时间将加倍
O(n 2 ):如果输入大小加倍运行时间四倍
O(log n):如果输入大小加倍,则运行时间增加一
O(2 n ):如果输入大小增加一,则运行时会加倍
输入大小通常是表示输入所需的空间(以位为单位)。
#25楼
它显示了算法如何根据输入大小进行缩放。
O(n 2 ) :称为二次复杂度
- 1项:1秒
- 10件:100秒
- 100项:10000秒
请注意,项目数增加了10倍,但时间增加了10 2倍。 基本上,n = 10,所以O(n 2 )给我们缩放因子n 2 ,它是10 2 。
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
的对n
就是所需的时间,因此为log n
。
这就是要旨。 他们降低了数学运算的精度,因此可能不是正好是n 2或他们所说的那样,但这将是缩放的主要因素。
#26楼
大O描述了算法的基本缩放性质。
Big O并没有告诉您很多关于给定算法的信息。 它切开了骨头,仅提供有关算法可缩放性的信息,特别是响应“输入大小”算法的资源使用(思考时间或内存)如何缩放。
考虑一下蒸汽机和火箭之间的区别。 它们不仅是同一事物的不同变体(例如普锐斯(Prius)引擎还是兰博基尼(Lamborghini)引擎),而且它们的核心是截然不同的推进系统。 蒸汽机可能比玩具火箭快,但是没有蒸汽活塞机能够达到轨道运载火箭的速度。 这是因为这些系统在达到给定速度(“输入大小”)所需的燃料(“资源使用”)的关系方面具有不同的缩放特性。
为什么这个这么重要? 因为软件处理的问题的大小差异可能高达一万亿美元。 考虑一下。 登月所需的速度与人的行走速度之间的比小于10,000:1,与软件可能面对的输入大小范围相比,这绝对很小。 而且由于软件的输入大小可能面临天文数字范围,因此算法的Big O复杂性(其基本的伸缩性)可能胜过任何实现细节。
考虑规范的排序示例。 冒泡排序为O(n 2 ),而合并排序为O(n log n)。 假设您有两个排序应用程序,使用气泡排序的应用程序A和使用合并排序的应用程序B,假设输入大小约为30个元素,则应用程序A在排序时比应用程序B快1000倍。 如果您不必排序多于30个元素,那么显然您应该首选应用程序A,因为在这些输入大小下它要快得多。 但是,如果您发现可能必须分类一千万个项目,那么您期望的是,在这种情况下,应用程序B实际上最终比应用程序A快数千倍,这完全是由于每种算法的扩展方式所致。
#27楼
英文对“ Big O”符号的解释是什么?
快速说明:
“大O”中的O称为“顺序”(或恰好是“ ...的顺序”)
因此您可以从字面上理解它的想法,即它用于订购某些东西以进行比较。
“大O”做两件事:
- 估计计算机执行任务所需的方法步骤。
- 促进与他人进行比较的过程,以确定它是否好?
- “ Big O'以标准化的
Notations
实现了以上两个。
有七个最常用的符号
- O(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!
完成任务N!
步骤,太糟糕了,第7号订单
- O(1),意味着你的计算机获得与完成的任务
假设您得到符号O(N^2)
,不仅您清楚该方法需要N * N个步骤来完成一项任务,而且从其排名来看,它还不如O(NlogN)
好。
请注意行尾的顺序,以方便您更好地理解。如果考虑所有可能,这里有7种以上的符号。
在CS中,完成任务的步骤集称为算法。
在术语中,Big O表示法用于描述算法的性能或复杂性。
另外,Big O确定最坏情况或测量上限步。
您可以参考Big-Ω(Big-Omega)以获得最佳情况。
摘要
“大O”描述算法的性能并对其进行评估。或正式解决这个问题,“大O”对算法进行分类并标准化比较过程。
#28楼
当您忽略恒定因素和原点附近的东西时, Big-O表示法(也称为“渐近生长”表示法)的作用是“看起来像” 。 我们用它来谈论事物如何扩展 。
基本
用于“足够”的大投入...
-
f(x) ∈ O(upperbound)
意味着f
“增长upperbound
于”upperbound
-
f(x) ∈ Ɵ(justlikethis)
平均f
“正是变得像”justlikethis
-
f(x) ∈ Ω(lowerbound)
表示f
“增长不慢于”lowerbound
big-O表示法不关心常量因素:函数9x²
据说“完全像10x²
一样增长”。 big-O 渐近符号也不关心非渐近的东西(“原点附近的东西”或“问题大小较小时会发生什么”):函数10x²
被称为“完全像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
是处理器/内存使用率的最坏情况”。
应用领域
作为纯粹的数学构造,big-O表示法不限于谈论处理时间和内存。 您可以使用它来讨论有意义缩放的任何事物的渐近性,例如:
- 一次聚会上
N
个人之间可能握手的数量(Ɵ(N²)
,特别是N(N-1)/2
,但重要的是它的缩放比例类似于N²
) - 将病毒性营销视为时间的函数的预期概率人数
- 网站等待时间如何随CPU或GPU或计算机群集中的处理单元数量扩展
- CPU上的热量输出比例如何随晶体管数量,电压等的变化而消失。
- 一个算法需要运行多少时间,取决于输入大小
- 算法需要运行多少空间,取决于输入大小
例
对于上面的握手示例,房间中的每个人都与其他人握手。 在该示例中, #handshakes ∈ Ɵ(N²)
。 为什么?
稍微备份一下:握手的次数恰好是n-choose-2或N*(N-1)/2
(每N个人握手N-1个其他人的握手,但是这种重复计算的握手次数除以2):
但是,对于非常多的人,线性项N
相形见and,并有效地使比率为0(在图表中:随着参与者数量的增加,对角线上的空框在总框上所占的比例会变小)。 因此,缩放行为为order N²
,或者握手次数“像N²一样增长”。
#handshakes(N)
────────────── ≈ 1/2
N²
好像图表对角线上的空白框(N *(N-1)/ 2个复选标记)甚至没有(N 2个复选标记渐近)一样。
(与“普通英语”暂时偏离:)如果您想向自己证明这一点,则可以对比率进行一些简单的代数运算,以将其分解为多个项( lim
表示“被认为是lim
的极限”,如果忽略则忽略它)。您还没有看到它,只是“ N非常大”的记法):
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;博士:握手“的样子”的数量X 2这么多的大的值,如果我们写下的比例#握手/ X 2,事实上,我们并不需要确切地 X 2的握手甚至不露面在十进制中任意大一会儿。
例如,对于x = 1百万,比率#握手/x²:0.499999 ...
建立直觉
这使我们可以像...
“对于足够大的inputsize = N,无论常数因子是多少,如果我将输入大小 加倍 ...
- ...我将O(N)(“线性时间”)算法花费的时间加倍。”
N →(2N)= 2( N )
- ...我将O(N²)(“二次时间”)算法所花费的时间加倍(四倍)。” (例如,问题的100倍大需要100²= 10000倍长……可能是不可持续的)
N² →(2N)²= 4( N² )
- ...我将O(N³)(“立方时间”)算法所花费的时间加倍(八倍)。” (例如,问题的100倍大需要100³= 1000000x的长久……非常不可持续)
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 * 1 → c * 1
- ...“我(基本上)将O(N log(N))算法花费的时间加倍。” (相当普遍)
它小于O(N 1.000001 ),您可能愿意将其称为线性
- ...我荒谬地增加了O(2 N )(“指数时间”)算法所花费的时间。” (您只需将问题增加一个单位就可以使时间增加一倍(或三倍,等等)。)
2 N →2 2N =(4 N )............换一种方式...... 2 N →2 N + 1 = 2 N 2 1 = 2 2 N
[对于数学上的倾向,您可以将鼠标悬停在扰流板上以找到较小的旁注]
(感谢https://stackoverflow.com/a/487292/711085 )
(从技术上讲,常数因子可能在一些更深奥的例子中可能很重要,但是我在上面用了措辞(例如在log(N)中),使它不起作用)
这些是程序员和应用程序计算机科学家用作参考的增长的基础。 他们一直看到这些。 (因此,尽管您可以从技术上考虑“将输入加倍会使O(√N)算法慢1.414倍”,但最好将其认为是“这比对数差,但比线性差”。)
恒定因素
通常,我们不在乎特定的常数因子是什么,因为它们不会影响函数的增长方式。 例如,两种算法都可能花费O(N)
时间来完成,但是一种算法的速度可能是另一种算法的两倍。 我们通常不会太在乎,除非这个因素很大,因为优化是一项棘手的业务( 优化何时过早? ); 同样,仅选择具有更好的big-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(N²)
),我们试图保持较小。 此后,修改数据结构(插入/删除等)并查询数据需要很少的时间,例如O(1)
或O(log(N))
。 然后,您继续进行大量查询! 通常,您愿意提前做的工作越多,以后要做的工作就越少。
例如,假设您拥有数百万条路段的纬度和经度坐标,并且想要查找所有街道交叉点。
- 朴素的方法:如果您具有街道交叉点的坐标,并且想要检查附近的街道,则每次都必须遍历数百万个路段,并检查每个路段的相邻性。
- 如果只需要执行一次,那么只需要一次执行
O(N)
的朴素方法就没问题了,但是如果您想执行多次(在这种情况下为N
次,那么每个段),我们必须进行O(N²)
工作,或1000000²= 1000000000000运算。 不好(一台现代计算机每秒可以执行约十亿次操作)。 - 如果我们使用称为哈希表(即时速度查找表,也称为哈希图或字典)的简单结构,则通过在
O(N)
时间内对所有内容进行预处理,我们将付出很小的代价。 此后,平均仅需花费固定时间即可通过其键查找内容(在这种情况下,我们的键是经度和纬度坐标,四舍五入为一个网格;我们搜索相邻网格空间中只有9个,这是一个不变)。 - 我们的任务从无法实现的
O(N²)
变为可管理的O(N)
,而我们要做的就是花很少的钱来制作哈希表。 - 类比 :在此特定情况下的类比是一个拼图游戏:我们创建了一个利用数据某些属性的数据结构。 如果我们的路段像拼图一样,我们通过匹配颜色和图案将它们分组。 然后,我们利用它来避免以后做额外的工作(将颜色相似的拼图相互比较,而不是将每个拼图相互比较)。
故事的寓意:数据结构使我们加快了运营速度。 更高级的数据结构可以使您以难以置信的巧妙方式组合,延迟甚至忽略操作。 不同的问题会有不同的类推,但它们都涉及以一种利用我们关心的结构或为簿记而人为地施加某种结构的方式来组织数据。 我们会提前进行工作(基本上是计划和组织),现在重复执行的任务要容易得多!
实际示例:在编码时可视化增长顺序
渐进符号的本质与编程完全不同。 渐近符号是一种思考事物如何缩放并可以在许多不同领域中使用的数学框架。 就是说...这就是将渐近符号应用于编码的方式。
基础知识:每当我们与大小为A的集合中的每个元素进行交互(例如数组,集合,映射的所有键等),或执行循环A的迭代时,即大小A的乘法因子。为什么说“乘法因子”?–因为循环和函数(几乎是按定义)具有乘法运行时间:迭代次数,循环中完成的工作时间(或对于函数:调用次数)功能,乘以在该功能中完成的工作)。 (这很普遍,如果我们不做任何花哨的事情,例如跳过循环或尽早退出循环,或者基于参数更改函数中的控制流,这是很常见的。)这是一些可视化技术示例,带有伪代码。
(这里, x
代表工作的恒定时间单位,处理器指令,解释器操作码等)
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)一样; 这里的两个常数因子仍然是两者之间的渐近比,但是,我们像所有因子一样都忽略了它……(这种技术有些不幸之处,我不在这里讨论;它可能会误导您。)
当然,这并不意味着循环和功能不好。 相反,它们是现代编程语言的基础,我们喜欢它们。 但是,我们可以看到,将循环,函数和条件与数据(控制流等)结合在一起的方式模仿了程序的时间和空间使用! 如果时间和空间的使用成为问题,那就是当我们诉诸机灵并找到我们未曾考虑过的简单算法或数据结构时,以某种方式减少增长的顺序。 但是,这些可视化技术(尽管它们并不总是有效)可以在最坏的运行时间为您提供幼稚的猜测。
这是我们可以从视觉上识别的另一件事:
<----------------------------- 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)
如果我们执行非常复杂的操作(例如使用递归函数或分治算法),则
可以使用
主定理 (通常有效),或者在荒谬的情况下,可以通过Akra-Bazzi定理(几乎总是有效)
来查找您的算法在Wikipedia上的运行时间。
但是,程序员并不这样认为,因为最终,算法直觉只是第二天性。 您将开始编写效率低下的代码,然后立即想到“我在做效率低下的事情吗? ”。 如果答案是“是”,并且您认为它实际上很重要,那么您可以退后一步,想出各种使事情运行更快的技巧(答案几乎总是“使用哈希表”,很少使用“使用树”,而且很少有更复杂的东西)。
摊销和平均情况下的复杂性
还有“摊销”和/或“平均情况”的概念(请注意,它们是不同的)。
平均情况 :这仅是对函数的期望值使用big-O表示法,而不是对函数本身使用。 在通常情况下,您认为所有输入的可能性均等,平均情况只是运行时间的平均值。 例如,对于快速排序,即使对于某些真正糟糕的输入,最坏的情况是O(N^2)
,但通常情况下是通常的O(N log(N))
(真正糟糕的输入的数量非常小,因此在一般情况下,我们很少会注意到它们)。
摊销最坏情况 :某些数据结构可能具有最坏情况的复杂性,但是要保证,如果您执行许多此类操作,则平均工作量将比最坏情况要好。 例如,您可能具有通常需要恒定O(1)
时间的数据结构。 但是,偶尔它会“打cup”并花O(N)
时间进行一次随机操作,因为也许它需要做一些簿记或垃圾收集之类的工作……但是它向您保证,如果打h,它不会再次打N,需要进行N次以上的操作。 最坏的情况下,每次操作的成本仍为O(N)
,但多次运行的摊销成本为O(N)/N
= O(1)
。 由于大型操作非常罕见,因此可以将大量的偶然工作与其他工作融合为一个恒定因素。 我们说该作品在足够多的通话中被“摊销”,以至于渐渐消失。
摊销分析的类比:
你开汽车。 有时,您需要花费10分钟去加油站,然后花费1分钟为储罐加气。 如果您每次开车去任何地方都这样做(花10分钟车程到加油站,花几秒钟填充一加仑的油),这将是非常低效的。 但是,如果您每隔几天就给油箱加油一次,那么开车到加油站所花费的11分钟便会在足够多的行程中“摊销”,您可以忽略它并假装所有行程可能要长5%。
平均情况与摊销最坏情况之间的比较:
- 平均情况:我们对输入进行一些假设; 即,如果我们的输入具有不同的概率,那么我们的输出/运行时将具有不同的概率(我们取其平均值)。 通常,我们假设所有输入均具有相同的可能性(均等概率),但是如果实际输入不符合我们对“平均输入”的假设,则平均输出/运行时计算可能毫无意义。 但是,如果您期望统一的随机输入,则可以考虑一下!
- 摊销后的最坏情况:如果使用摊销后的最坏情况数据结构,则性能可以保证在摊销后的最坏情况之内...最终(即使输入是由了解所有情况并试图搞砸了)。 通常,我们用它来分析那些性能可能非常“不稳定”且出现意外大打algorithms的算法,但是随着时间的推移,它的性能与其他算法一样好。 (但是,除非您的数据结构对其愿意拖延的许多出色工作有上限,否则邪恶的攻击者可能会强迫您一次全部追赶最大的拖延工作量。
不过,如果您合理地担心攻击者,那么除摊销和平均情况外,还有许多其他算法攻击媒介值得担心。)
平均用例和摊销都是在考虑和考虑比例时非常有用的工具。
(如果对此子主题感兴趣,请参阅平均案例与摊销分析之间的差异 。)
多维big-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))
快速排序。 您想使用插入排序,而插入排序恰好在较小的输入上表现良好。 这些情况通常在分而治之的算法中出现,您可以将问题分解为越来越小的子问题,例如递归排序,快速傅立叶变换或矩阵乘法。 - 如果由于某些隐藏的事实而使某些值有效地受到限制(例如,一般的人名柔和地限制在40个字母左右,而人的年龄则柔和地限制在150个左右)。 您还可以在输入上加上界限,以有效地使术语保持不变。
实际上,即使在具有相同或相似渐近性能的算法中,它们的相对价值实际上也可能受其他因素驱动,例如:其他性能因素(quicksort和mergesort均为O(N log(N))
,但quicksort需要CPU缓存的优势); 非性能方面的考虑因素,例如易于实施; 库是否可用以及库的信誉和维护程度。
程序在500MHz计算机上的运行速度也将比2GHz计算机上的运行慢。 我们实际上并不认为这是资源范围的一部分,因为我们考虑的是机器资源(例如,每个时钟周期)而不是每秒的扩展。 但是,有些类似的事情会“秘密地”影响性能,例如您是否在仿真下运行,或者编译器是否优化了代码。 这些可能会使一些基本操作花费更长的时间(甚至相对于彼此),甚至可能渐近地(甚至相对于彼此)加快或减慢某些操作。 在不同的实现和/或环境之间,效果可能很小或很大。 您是否切换语言或机器以节省一点额外的工作? 这取决于其他一百个原因(必要性,技能,同事,程序员的生产力,时间的金钱价值,熟悉程度,变通办法,为什么不使用汇编或GPU等),这可能比性能更重要。
上述问题,就像选择哪种编程语言的效果一样,几乎永远不会被视为恒定因素的一部分(也不应该)。 但人们应该意识到它们,因为有时 (尽管很少)它们可能会影响事物。 例如,在cpython中,本机优先级队列实现是渐近非最优的(对于您选择插入还是find-min来说,是O(log(N))
而不是O(1)
); 您是否使用其他实现? 可能不是,因为C的实现可能更快,并且其他地方可能还有其他类似的问题。 需要权衡; 有时它们很重要,有时却不重要。
( 编辑 :“普通英语”的解释到此结束。)
数学附录
#29楼
好吧,我的2美分。
Big-O,是程序消耗的资源的增长率 ,问题问题实例大小
资源:可能是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”次迭代的速度增长。 因此大O是N 2表示为O(n 2)的
#30楼
程序员最常使用Big O表示法来近似表示度量(算法)完成所需时间,以输入集大小的函数表示。
大O有助于比较两种算法随着输入数量增加而扩展的程度。
更确切地说, Big O表示法用于表达函数的渐近行为。 这意味着函数在接近无穷大时的行为。
在许多情况下,算法的“ O”将属于以下情况之一:
- O(1) -完成时间是相同的,与输入集的大小无关。 一个示例是按索引访问数组元素。
- O(Log N) -完成时间大致与log2(n)一致。 例如,1024个项目花费的时间大约是32个项目的两倍,因为Log2(1024)= 10且Log2(32)=5。一个示例是在二进制搜索树 (BST)中查找一个项目。
- O(N) -完成时间与输入集的大小成线性比例。 换句话说,如果将输入集中的项数加倍,则该算法所需的时间大约是原来的两倍。 一个示例是计算链接列表中的项目数。
- O(N Log N) -完成时间增加的项目数乘以Log2(N)的结果。 一个例子就是堆排序和快速排序 。
- O(N ^ 2) -完成时间大致等于项数的平方。 一个例子就是冒泡排序 。
- O(N!) -完成时间是输入集的阶乘。 一个例子就是旅行推销员问题暴力解决方案 。
大O会忽略在输入大小朝无穷大增加时不会对功能的增长曲线产生有意义影响的因素。 这意味着添加或乘以该函数的常量将被忽略。