O(log n)到底是什么意思?


2138

我正在了解Big O Notation运行时间和摊销时间。我理解O(n)线性时间的概念,这意味着输入的大小会成比例地影响算法的增长...例如,二次时间O(n 2)等也是如此。 ,例如乘数为O(n!)的排列生成器,并通过阶乘增长。

例如,以下函数为O(n),因为该算法与输入n成正比增长:

f(int n) {
  int i;
  for (i = 0; i < n; ++i)
    printf("%d", i);
}

同样,如果存在嵌套循环,则时间将为O(n 2)。

但是O(log n)到底是什么?例如,说一个完整的二叉树的高度为O(log n)是什么意思?

我确实知道(也许不是很详细)什么是对数,即:log 10 100 = 2,但是我不明白如何用对数时间来识别一个函数。


60
一节点二叉树的高度为log2(1)+1 = 1,二节点树的高度为log2(2)+1 = 2,四节点树的高度为log2(4)+1 = 3,并且以此类推。n节点树的高度为log2(n)+1,因此,在树上添加节点会导致其平均高度对数增长。
David R Tribble

36
我在大多数答案中看到的一件事是,它们本质上描述了“ O(某物)”,这意味着算法的运行时间与“某物”成比例地增长。考虑到您要求的是“ O(log n)”的“确切含义”,所以这不是真的。那是Big-Theta符号的直观描述,而不是Big-O。O(log n)的直观表示运行时间增长最多比例“登录N”:stackoverflow.com/questions/471199/...
迈赫达德Afshari

31
我一直记得以分而治之作为O(log n)的示例
RichardOD

14
重要的是要意识到它的日志基数为2(而不是基数为10)。这是因为在算法的每个步骤中,您都会删除剩余选择的一半。在计算机科学中,我们几乎总是处理对数为2的对数,因为我们可以忽略常数。但是,也有一些例外情况(即,四叉树的运行时间是基于日志的4)
Ethan

13
@Ethan:所使用的基数无关紧要,因为基数转换只是一个常数乘法,所以公式为log_b(x)= log_d(x)/ log_d(b)。Log_d(b)将只是一个常数。
病毒

Answers:


2709

我不明白如何用记录时间来识别功能。

对数运行时函数的最常见属性是:

  • 选择下一个要执行操作的元素是几种可能性之一,并且
  • 只需要选择一个。

要么

  • 执行操作的元素是n的数字

例如,这就是为什么在电话簿中查找人为O(log n)的原因。您无需检查电话簿中的每个人都可以找到合适的人。取而代之的是,您可以根据名字的字母顺序在哪里进行分而治之,并且在每个部分中,只需找到每个部分的子集即可,最终找到某人的电话号码。

当然,更大的电话簿仍会花费您更长的时间,但增长速度不会像增加的其他电话一样成比例地增长。


我们可以扩展电话簿示例,以比较其他类型的操作及其运行时间。我们将假定电话簿中的公司(“黄页”)具有唯一的名称,而(“白页”)则可能没有唯一的名称。一个电话号码最多分配给一个人或一个公司。我们还将假设翻转到特定页面需要花费固定时间。

以下是我们可能在电话簿上执行的某些操作的运行时间,从最快到最慢:

  • O(1)(在最坏的情况下):给定一家企业名称所在的页面和该企业名称,找到电话号码。

  • O(1)(在通常情况下):给定一个人的名字所在的页面及其名字,找到电话号码。

  • O(log n):给定一个人的名字,通过在您尚未搜索的书本的一半左右随机选择一个点来找到电话号码,然后检查该人的名字是否在该点上。然后在此人名字所在的部分的一半左右重复该过程。(这是对人名的二进制搜索。)

  • O(n):查找所有电话号码包含数字“ 5”的人。

  • O(n):给定一个电话号码,找到具有该号码的人或公司。

  • O(n log n):打印机办公室里有些混乱,我们的电话簿中所有页面都以随机顺序插入。通过查看每个页面上的名字,然后将该页面放在新的空电话簿中的适当位置,可以更正顺序,使其正确无误。

