What does O(log n) mean exactly?
我目前正在学习大O符号的运行时间和摊余时间。我理解O(n)线性时间的概念,这意味着输入的大小成比例地影响算法的增长……例如,二次时间O(n2)等,同样适用。即使是算法,如置换生成器,也有O(n!)乘以因式分解而增长。
例如,以下函数为o(n),因为算法的增长与其输入n成比例:
1 2 3 4 5 | f(int n) { int i; for (i = 0; i < n; ++i) printf("%d", i); } |
同样,如果有一个嵌套循环,那么时间将是O(n2)。
但O(log n)究竟是什么?例如,一个完整的二叉树的高度是O(log n),这意味着什么?
我确实知道(也许不是很详细)对数是什么,从这个意义上说:log10100=2,但我不明白如何用对数时间来标识函数。
I cannot understand how to identify a function with a log time.
Ok.
对数运行时间函数最常见的属性是:好的。
- 选择下一个元素来执行某些操作是多种可能性之一,并且
- 只需要选择一个。
或好的。
- 执行操作的元素是N的数字。
这就是为什么,例如,在电话簿中查找人是O(log n)。你不需要检查电话簿中的每个人就可以找到合适的人;相反,你可以简单地根据他们的名字按字母顺序排列的位置来划分和征服他们,在每个部分中,你只需要在最终找到某人的电话号码之前探索每个部分的一个子集。好的。
当然,大一点的电话簿仍然会花费你更长的时间,但它不会像增加的电话簿那样快速增长。好的。
我们可以扩展电话簿示例来比较其他类型的操作及其运行时间。我们假设我们的电话簿中有一些业务("黄页")具有唯一的名称,而有些人("白页")可能没有唯一的名称。电话号码最多分配给一个人或一个公司。我们还假设翻转到特定页面需要持续的时间。好的。
以下是我们可能在电话簿上执行的某些操作的运行时间,从最佳到最差:好的。
O(1)(最佳情况):根据企业名称所在的页面和企业名称,找到电话号码。好的。
O(1)(一般情况):给定一个人的名字所在的页面和他们的名字,找到电话号码。好的。
o(登录n):给定一个人的名字,在你还没有搜索到的那部分书的中间选择一个随机的点,然后检查这个人的名字是否在那个点上,找到这个电话号码。然后在书中人名所在部分的中间部分重复这个过程。(这是对人名的二进制搜索。)好的。
O(N):查找所有电话号码中包含数字"5"的人。好的。
O(N):给定一个电话号码,找到这个号码的人或公司。好的。
O(N日志N):打印机办公室发生了混乱,我们的电话簿的所有页面都按随机顺序插入。通过查看每一页上的名字,然后将该页放在新的空电话簿中的适当位置,修复排序,使其正确无误。好的。
下面的例子,我们现在在打印机办公室。电话簿正等着邮寄给每个居民或企业,而且每个电话簿上都有一个标签,标明应该邮寄到哪里。每个人或企业都有一本电话簿。好的。
-
O(n日志n):我们想personalize图书的电话,所以我们要找到每个人或是在他们的商业计划指定的拷贝,然后是圆他们的书和写一短,感谢你在为他们的资助人。 红血丝的好。
-
O(N2):A错误发生在办公室,和每一个输入的每个额外的电话"0"的书籍已安在结束电话号码。把一些白色和删除每一个零。 红血丝的好。
-
O(N和网站!)我们是准备的phonebooks到航运码头的负荷。不幸的是,机器人,是书,有《失控:负荷的把图书的卡车到A随机顺序。同样糟糕,它的所有图书的荷载到卡车,然后检查,看看是否在正确的顺序,如果不是,它unloads他们起飞。(这是dreaded Bogo排序)。 红血丝的好。
-
O(NN):你安装的机器人,它的装载的东西是正确的。下一节,一个你的公司职工是在你和A的恶作剧.加载到码头机器人自动打印系统。每一个时代的机器人去负荷一原书,本厂打印机使A重复运行的所有phonebooks!fortunately,机器人的缺陷检测系统是很复杂的,机器人不想打印副本,当它接触一个更为吃重的重复的书,但它仍然具有对负荷的每一个原始和复制,印刷图书的腿。 红血丝的好。
你可以用更多的数学解释如何到达时间的复杂性对出口
这个问题已经有很多好的答案,但我相信我们确实错过了一个重要的答案——即图解的答案。
What does it mean to say that the height of a complete binary tree is O(log n)?
下图描述了一个二叉树。请注意,每个级别包含的节点数是上面级别的两倍(因此是二进制的):
二进制搜索是一个复杂度为dOCx1〔0〕的例子。假设图1中树底部级别的节点表示一些已排序集合中的项。二进制搜索是一种分而治之的算法,图中显示了我们如何(最多)需要4个比较才能找到我们在这个16项数据集中搜索的记录。
假设我们有一个包含32个元素的数据集。继续上面的图,发现我们现在需要5个比较来找到我们正在搜索的内容,因为当我们乘以数据量时,树只会加深一层。因此,该算法的复杂性可以描述为一个对数阶。
在一张普通纸上绘制
???????这是当我们做
下面的解释是使用完全平衡的二叉树的情况来帮助您了解如何获得对数时间复杂性。
二叉树是将大小n的问题分为大小n/2的子问题,直到我们达到大小1的问题:
这就是您得到O(log n)的方式,这是需要在上面的树上完成的工作量,以获得解决方案。
具有O(log n)时间复杂性的一种常见算法是二进制搜索,其递归关系为T(n/2)+O(1),即在树的每个后续级别上,将问题分成两部分,并做恒定数量的额外工作。
概述
其他人给出了很好的图表示例,例如树图。我没有看到任何简单的代码示例。因此,除了我的解释,我还将提供一些带有简单打印语句的算法,以说明不同算法类别的复杂性。
首先,你需要对对数有一个大致的概念,你可以从https://en.wikipedia.org/wiki/logarith获得。自然科学使用
当您查看下面的代码示例时,我建议您先查看O(1),然后查看O(n),再查看O(n^2)。当你对这些很好之后,再看看其他人。我已经包含了一些干净的例子以及一些变化,来演示细微的变化如何仍然可以导致相同的分类。
您可以将O(1)、O(n)、O(logn)等看作是增长的类或类别。有些类别比其他类别要花更多的时间。这些类别有助于我们对算法性能进行排序。有些随着输入n的增长而增长得更快。下表从数值上说明了上述增长。在下表中,将log n(n)视为log 2的上限。
各种大O类的简单代码示例:
O(1)-恒定时间示例:
- 算法1:
算法1只打印一次hello,它不依赖于n,所以它总是在恒定时间内运行,所以它是
1 | print"hello"; |
- 算法2:
算法2打印hello 3次,但不取决于输入大小。即使n增长,这个算法也只能打印3次hello。也就是说3是一个常数,所以这个算法也是
1 2 3 | print"hello"; print"hello"; print"hello"; |
o(对数(n))-对数示例:
- 算法3-这类似于"log 2"
算法3演示了一个在log 2(n)中运行的算法。注意for循环的后置操作将i的当前值乘以2,所以
1 2 | for(int i = 1; i <= n; i = i * 2) print"hello"; |
- 算法4-这类似于"log 3"
算法4演示了log 3。注意:
1 2 | for(int i = 1; i <= n; i = i * 3) print"hello"; |
- 算法5-这类似于"log 1.02"
算法5很重要,因为它有助于表明,只要数字大于1,并且结果反复相乘,那么您将看到一个对数算法。
1 2 | for(double i = 1; i < n; i = i * 1.02) print"hello"; |
o(n)-线性时间示例:
- 算法6
这个算法很简单,可以打印n次hello。
1 2 | for(int i = 0; i < n; i++) print"hello"; |
- 算法7
这个算法显示了一个变化,它将在其中打印hello n/2次。n/2=1/2*n。我们忽略1/2常量,发现该算法是O(n)。
1 2 | for(int i = 0; i < n; i = i + 2) print"hello"; |
o(n*log(n))-nlog(n)示例:
- 算法8
把它看成是
1 2 3 | for(int i = 0; i < n; i++) for(int j = 1; j < n; j = j * 2) print"hello"; |
- 算法9
算法9与算法8相似,但每个循环都允许变化,最终结果仍然是
1 2 3 | for(int i = 0; i < n; i = i + 2) for(int j = 1; j < n; j = j * 3) print"hello"; |
o(n^2)-n平方示例:
- 算法10
通过对回路的套料标准,很容易得到
1 2 3 | for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) print"hello"; |
- 算法11
类似于算法10,但有一些变化。
1 2 3 | for(int i = 0; i < n; i++) for(int j = 0; j < n; j = j + 2) print"hello"; |
O(n^3)-n立方示例:
- 算法12
这类似于算法10,但是有3个循环而不是2个。
1 2 3 4 | for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) for(int k = 0; k < n; k++) print"hello"; |
- 算法13
与算法12相似,但有些变化仍然产生
1 2 3 4 | for(int i = 0; i < n; i++) for(int j = 0; j < n + 5; j = j + 2) for(int k = 0; k < n; k = k + 3) print"hello"; |
总结
上面给出了几个直截了当的例子,以及一些变化,以帮助展示哪些细微的变化可以被引入,而这些变化确实不会改变分析。希望它能给你足够的洞察力。
如果你有一个函数的那把。 </P >
1 2 3 4 5 6 | 1 millisecond to complete if you have 2 elements. 2 milliseconds to complete if you have 4 elements. 3 milliseconds to complete if you have 8 elements. 4 milliseconds to complete if you have 16 elements. ... n milliseconds to complete if you have 2**n elements. |
然后,它把日志<<2>子/子>(n)的时间。大O notation,活动游的均值,这关系到只读的需求是真的对大的N,,常数的影响因素和smaller术语可以不容忽视的。 </P >
对数的运行时(
对数
好,让我们试着完全理解对数实际上是什么。
想象一下,我们有一根绳子,我们把它拴在一匹马身上。如果绳子直接系在马身上,马需要拉离的力(例如,从人身上)是1。
现在想象绳子绕着一根杆子。要逃走的马现在必须用力拉很多倍。次数将取决于绳子的粗糙度和杆子的大小,但假设它将使一个人的力量乘以10(当绳子完全转动时)。
现在,如果绳子绕了一圈,马就需要再用力拉10倍。如果人类决定让马很困难,他可以把绳子再绕在一根杆子上,使它的力量增加10倍。第三个循环将再次增加10倍的强度。
我们可以看到,对于每个循环,值增加了10。得到任意数所需的匝数称为数的对数,即我们需要3根柱子将你的强度乘以1000倍,6根柱子将你的强度乘以1000000。
3是1000的对数,6是1000000的对数(基10)。
那么O(log n)实际上是什么意思呢?
在上面的例子中,我们的"增长率"是O(log n)。每增加一圈,我们的绳子能承受的力是10倍:
1 2 3 4 5 6 7 | Turns | Max Force 0 | 1 1 | 10 2 | 100 3 | 1000 4 | 10000 n | 10^n |
上面的例子确实使用了基数10,但幸运的是,当我们讨论大O符号时,日志的基数是微不足道的。
现在让我们想象一下,你试图猜测一个介于1-100之间的数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Your Friend: Guess my number between 1-100! Your Guess: 50 Your Friend: Lower! Your Guess: 25 Your Friend: Lower! Your Guess: 13 Your Friend: Higher! Your Guess: 19 Your Friend: Higher! Your Friend: 22 Your Guess: Lower! Your Guess: 20 Your Friend: Higher! Your Guess: 21 Your Friend: YOU GOT IT! |
现在你花了7个猜测才弄好。但是这里的关系是什么?从每一个额外的猜测中,你能猜到的最多的项目是什么?
1 2 3 4 5 6 7 8 9 | Guesses | Items 1 | 2 2 | 4 3 | 8 4 | 16 5 | 32 6 | 64 7 | 128 10 | 1024 |
使用这个图,我们可以看到,如果我们使用二进制搜索来猜测一个介于1-100之间的数字,我们最多需要7次尝试。如果我们有128个数字,我们也可以猜测7次尝试中的数字,但是129个数字最多需要8次尝试(对于对数,这里我们需要对128个值范围进行7次猜测,对1024个值范围进行10次猜测)。7是128的对数,10是1024的对数(以2为底)。
注意,我"最多"加了一个黑体。大O符号总是指更糟的情况。如果你幸运的话,你可以在一次尝试中猜出数字,所以最好的情况是O(1),但那是另一个情况。
We can see that for every guess our data set is shrinking. A good rule of thumb to identify if an algorithm has a logarithmtic time is
to see if the data set shrinks by a certain order after each iteration
那O(N)日志N呢?
您最终会遇到一个线性时间O(n log(n)算法。上面的经验法则同样适用,但这次对数函数必须运行n次,例如,减少一个列表的大小n次,这在类似mergesort的算法中发生。
您可以很容易地确定算法时间是否为n logn。寻找一个循环,循环遍历一个列表(o(n))。然后看看是否有一个内部循环。如果内部循环在每次迭代时都在切割/减少数据集,则该循环为(o(log n),因此总体算法为=o(n logn)。
免责声明:绳子对数的例子是W.Sawyer从优秀数学家的喜悦书中抓取的。
你可以认为),O(log n)的intuitively说用的时间是比例的数的位数。 </P >
如果一个操作performs常数时的工作是一位或两位数的每一个输入的操作会把全部的时间比例的数的位数或比特的输入,而不是在输入级的;因此,在O(log n),比O(N)。 </P >
如果一个操作,使得一系列的时间常数决定每部这halves(A因子的影响;3,4,5…………………)的大小的输入是考虑,会把全部的时间比例的日志(3 2基地基地基地基地,4,5…………………)的尺寸n的输入Rather,比有O(N)。 </P >
和SO。 </P >
我在脑海中想象一个在O(log n)中运行的算法的最好方法是:
如果将问题的大小增加一个乘法量(即,将问题的大小乘以10),则工作只会增加一个加法量。
将这个问题应用到二叉树问题中,这样您就有了一个好的应用程序:如果将二叉树中的节点数翻倍,那么高度只会增加1(相加的数量)。如果你再加倍,它仍然只增加了1。(很明显,我假设它保持平衡。这样一来,当问题的大小成倍增加时,你的工作量就不会增加一倍,而只是稍微增加一点。这就是为什么O(log n)算法很棒。
What's logb(n)?
它是在达到尺寸1的截面之前,可以将长度为n的原木反复切割成b等份的次数。
首先,我建议你读下面这本书;
算法(第4版)
下面是一些函数及其预期的复杂性。数字表示语句执行频率。
遵循Bigo Co表的Big-O复杂性图
最后非常简单的展示,展示它是如何计算的;
程序语句执行频率的剖析。
分析程序的运行时间(示例)。
除与conquer算法通常有一个
在的情况下,每一个迭代BINARY SEARCH,你再把"半的输入。它应该是在noted大O notation日志,日志是基地2。 </P >
编辑:作为noted,日志数据库不t物,但是当产生的"大O的算法的性能,在对数因子来将从halving hence,为什么我认为它作为基地。 </P >
But what exactly is O(log n)? For example, what does it mean to say that the height of a >complete binary tree is O(log n)?
我将把它改为"完整二叉树的高度是对数n"。如果一步一步地向下遍历,那么计算完整二叉树的高度将是O(log n)。
I cannot understand how to identify a function with a logarithmic
time.
对数本质上是求幂的倒数。所以,如果函数的每个"步骤"都在从原始项集中消除元素的一个因子,那就是对数时间算法。
对于树示例,可以很容易地看到,在继续遍历时,降低节点级别会降低元素的指数数量。查看按名字排序的电话簿的流行示例实质上相当于遍历二进制搜索树(中间页是根元素,您可以在每个步骤中推断是向左还是向右)。
这两个案例需要O(log n)时间
1 2 3 4 5 6 7 8 9 10 11 12 | case 1: f(int n) { int i; for (i = 1; i < n; i=i*2) printf("%d", i); } case 2 : f(int n) { int i; for (i = n; i>=1 ; i=i/2) printf("%d", i); } |
型
对数函数是指数函数的倒数。换句话说,如果你的输入是指数增长的(而不是像你通常认为的那样线性增长),你的函数是线性增长的。
二进制搜索的运行时间复杂性是
合并排序的运行时间复杂性是
*记住,根据定义,大O符号,常数并不重要。另外,通过改变对数的底数规则,不同底数的对数之间唯一的差别就是一个常数因子。
它是简单的均值的时间需要为这个任务的生长与log(n))(例如:2S(N = 10,4S(N = 100…………………)。读《维基百科的文章是二进制搜索算法和大O notation precisions更多的方法。 </P >
在O(log n)是一位misleading,更多的precisely是O(log子><2</子>),即(logarithm基地(2)。 </P >
在一个高度,平衡计分卡的二元树是O(log子><2</子>),因为每一个节点有两个(音"双"作为在一个日志<<2>子/子>)的子节点。所以,A和N节点的树,有一个高的日志(<<2>子/子>。 </P >
另一个实例是BINARY SEARCH,这有一个运行时间为O(log子><2</子>),因为在每一步你鸿沟的搜索空间;2。 </P >
简单地说:在你的算法的每一步中,你可以把工作减半。(渐近等价于第三,第四,…)
如果你图的对数函数的一种图形Calculator或类似的东西,你将看到,它真的很慢—上涨,甚至更慢比的线性函数。 </P >
这就是为什么用对数算法的时间复杂性是高广受欢迎。在夜的两大方法:(让我们说S = N×10^8,for example),他们的颈静脉孔区以上的acceptably。。。。。。。 </P >
But what exactly is O(log n)
确切地说,"
或者,实际上,这并不意味着;更可能是指"
"倾向于"具有"分析"通常的数学意义:例如,"如果你选择任意小的非零常数
在lay术语中,它意味着时间方程可能有一些其他的成分:例如,它可能有一些恒定的启动时间;但是对于n的大值,这些其他成分会变得微不足道,而a*log(n)是大n的主要术语。
注意,如果方程是,例如…
时间(n)=a+博客(n)+cn+dnn
……那么这将是O(n的平方),因为无论常数a、b、c和非零值d的值是多少,对于任何足够大的n值,
这就是BitO符号的意思:它的意思是"对于任何足够大的n,主项的次序是什么"。
我可以添加一些有趣的东西,我很久以前在科尔曼等人的书中读到的。现在,想象一个问题,我们必须在问题空间中找到解决方案。这个问题空间应该是有限的。
现在,如果你能证明,在你的算法的每次迭代中,你切断了这个空间的一小部分,这不小于某个限制,这意味着你的算法在O(logn)时间内运行。
我应该指出,我们这里讨论的是相对分数极限,而不是绝对分数极限。二进制搜索是一个经典的例子。在每个步骤中,我们都会丢弃问题空间的1/2。但是二进制搜索并不是唯一这样的例子。假设,您以某种方式证明了,在每一步中,您都会丢弃至少1/128的问题空间。这意味着,您的程序仍然在O(logn)时间运行,尽管比二进制搜索慢得多。这是分析递归算法的一个很好的提示。通常可以证明,在每一步递归都不会使用多个变量,这会导致问题空间中某些分数的截止。
如果有深度为d、大小为n的m叉树,则:
穿过整棵树~o(m^d)=o(n)
在树上走一条路~o(d)=o(对数n到基数m)
我可以举一个for循环的例子,也许一旦掌握了这个概念,在不同的上下文中理解可能会更简单。
这意味着在循环中,步骤会呈指数增长。例如。
1 | for (i=1; i<=n; i=i*2) {;} |
这个程序的O符号的复杂性是O(log(n))。让我们试着用手循环它(n介于512和1023之间(不包括1024):
1 2 | step: 1 2 3 4 5 6 7 8 9 10 i: 1 2 4 8 16 32 64 128 256 512 |
虽然n介于512和1023之间,但只发生了10次迭代。这是因为循环中的步骤以指数形式增长,因此只需10次迭代即可到达终止。
The logarithm of x (to the base of a) is the reverse function of a^x.
It is like saying that logarithm is the inverse of exponential.
现在试着这样看,如果指数增长很快,那么对数增长(相反)很慢。
O(n)和O(log(n))之间的差异很大,类似于O(n)和O(a^n)(a是常数)之间的差异。
在信息技术中,这意味着:
1 2 3 | f(n)=O(g(n)) If there is suitable constant C and N0 independent on N, such that for all N>N0 "C*g(n) > f(n) > 0" is true. |
看来这个符号大部分是从数学中提取的。
在本文中有一个引用:D.E.Knuth,"大Omicron和大Omega和大Theta",1976年:
On the basis of the issues discussed here, I propose that members of
SIGACT, and editors of computer science and mathematics journals,
adopt notations as defined above, unless a better alternative can be
found reasonably soon.
今天是2016年,但我们今天仍在使用它。
在数学分析中,这意味着:
1 | lim (f(n)/g(n))=Constant; where n goes to +infinity |
但即使在数学分析中,有时这个符号也用于表示"c*g(n)>f(n)>0"。
我从大学就知道这个符号是由德国数学家兰道(1877-1938)提出的。
实际上,如果您有一个n个元素的列表,并从该列表创建一个二叉树(就像在分治算法中一样),那么您将一直除以2,直到达到大小为1的列表(叶)。
在第一步,你除以2。然后有2个列表(2^1),将每个列表除以2,这样就有4个列表(2^2),再除以8个列表(2^3),依此类推,直到列表大小为1
这给了你一个等式:
(你取每边的lg,lg为对数基数2)
每次我们编写一个算法或代码时,我们都试图分析它的渐进复杂性。它不同于它的时间复杂性。
渐近复杂度是一种算法执行时间的行为,而时间复杂度是实际执行时间。但有些人可以互换使用这些术语。
因为时间的复杂性取决于各种参数,即1。物理系统2。程序设计语言三。编码风格4。还有更多……
实际执行时间不是一个很好的分析度量。
相反,我们将输入大小作为参数,因为无论代码是什么,输入都是相同的。所以执行时间是输入大小的函数。
下面是线性时间算法的一个例子
线性搜索给定n个输入元素,要搜索数组中的元素,最多需要"n"个比较。换句话说,不管您使用什么编程语言,您喜欢什么编码风格,在什么系统上执行它。在最坏的情况下,它只需要n个比较。执行时间与输入大小成线性比例。
它不仅仅是搜索,不管是什么工作(增量、比较或任何操作),它都是输入大小的函数。
所以当你说任何算法是O(log n)时这意味着执行时间是log乘以输入大小n。
随着输入大小的增加,完成的工作(这里是执行时间)也会增加。(因此成比例)
1 2 3 4 | n Work 2 1 units of work 4 2 units of work 8 3 units of work |
请参阅,随着输入大小的增加,所完成的工作将增加,并且它独立于任何机器。如果你试图找出工作单位的价值它实际上取决于以上指定的参数,它将根据系统和所有参数进行更改。
如果你在寻找基于直觉的答案,我想为你提出两种解释。
想象一下,一座非常高的小山,底部也很宽。要到达山顶,有两种方法:一种是绕着山顶盘旋的专用通道,另一种是:像小平台一样的雕刻,切割出一个楼梯。现在,如果第一种方法是以线性时间o(n)到达,第二种方法是o(logn)。
假设一个算法接受一个整数,
二是以完全的O(LN N),因为搜索就像这样: </P >
1 | 1 2 3 4 5 6 7 8 9 10 11 12 |
找矿方法YIELDS打4 3 6、3和4。和log2 12 3例,这是一个好的apporximate到如何在许多成功的需要。 </P >
分治范式中的算法具有复杂性O(logn)。这里的一个例子,计算你自己的幂函数,
1 2 3 4 5 6 7 8 9 10 11 | int power(int x, unsigned int y) { int temp; if( y == 0) return 1; temp = power(x, y/2); if (y%2 == 0) return temp*temp; else return x*temp*temp; } |
来自http://www.geeksforgeks.org/write-a-c-program-to-calculate-powxn/
我想补充一点,树的高度是从根到叶的最长路径的长度,节点的高度是从该节点到叶的最长路径的长度。路径是指我们在两个节点之间遍历树时遇到的节点数。为了实现O(log n)时间复杂性,树应该是平衡的,这意味着任何节点的子节点之间的高度差应该小于或等于1。因此,树并不总是保证时间复杂性O(log n),除非它们是平衡的。实际上,在某些情况下,树中搜索的时间复杂性在最坏的情况下可能是O(N)。
你可以看看平衡树,比如