对于以下示例,我们现在在打印机办公室。电话簿正在等待邮寄给每个居民或企业,每本电话簿上都有一个标签,用于标识应将其邮寄到何处。每个人或每个企业都会获得一本电话簿。

  • O(n log n):我们想对电话簿进行个性化设置,因此我们将在其指定副本中查找每个人或公司的名称,然后在电话簿中圈出他们的姓名,并为他们的惠顾写一封简短的感谢信。

  • O(n 2):在办公室发生了一个错误,每个电话簿中的每个条目在电话号码的末尾都有一个额外的“ 0”。进行一些涂白,并删除每个零。

  • O(n·n!):我们已经准备好将电话簿加载到运输码头上。不幸的是,原本应该用来装书的机器人已经走到了尽头:它将书随机地放到卡车上!更糟糕的是,它将所有书籍装到卡车上,然后检查它们的顺序是否正确,如果不正确,则将其卸下并重新开始。(这是可怕的bogo排序。)

  • O(n n):修复机器人,使其正确装载东西。第二天,您的一个同事在对您进行恶作剧,并将装卸台机器人连接到自动打印系统。每次机器人去加载原书时,工厂打印机都会重复运行所有电话簿!幸运的是,该机器人的错误检测系统足够复杂,以至于当机器人遇到一本重复的书本进行加载时,它不会尝试打印更多的副本,但是仍然必须加载每本印刷的原始书本和重复本。


81
@cletus:碰巧,恐怕。我之所以选择它,是因为电话簿具有很大的N,人们可以理解它们的含义和作用,并且因为它的用途广泛。另外,我必须在解释中使用机器人!全方位获胜。(而且,看来您的答案是在我甚至还没有成为StackOverflow成员之前就做出的!)
John Feminella 2010年

12
“办公室发生了一个错误,每个电话簿中的每个条目在电话号码的末尾都有一个额外的“ 0”。进行一些涂白,并删除每个零。<-这不是N阶的平方。N定义为输入的大小。输入的大小是电话号码的数量,即每本书的数量乘以本书的数量。那仍然是线性时间操作。
Billy ONeal 2010年

21
@Billy:在此示例中,N是一本书中的人数。由于电话簿中的每个人也都有自己的电话簿副本,因此存在N 相同的电话簿,每个电话簿中都有N人,这就是O(N ^ 2)。
约翰·费米内拉

48
O(1)不是最好的情况,不是最坏的情况吗?
Svip

54
我花了O(long⅝n!n-55 / 2)的时间来找到一个O(log n)定义,该定义最终是有意义的。+1
iAteABug_And_iLiked_it

611

O(log N)基本上意味着时间线性增长,而n指数增长。因此,如果需要1第二个计算10单元,将采取2秒来计算100的元素,3秒来计算1000的元素,依此类推。

O(log n)`` 这是我们进行分治制算法的时间,例如二进制搜索。另一个例子是快速排序,其中每次我们将数组分为两部分,并且每次花费O(N)时间来查找枢轴元素时。因此 N O(log N)


108
三行智慧击败了所有其他论文答案... :)以防万一有人丢失它,在编程上下文中,对数的底数为2(而不是10),因此O(log n)的缩放比例为1秒,持续10秒元素,2秒,20,3 40等
nawfal

3
达成共识,简洁明了,尽管OP的最终问题是如何识别对数函数,但并不完全是“这是什么”
亚当

4
是的,对数函数是指数函数的逆函数。((log x)base a)是(幂x)的逆数。用图形对这些函数进行定性分析将提供更多的直觉。
2014年

7
这花了我大约3次通读,才意识到这没错。时间线性增长,而元素数量呈指数增长。这意味着可以在更少的时间内获得更多的元素。对于那些可视log化为图形上熟悉的对数曲线的人来说,这是个精神上的负担。
Qix-蒙尼卡(Monica)

1
我认为这是一个很好的答案,除了声称二进制搜索是一种分而治之算法的部分。不是。
code_dredd

579

关于这个问题,已经有很多好的答案,但是我相信我们确实缺少一个重要的答案,那就是图示的答案。

说完整的二叉树的高度为O(log n)是什么意思?

下图描绘了一个二叉树。请注意,每个级别如何包含的节点数量是以上级别的两倍(因此,binary):

二叉树

二进制搜索就是一个复杂的例子O(log n)。假设图1中树的最底层节点代表某种已排序集合中的项目。二进制搜索是一种分而治之的算法,该图显示了我们如何(最多)需要进行4次比较才能找到我们要在16个项目数据集中搜索的记录。

假设我们有一个包含32个元素的数据集。继续上面的绘图,发现我们现在需要5个比较来查找我们要寻找的内容,因为当我们乘以数据量时,树仅增长了一层。结果,算法的复杂性可以描述为对数阶。

log(n)在一张普通纸上绘图,将生成一张图表,其中曲线的上升随着n增加而减速:

O(log n)


60
“请注意,每个级别包含的节点数量均高于上一级(因此为二进制)”,这是不正确的。您要描述的是平衡的二叉树。一棵二叉树只意味着每个节点最多有两个孩子。
Oenotria

8
实际上,这是一个非常特殊的平衡二叉树,称为完整二叉树。我已经编辑了答案,但需要有人批准。
user21820

5
完整的二叉树不需要完全填充最后一个级别。我想说,“完整的二叉树”更合适。
AJ先生2014年

您的答案试图对OP的原始问题做出更具体的反应,因此它比当前接受的答案(IMO)更好,但是仍然非常不完整:您只给出了一半的示例和2张图片...
nbro

2
该树中有31个项目,而不是16个。为什么将其称为16个项目数据集?它上的每个节点都代表一个数字,否则它将是效率低下的二叉树:P
Perry Monschau,2016年

245

以下说明使用的是完全平衡的二叉树的情况,以帮助您了解我们如何获得对数时间复杂度。

二叉树是将大小为n的问题分为大小为n / 2的子问题,直到我们遇到大小为1的问题的情况:

二叉树的高度

这就是获得O(log n)的方式,这是需要在上述树上完成的工作量才能找到解决方案。

具有O(log n)时间复杂度的常见算法是二进制搜索,其递归关系为T(n / 2)+ O(1),即,在树的每个后续级别上,您都将问题分为两部分,并进行固定数量的附加工作。


2
新手在这里。那么,您能否说树的高度是递归除法以达到n = 1的比例?
科迪

@Cody,是的,在大多数情况下,您的观察是准确的。此示例说明/利用log_2。您的观察将超出范围log_2并且在任何log_x地方都是准确的x > 1。进行直除可能不会精确到1,因此您可能要说递归除法,直到Ceiling()最新除法的等于1或类似的结果为止。
James Oravec

198

总览

其他人给出了很好的图表示例,例如树形图。我没有看到任何简单的代码示例。因此,除了我的解释之外,我还将为某些算法提供简单的打印语句,以说明不同算法类别的复杂性。

首先,您需要了解对数的一般概念,您可以从https://en.wikipedia.org/wiki/Logarithm获得。自然科学用途e和自然对数。工程弟子将使用log_10(对数为10),计算机科学家将大量使用log_2(对数为2),因为计算机是基于二进制的。有时,您会看到自然日志的缩写为ln(),工程师通常不使用_10,而是直接使用,log()而log_2则缩写为lg()。所有类型的对数以相似的方式增长,这就是为什么它们共享相同的对数类别log(n)

当您看下面的代码示例时,我建议先看O(1),然后看O(n),再看O(n ^ 2)。当你对这些好之后,再看看其他的。我提供了一些干净的示例以及各种变体,以演示微妙的变化仍然可以导致相同的分类。

您可以将O(1),O(n),O(logn)等视为增长的类或类别。有些类别比其他类别需要更多时间。这些类别有助于为我们提供一种排序算法性能的方式。随着输入n的增长,某些增长更快。下表通过数值显示了上述增长。在下表中,将log(n)视为log_2的上限。

在此处输入图片说明

各种大O类别的简单代码示例:

O(1)-恒定时间示例:

  • 算法1:

算法1只打印一次hello,它不依赖于n,因此它将始终在恒定时间内运行,因此它是O(1)

print "hello";
  • 算法2:

算法2打印hello 3次,但是它不依赖于输入大小。即使随着n的增长,该算法也始终只会打印hello 3次。就是说3是一个常数,因此该算法也是O(1)

print "hello";
print "hello";
print "hello";

O(log(n))-对数示例:

  • 算法3-行为类似于“ log_2”

算法3演示了在log_2(n)中运行的算法。注意for循环的后操作将i的当前值乘以2,因此i从1到2到4到8到16到32 ...

for(int i = 1; i <= n; i = i * 2)
  print "hello";
  • 算法4-类似于“ log_3”

算法4演示了log_3。通知i从1到3到9到27 ...

for(int i = 1; i <= n; i = i * 3)
  print "hello";
  • 算法5-行为类似于“ log_1.02”

算法5很重要,因为它有助于表明只要数字大于1并且结果与自身重复相乘,就意味着您正在寻找对数算法。

for(double i = 1; i < n; i = i * 1.02)
  print "hello";

O(n)-线性时间示例:

  • 算法6

该算法很简单,可以打出n次问候。

for(int i = 0; i < n; i++)
  print "hello";
  • 算法7

该算法显示了一种变体,它将打出n / 2次问候。n / 2 = 1/2 * n。我们忽略了1/2常数,并看到此算法为O(n)。

for(int i = 0; i < n; i = i + 2)
  print "hello";

O(n * log(n))-nlog(n)示例:

  • 算法8

这个作为组合O(log(n))O(n)。for循环的嵌套可帮助我们获得O(n*log(n))

for(int i = 0; i < n; i++)
  for(int j = 1; j < n; j = j * 2)
    print "hello";
  • 算法9

算法9与算法8相似,但是每个循环都允许变化,最终仍会导致 O(n*log(n))

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

O(n^2) 通过嵌套循环标准很容易获得。

for(int i = 0; i < n; i++)
  for(int j = 0; j < n; j++)
    print "hello";
  • 算法11

类似于算法10,但有一些变化。

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个循环。

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类似,但仍会产生一些变化O(n^3)

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";

摘要

上面给出了几个简单的示例,并通过各种变体来说明可以引入哪些细微的更改,这些更改实际上并不会改变分析。希望它能给您足够的见识。


17
太棒了 我见过的最好的解释。如果这会是更好的O(n^2)表述为组合O(n)O(n),因此O(n) * O(n) = O(n * n) = O(n^2)。没有这个方程,感觉就像是在跳跃。这是对先前解释的重复,但是我认为这种重复可以使读者更有信心进行理解。
Eonil '16

2
这仅仅是有史以来最好的解释。
Edgar Kiljak

2
@IceTea,为您提供有关问题的真知灼见。如果您将图表n与图表相对,n/2就会发现它们都成一条直线。这使它们处于相同的类别,因为它们具有相似的增长率(可以将其视为图表的形状)。同样,如果将图表log_2与图表进行比较,log_3则会发现它们都呈现“相似的形状”或“相似的增长率”。
James Oravec '18

1
@ IceTea,@ Shai和@James给出的解释更准确,n/2 or 2n or n+2 or n它们在图中的线条不同,但是它们的增长率相同,这意味着它们都将遵循线性增长。
Naresh Joshi

2
如果有两个嵌套循环,但是第二个迭代器依赖第一个迭代器,这种依赖性会影响时间复杂度吗?
Bionix1441

131

如果您的函数需要:

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.

然后,它需要log 2(n)时间。在大O符号,严格地讲,这意味着关系只需要进行大的n是真实的,而且持续性因素和更小的方面可以忽略不计。


log2(n)与o(log n)相同吗?
Sven van den Boogaart's

是的,请在此处查看nawfal的评论以获取其他答案:(复制粘贴)-在编程上下文中,日志的底数是2(而不是10),因此O(log n)的缩放比例为10秒1秒,20秒2秒,3代表40,等等
Andrejs

@SvenvandenBoogaart,此解决方案中的示例log_2在类中进行了说明O(log(n))。有许多人在同一类的O(log(n)),即log_x其中x > 1
詹姆斯Oravec

@Andrejs,您的评论so O(log n) scales like 1 sec for 10 elements, 2 sec for 20, 3 for 40 etc不正确。该模式/类将与O(n)not 匹配/对齐O(log(n))。如果有人感兴趣的是log_10然后等效示例将是1秒,持续10组的元素,2秒100,3 1000等
詹姆斯Oravec

99

对数运行时间(O(log n))本质上意味着运行时间与输入大小的对数成比例增长-例如,如果10个项目最多花费一定的时间x,而100个项目最多花费(例如2x)和10,000个项目最多花费时间4x,然后看起来就像是O(log n)时间复杂度。


1
+1,但您确实应该指出,它是log2,而不是log10。
阿德里亚诺瓦里

62
log2或log10不相关。它们仅因比例因子而不同,这使它们具有相同的数量级,即它们仍以相同的速率增长。
Noldorin 2010年

17
对数的有趣之处在于,当比较相对高度时,使用的确切基数并不重要。log 10,000 / log 100不论您使用什么基础,均为2。
Anon。

12
要挑剔,O(lg n)表示运行时间最多与lg n成正比。您描述的是Theta(lg n)。

1
@rgrig:是的。我进行了一些“至多”编辑,以指示big-O的上限性质。
Anon。

95

对数

好吧,让我们尝试并完全理解对数实际上是什么。

想象一下,我们有一条绳索,并且已经将它绑在马上了。如果将绳索直接绑在马匹上,则马匹需要拉开的力(例如,从人身上拉开)直接为1。

现在,想象一下绳子缠绕在一根杆子上。要逃脱的马匹现在将不得不更努力地拉很多次。时间的长短取决于绳索的粗糙度和杆子的大小,但让我们假设它会将一个人的力量乘以10(绳索完全转一圈)。

现在,如果将绳子缠绕一次,则马将需要拉力十倍。如果人类决定让这匹马真正困难,他可以将绳索再次绕在一根杆子上,使其强度再增加10倍。第三个循环将再次使强度再提高10倍。

在此处输入图片说明

我们可以看到,对于每个循环,该值都会增加10。获得任何数字所需的匝数称为该数字的对数,即,我们需要3个柱将您的强度乘以1000倍,将6个柱将您的强度乘以1,000,000。

3是1,000的对数,而6是1,000,000(以10为底)的对数。

那么O(log n)到底是什么意思?

在上面的示例中,我们的“增长率”为O(log n)。每增加一个环,我们的绳索可以承受的力就会增加十倍:

Turns | Max Force
  0   |   1
  1   |   10
  2   |   100
  3   |   1000
  4   |   10000
  n   |   10^n

现在,上面的示例确实使用了以10为底,但是幸运的是,当我们谈论大符号时,日志的底数是微不足道的。

现在,让我们假设您正在尝试猜测1-100之间的数字。

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个猜测就可以解决这个问题。但是这里的关系是什么?您可以从每个其他猜测中猜出的最大项目数是多少?

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),但这是另一回事了。

我们可以看到,对于每个猜测,我们的数据集都在缩小。判断算法是否具有对数时间的一个好的经验法则是查看每次迭代后数据集是否按一定顺序收缩

O(n log n)呢?

您最终会遇到线性时间O(n log(n))算法。上面的经验法则再次适用,但是这次对数函数必须运行n次,例如,将列表的大小减小n次,这在诸如mergesort之类的算法中会发生。

您可以轻松确定算法时间是否为n log n。寻找一个遍历列表(O(n))的外部循环。然后查看是否存在内部循环。如果内部循环在每次迭代中削减/减少数据集,则该循环为(O(log n)),因此整个算法为= O(n log n)

免责声明:绳对数示例是W.Sawyer从出色的数学家的Delight书中获得的


No. In our example above, our 'growth rate' is O(log n). For every additional loop, the force our rope can handle is 10 times more,由图表支持,该图表显示n ==循环数和our 'growth rate'=> 10 ^ n,这不是log n。通过制作可以使示例正确n=# horses,这需要log n循环进行约束。不良的教学实例使学生只相信自己的理解。
psimpson

56

您可以说时间与N中的位数成正比,因此可以直观地想到O(log N)。

如果操作对输入的每个数字或位执行恒定时间的工作,则整个操作将花费的时间与输入中的数字或位的数量成比例,而不是输入的大小;因此,O(log N)而不是O(N)。

如果某个操作做出一系列恒定时间决策,每个决策都将要考虑的输入大小减半(减少3、4、5 ..),则整个过程将花费与对数底数2(底数3)成比例的时间,基数4,基数5 ...),其大小为输入的大小N,而不是O(N)。

等等。


7
我认为,与大多数解释相比,该方法足够准确且更容易掌握。
吨。

这是对的解释,对log<sub>10</sub> N吗?
刘岩刘研

1
@LiuYan刘研他们没有说数字的位数。无论如何,log 2(n)=log₁₀(n)/log₁₀(2)和1 /log₁₀(2)是一个常数乘数,同样的原则适用于所有其他基础。这显示了两件事。首先,月影原理适用于任何基础(尽管基础越低,估计中的“锯齿”越少),而且无论导致什么结论的基础是什么,O(log n)都是O(log n)。 。
乔恩·汉娜

“比例” ...“每个都将输入的大小减半”
csguy

52

我一直必须在心理上可视化以O(log n)运行的算法的最佳方法如下:

如果您将问题的大小增加一个乘数(即,将其大小乘以10),那么功只会增加一个加法数。

将其应用于您的二叉树问题,这样您就可以很好地应用:如果将二叉树中的节点数加倍,则高度仅增加1(加法数)。如果再次将其加倍,它仍然仅增加1。(显然,我假设它保持平衡,等等)。这样,您不会在问题规模成倍增加的情况下加倍工作,而只是做更多的工作。这就是O(log n)算法很棒的原因。


52

首先,我建议您阅读以下书籍;

算法(第4版)

这是一些功能及其预期的复杂性。数字表示语句执行的频率

这是一些功能及其预期的复杂性

big-O复杂度图表也来自bigocheatsheet 大O复杂度图表

最后,一个非常简单的展示会展示了它是如何计算的;

程序语句执行频率的剖析。

分析程序的运行时间(示例)。

分析程序的运行时间


5
我不会把O(n log n)放在篮子里。它属于公平的
安德烈Werlang

查看大O复杂度图表(上)时,您必须记住O(n)是实际的线性点,而不是粉红色/橙色边界。@Andre这就是为什么O(n log n)正确标记在“性能”差的括号内的原因,它的性能比线性差。
JavaBeast

@JavaBeast是正确的,尽管从技术上说O(n log n)的性能比O(n)差,但是请参考上表,它们对它们进行了很好的比较(请参阅两者的增长)。图表从不同的来源来看是矛盾的,因为它使O(1)和O(log n)处于相同的良好/优良状态。它们的相对增长顺序可与O(n)和O(n log n)相媲美。tl; dr; O(n log n)并不出色,但远非劣。
安德烈·沃朗(AndréWerlang)'18

1
这个答案是错误的!假设N = N *N。实际上N = N!您的示例实际上是N立方的。您在图中执行相同的操作。您的O(n)实际上应该是恐怖与坏之间的鸿沟。数学证明:您说for循环对于O(1)是常数。这就是1的实际含义,不依赖于N。它仅表示不可变。但是它是可变的,因为它取决于N。两次N,并且只有一半时间。因此无效。如果是那本书,那就不要买!您显示的代码图形不是真实的,这是个玩笑,看起来是“ Theesome”,这意味着三个人一次发生性关系!OMG
jgmjgm

1
O(n)不应该在对角线上吗?
gyosifov

46

日志b(n)是多少?

这是您可以在到达大小为1的部分之前将长度为n的对数重复切成b个相等部分的次数。


极好的评论!简洁明了,正是我所追求的答案。
DennisL

18

分而治之算法通常具有 logn与运行时间有关。这源于输入的重复减半。

对于二进制搜索,每次迭代都将丢弃一半的输入。应该注意的是,在Big-O表示法中,log是日志基数2。

编辑:如前所述,对数基数无关紧要,但是在推导算法的Big-O性能时,对数因子将减半,因此我为什么将其视为基数2。


2
为什么以2为底数?例如,在随机快速排序中,我不认为它是基数2。据我所知,基数并不重要,因为对数基数a(n)= log2(n)/ log2(a),所以每个对数与另一个的区别在于常量,并且常量用big-o表示法忽略。实际上,在我看来,写大写记数法的基础是错误的,因为您正在编写常量。
IVlad 2010年


的确可以将其转换为任何基数,这并不重要,但是,如果您尝试获得Big-O性能并且看到不断减半,则有助于理解您不会在代码中看到以10为底的对数。
David Kanarek 2010年

旁白:在诸如B树之类的节点中,扇出数大于2(即比二叉树“宽”)的情况下,您仍然会看到O(logn)增长,因为它仍在分裂和-conquer,但是日志的基础将与扇出有关。
罗杰·利普斯科姆2010年

实际上,日志2的题外话非常有帮助。
Dan Rosenstark

15

但是O(log n)到底是什么?例如,说一个>完整的二叉树的高度是O(log n)是什么意思?

我将其重新表述为“一个完整的二叉树的高度为log n”。如果您要逐步遍历,则确定完整的二叉树的高度将为O(log n)。

我不明白如何用对数时间识别函数。

对数本质上是幂的倒数。因此,如果您的功能的每个“步骤”都消除了一个因素从原始项目集中,那就是对数时间算法。

对于树示例,您可以轻松地看到,在继续遍历时,降低节点级别将减少指数级的元素。翻阅按名称排序的电话簿的流行示例从本质上讲等同于遍历二叉搜索树(中间页是根元素,您可以在每一步中推论是左移还是右移)。


3
+1表示“对数本质上是幂的倒数”。
talonx 2013年

12

这两种情况将花费O(log n)时间

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);
    }

我确定我会丢失某些东西,但是由于0 * 2 = 0和0/2 = 0,在这两种情况下我都不会始终为零并且循环永远运行吗?
dj_segfault 2013年

2
@dj_segfault,那是我的错误。我认为现在确实是合理的.. :)
Ravi Bisla 2013年

@RaviBisla其他答案指出,输入10所花的时间是10个循环的1倍,输入100所花的时间是输入1的3倍,这在这些示例中显然不是这种情况。stackoverflow.com/a/2307330/1667868
Sven van den Boogaart,

12

O(log n)有点误导,更确切地说是O(log 2 n),即(以2为底的对数)。

平衡二叉树的高度为O(log 2 n),因为每个节点都有两个(在log 2 n中请注意“两个” )子节点。因此,具有n个节点的树的高度为log 2 n。

另一个示例是二进制搜索,它的运行时间为O(log 2 n),因为在每一步您都将搜索空间除以2。


4
O(log n)与O(ld n)或O(LN n)的阶数相同。它们是成比例的。我了解,出于学习目的,使用ld更容易。
helios 2010年

4
“更确切地说,它是O(ld n)”-不,不是:所有日志都是相同的顺序(每个日志之间只有一个恒定的比例因子是不同的,可以忽略/可忽略)。
ChrisW 2010年

1
克里斯,你说得对,措辞很不好。应该像太阳神说的那样说。它有助于学习/理解,但最终所有日志的顺序相同。
stmax 2010年

10

O(log n) 指的是一个函数(或算法,或算法中的步骤),其工作时间与对数成正比(通常在大多数情况下以2为底,但并非总是如此,无论如何,用big-O表示法无关紧要*)输入的大小。

对数函数是指数函数的反函数。换句话说,如果您的输入呈指数增长(而不是通常认为的线性增长),则函数呈线性增长。

O(log n)在任何一种“分而治之”的应用程序中,运行时间都是很常见的,因为(理想情况下)每次都将工作量减半。如果您在每个分割或征服步骤中都在进行固定时间工作(或不是固定时间的工作,但是时间增长的速度比慢O(log n)),则整个功能为O(log n)。通常,每个步骤都需要线性输入时间,而不是线性输入。这将导致总的时间复杂度为O(n log n)

二进制搜索的运行时间复杂性就是一个例子O(log n)。这是因为在二进制搜索中,您总是在后面的每一步中忽略输入的一半,方法是将数组分为两半,而每一步只关注一半。每个步骤都是固定时间的,因为在二进制搜索中,无论您考虑的数组有多大,您只需将一个元素与您的键进行比较即可确定下一步要做什么。因此,您大约执行log(n)/ log(2)步骤。

合并排序的运行时间复杂度是的一个示例O(n log n)。这是因为您将每一步将数组分为两半,因此总共大约需要log(n)/ log(2)个步骤。但是,在每个步骤中,您都需要对所有元素执行合并操作(无论是对n / 2个元素的两个子列表进行一次合并操作,还是对n / 4个元素的四个子列表进行两次合并操作都是无关紧要的,因为这增加了必须在每个步骤中对n个元素执行此操作)。因此,总复杂度为O(n log n)

*请记住,按照定义,big-O表示法并不重要。同样,通过改变对数的基本规则,不同底数的对数之间的唯一区别就是一个常数因子。


最后的*注释解决了我对基于2或10的对数的困惑:)非常感谢。
yahya


9

简而言之:在算法的每个步骤中,您都可以将工作量减半。(渐近等效于第三,第四,...)


2
这个答案很不准确。首先,您可以考虑仅在以2为底的对数情况下才将工作减半。这真令人难以置信,这个答案(以及大多数原始问题的答案)是如何获得如此多的赞成票的。“(渐近等效于第三,第四……)”?如果没有时间,为什么要回答问题?
nbro

8

如果在图形计算器或类似的工具上绘制对数函数,您会发现它的上升非常缓慢-甚至比线性函数还慢。

这就是为什么具有对数时间复杂度的算法受到高度追捧的原因:即使对于非常大的n(例如,n = 10 ^ 8),它们的性能也可以接受。


7

但是O(log n)到底是什么

它的意思恰恰是“为n走向趋于infinity时,time趋向于a*log(n)何处a是恒定的缩放因子”。

或实际上,这并不完全意味着。它更可能表示“time除以a*log(n)趋向1”的内容。

“趋向”具有从“分析”通常的数学含义:例如,“如果你选择任何任意小的非零常数k,然后我能找到一个对应的值X,使得((time/(a*log(n))) - 1)小于k用于的所有值n大于X”。


通俗地说,它意味着时间方程可能具有其他一些组成部分:例如,它可能具有一定的启动时间;但是对于n较大的值,这些其他分量显得微不足道,而a * log(n)是n较大的支配项。

请注意,如果等式为...

time(n)= a + b log(n)+ c n + d n n

...这将是O(n平方),因为无论常数a,b,c和非零d的值是多少, d*n*n值是什么,对于n的任何足够大的值将始终主导其他项。

这就是O表示法的含义:它的意思是“对于任何足够大的n,主导项的顺序是什么”。



7

我可以添加一些很有趣的东西,很久以前我在Kormen等人的书中就读过。现在,想象一个问题,我们必须在问题空间中找到解决方案。这个问题空间应该是有限的。

现在,如果可以证明,在算法的每次迭代中,您都切掉了该空间的一小部分,即不少于某个限制,这意味着您的算法正在O(logN)时间内运行。

我应该指出,我们在这里谈论的是相对分数限值,而不是绝对限值。二进制搜索是一个经典示例。在每一步中,我们丢弃问题空间的1/2。但是二进制搜索并不是唯一的例子。假设您以某种方式证明了,在每个步骤中,您至少丢掉了1/128个问题空间。这意味着您的程序仍在O(logN)时间运行,尽管比二进制搜索要慢得多。这是分析递归算法的很好提示。经常可以证明,在每个步骤中递归都不会使用多个变体,这会导致问题空间中某些部分的截止。


6

我可以举一个for循环的例子,也许一旦掌握了这个概念,也许在不同的环境下将更容易理解。

这意味着在循环中,步数呈指数增长。例如

for (i=1; i<=n; i=i*2) {;}

该程序的O符号表示复杂度为O(log(n))。让我们尝试手动遍历它(n介于512和1023之间(不包括1024):

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次迭代即可到达终止点。

x的对数(以a为底)是a ^ x的反函数。

就像说对数是指数的倒数。

现在尝试以这种方式看待它,如果指数增长非常快,那么对数增长(反之)非常慢。

O(n)与O(log(n))之间的差异很大,类似于O(n)与O(a ^ n)之间的差异(a为常数)。


6

实际上,如果您有一个由n个元素组成的列表,并从该列表中创建一个二叉树(就像在分治法中一样),则将一直除以2,直到达到大小为1的列表(叶子)。

第一步,将您除以2。然后有2个列表(2 ^ 1),将每个列表除以2,因此,您有4个列表(2 ^ 2),再次除以,您有8个列表(2 ^ 3) ),依此类推,直到您的列表大小为1

那给你方程式:

n/(2^steps)=1 <=> n=2^steps <=> lg(n)=steps

(您将每边的lg作为对数,以lg为对数2)


2
直到某些恶意软件开始在叶子节点之前的两个级别插入一个长度为x的新列表。然后,这似乎将是一个无限循环……
弗朗西斯·库格勒

1
我没有收到你的评论。我的解释错了吗?
Dinaiz

1
我只是在开一个假想的笑话。我并没有真正意义。
弗朗西斯·库格勒

6

每次我们编写算法或代码时,我们都会尝试分析其渐近复杂性。它不同于时间复杂度

渐进复杂度是算法执行时间的行为,而时间复杂度是实际执行时间。但是有些人可以互换使用这些术语。

因为时间复杂度取决于各种参数,即。
1.物理系统
2.编程语言
3.编码样式
4.还有更多……

实际执行时间不是分析的好方法。


相反,我们将输入大小作为参数,因为无论代码是什么,输入都是相同的。 因此执行时间是输入大小的函数。

以下是线性时间算法的示例


线性搜索
给定n个输入元素,要搜索数组中的一个元素,您最多需要'n'比较。换句话说,无论您使用哪种编程语言,更喜欢哪种编码风格,在哪种系统上执行它。在最坏的情况下,它仅需要进行n次比较。执行时间与输入大小成线性比例关系。

而且它不仅是搜索,它的工作量(增量,比较或任何操作)都是输入大小的函数。

因此,当您说任何算法为O(log n)时,这意味着执行时间为log乘以输入大小n。

随着输入大小的增加,完成的工作(此处的执行时间)增加。

      n      Work
      2     1 units of work
      4     2 units of work
      8     3 units of work

看到随着输入大小的增加,完成的工作也增加了,并且它独立于任何机器。而且,如果您尝试找出工作单位的价值,则实际上取决于上面指定的参数,它将根据系统和所有要素而变化。


5

树

log x to base b = y 是...的逆 b^y = x

如果您有深度为d,大小为n的M元树,则:

  • 遍历整个树〜O(M ^ d)= O(n)

  • 在树中走一条路径〜O(d)= O(log n到底数M)


5

在信息技术中,这意味着:

  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.

蚂蚁似乎这种表示法大多来自数学。

在本文中有一个引语: DE Knuth,“大欧姆龙和大欧米茄和大THETA”,1976年

基于此处讨论的问题,我建议SIGACT的成员以及计算机科学和数学杂志的编辑采用上述定义的符号,除非可以在合理的时间内找到更好的替代方法

今天是2016年,但今天仍然使用它。


在数学分析中,这意味着:

  lim (f(n)/g(n))=Constant; where n goes to +infinity

但是即使在数学分析中,有时也会使用该符号表示“ C * g(n)> f(n)> 0”。

从大学知道,这个符号是由德国数学家Landau(1877-1938)引入的


3

完整的二进制示例为O(ln n),因为搜索如下所示:

1 2 3 4 5 6 7 8 9 10 11 12

搜索4会产生3个匹配:6、3然后是4。log2 12 = 3,这与需要多少匹配比较合适。


谢谢你的例子。它清楚地说明了我们的算法如何在分治法中使用对数时间。
广播公司

因此,如果循环为n / 2,则始终为log(n)?
吉尔·贝瑞斯

3

如果您正在寻找基于直觉的答案,我想为您提供两种解释。

  1. 想象一下一个很高的山丘以及一个非常广阔的基础。要到达山顶,有两种方法:一种是沿着山顶螺旋形延伸的专用路径,另一种是:像雕刻品一样切成小台阶的小露台。现在,如果第一种方法到达线性时间O(n),第二种方法就是O(log n)。

  2. 想象一个接受整数的算法n作为输入并按正比完成时间,n则它是O(n)或theta(n),但是如果它按与in成比例的时间运行,number of digits or the number of bits in the binary representation on number则该算法以O(log n)或theta运行(登录n)时间。


请编辑。在两种情况下都有“ O(n)或theta(n)”吗?另外,我听过很多,大小与#位数字有关。我们是说size === 128表示n = 10000000,数字=== 8表示n = 10000000吗?请阐明。
科迪2013年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.