O(log N)== O(1) - 为什么不呢?

时间:2009-09-29 10:40:01

标签: performance algorithm computer-science big-o

每当我考虑算法/数据结构时,我倾向于用常数替换log(N)部分。哦,我知道log(N)有所不同 - 但它在实际应用中是否重要?

  

log(无穷大)< 100用于所有实际目的。

我真的很好奇现实世界的例子,但这并不成立。

澄清:

  • 我理解O(f(N))
  • 我很好奇现实世界的例子,渐近行为比实际表现的常数更重要。
  • 如果log(N)可以被常量替换,它仍然可以被O(N log N)中的常量替换。

这个问题是为了(a)娱乐和(b)收集使用的论据,如果我(再次)再次讨论关于设计性能的争议。

23 个答案:

答案 0 :(得分:60)

Big O表示法会告诉您算法如何随着输入的增加而变化。 O(1)告诉你输入增长无关紧要,算法总是同样快。 O(logn)说算法会很快,但随着你的输入增长,它会花费更长的时间。

当你开始组合算法时,

O(1)和O(logn)会产生很大的差异。

以索引为例进行连接。如果你可以在O(1)而不是O(logn)中进行连接,那么你将获得巨大的性能提升。例如,使用O(1),您可以加入任何次数,但仍然有O(1)。但是使用O(logn),您需要每次将操作计数乘以logn。

对于大型输入,如果你的算法已经是O(n ^ 2),你宁可做一个内部为O(1)的操作,而不是内部的O(logn)。

还要记住,任何东西的Big-O都可以有不变的开销。假设持续的开销是100万。使用O(1),恒定开销不会像O(logn)那样放大操作次数。

另一点是,每个人都想到O(logn)代表树数据结构的n个元素。但它可能包括文件中的字节。

答案 1 :(得分:22)

我认为这是一种务实的做法; O(logN)永远不会超过64.实际上,每当条件变得像O(logN)一样“小”时,你必须测量以查看常数因子是否胜出。另见

Uses of Ackermann function?

引用自己对另一个答案的评论:

  

[Big-Oh]'分析'只对因素有影响   至少是O(N)。任何   较小的因素,大哦分析   没用,你必须测量。

  

“使用O(logN)输入大小   “这就是问题的全部内容   这个问题。当然重要......   理论上。 OP问的问题   是的,在实践中是否重要?一世   认为答案是否定的   不是,也绝不会是数据集   logN将以如此快的速度增长   永远被打败   算法。即使是最大的   可以想象的实用数据集   我们孙子的一生,一个logN   算法有很大的跳动机会   一个恒定的时间算法 - 你必须   总是衡量。

修改

好话:

http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

大约一半时间,Rich讨论了Clojure的哈希尝试,它显然是O(logN),但是对数的基数很大,所以即使它包含40亿个值,trie的深度也最多为6。这里“6”仍然是一个O(logN)值,但它是一个非常小的值,因此选择丢弃这个令人敬畏的数据结构因为“我真的需要O(1)”是一件愚蠢的事情。这强调了这个问题的大多数其他答案是如何从实用主义者的角度来看只是错误,他们希望他们的算法“快速运行”和“很好地扩展”,而不管“理论”说什么

修改

另见

http://queue.acm.org/detail.cfm?id=1814327

  

O(log2(n))算法有什么用处   如果这些操作导致页面错误   和慢速磁盘操作?对于大多数   相关数据集一个O(n)甚至一个   O(n ^ 2)算法,避免页面   错误,将围绕它运行。

(但请阅读上下文的文章)。

答案 2 :(得分:20)

这是一个常见的错误 - 记住Big O符号并没有告诉你算法在给定值下的绝对性能,它只是在你增加输入的大小时告诉你算法的行为。

当你在那个环境中使用它时,很明显为什么算法A~O(logN)和算法B~O(1)算法是不同的:

如果我在大小为a的输入上运行A,那么在大小为1000000 * a的输入上,我可以期望第二个输入在第一次输入时记录(1,000,000)次

如果我在大小为a的输入上运行B,那么在大小为1000000 * a的输入上,我可以预期第二个输入与第一个输入的时间大致相同

编辑:再考虑一下你的问题,我确实认为它有一些智慧。虽然我永远不会说O(lgN)== O(1)是正确的,但是 IS 可能会在O(1)算法上使用O(lgN)算法。这回顾了上面关于绝对性能的观点:只知道一个算法是O(1)而另一个算法是O(lgN) NOT 足以声明你应该使用O(1)而不是O(lgN),肯定有可能给出你可能的输入范围,O(lgN)可能最适合你。

答案 3 :(得分:7)

你问了一个真实世界的例子。我会给你一个。计算生物学。用ASCII编码的一条DNA链在空间的千兆字节上。一个典型的数据库显然会有成千上万的这样的链。

现在,在索引/搜索算法的情况下,当与常量结合时,log(n)倍数会产生很大的差异。之所以?这是您的输入大小是天文数字的应用程序之一。此外,输入大小将始终继续增长。

不可否认,这类问题很少见。这么大的应用程序只有很多。在那些情况下,虽然......它创造了一个与众不同的世界。

答案 4 :(得分:5)

平等,你所描述的方式,是对符号的常见滥用。

澄清一下:我们通常写f(x)= O(logN)暗示“f(x)是O(logN)”。

无论如何,O(1)表示执行动作的常数步数/时间(作为上限),无论输入集有多大。但是对于O(logN),步数/时间的数量仍然随着输入大小(它的对数)的变化而增长,它只是增长得非常慢。对于大多数现实世界的应用程序,您可以安全地假设这个步骤数不会超过100,但我敢打赌,有多个数据集大小足以标记您的语句既危险又无效(数据包跟踪,环境测量和更多)。

答案 5 :(得分:5)

对于足够小的N,O(N ^ N)实际上可以替换为1.不是O(1)(根据定义),但是对于N = 2,您可以将其视为具有4个部分的一个操作,或者恒定时间操作。

如果所有操作需要1小时怎么办? O(log N)和O(1)之间的差异很大,即使是小N.

或者,如果您需要运行该算法一千万次?好的,花了30分钟,所以当我在数据集上运行它一百倍时,它仍然需要30分钟,因为O(logN)与O(1)“相同”......呃......什么?< / p>

你的陈述“我理解O(f(N))”显然是错误的。

真实世界的应用程序,哦......我不知道......每次使用O() - 符号EVER?

例如,在1000万个项目的排序列表中进行二进制搜索。这是我们在数据变得足够大时使用哈希表的原因。如果你认为O(logN)与O(1)相同,那你为什么要使用哈希而不是二叉树?

答案 6 :(得分:5)

正如许多人已经说过的,对于现实世界,你需要先考虑常数因素,然后再担心O(log N)的因素。

然后,考虑一下你期望N是什么。如果您有充分的理由认为N <10,则可以使用线性搜索而不是二进制搜索。这是O(N)而不是O(log N),根据你的灯 是显着的 - 但是将找到的元素移到前面的线性搜索可能会胜过更复杂的平衡树, 取决于应用程序

另一方面,请注意,即使log N不可能超过50,性能系数10也非常大 - 如果你受计算限制,那么这样的因素很容易成就或破坏你的应用。如果这对你来说还不够,你会经常在算法中看到(log N)^ 2或(logN)^ 3的因子,所以即使你认为你可以忽略一个因子(log N),这并不意味着你可以忽略更多。

最后,请注意线性编程的单纯形算法具有O(2 ^ n)的最差情况。但是,对于实际问题,最坏的情况永远不会出现;在实践中,单纯形算法快速,相对简单,因此非常流行。

大约30年前,有人开发了一种用于线性规划的多项式时间算法,但它最初并不实用,因为结果太慢

如今,有线性编程的实用替代算法(具有多项式时间的wost-case,值得),在实践中可以胜过单纯形法。但是,根据问题,单纯形法仍具有竞争力。

答案 7 :(得分:4)

O(log n)通常与O(1)无法区分的观察结果很好。

作为一个熟悉的例子,假设我们想要在一个1,000,000,000,000个元素的排序数组中找到一个元素:

  • 使用线性搜索,搜索平均需要500,000,000,000步
  • 使用二分搜索,搜索平均需要40步

假设我们在我们正在搜索的数组中添加了一个元素,现在我们必须搜索另一个元素:

  • 使用线性搜索,搜索平均需要500,000,000,001步(无法区分的变化)
  • 使用二分搜索,搜索平均需要40步(无法区分的变化)

假设我们正在搜索的数组中的元素数量增加了一倍,现在我们必须搜索另一个元素:

  • 使用线性搜索,搜索平均需要1,000,000,000,000步(非常明显的变化)
  • 使用二分搜索,搜索平均需要41步(无法区分的变化)

正如我们从这个例子中可以看到的那样,对于所有意图和目的,像二元搜索这样的O(log n)算法通常无法与全知的O(1)算法区分开来。

接下来的要点是:*我们使用O(log n)算法,因为它们通常与常数时间无法区分,并且因为它们通常比线性时间算法表现得更好。

显然,这些例子假设合理的常数。显然,这些是一般性观察,并不适用于所有情况。显然,这些点适用于曲线的渐近结束,而不是n=3结束。

但是这个观察解释了为什么,例如,我们使用调整查询来执行索引搜索而不是表扫描的技术 - 因为无论数据集的大小如何,索引搜索都在几乎恒定的时间内运行,而对于足够大的数据集,表扫描速度非常慢。索引搜索是O(log n)

答案 8 :(得分:3)

您可能对Soft-O感兴趣,它忽略了对数成本。检查维基百科中的this paragraph

答案 9 :(得分:2)

你是否“重要”是什么意思?

如果您面临O(1)算法和O(lg n)算法的选择,那么您不应该认为它们是平等的。你应该选择恒定时间。你为什么不呢?

如果不存在常数时间算法,那么对数时间算法通常是你能得到的最好的算法。那么,它是否重要?你必须采取你能找到的最快速度。

你能否通过将两者定义为平等来给我一个获得任何收益的情况?充其量,它没有任何区别,最糟糕的是,你隐藏了一些真正的可扩展性特征。因为通常,恒定时间算法比对数算法快。

即使如你所说,lg(n) < 100用于所有实际目的,除了你的其他开销之外,这仍然是一个因素100。如果我调用你的函数N次,那么你的函数是以对数时间还是常数运行就会变得很重要,因为总复杂度是O(n lg n)O(n)

因此,不要问“在现实世界中”假设对数复杂度是否“不重要”,我会问这样做是否有任何意义。

通常你可以假设对数算法足够快,但是通过考虑它们不变会得到什么?

答案 10 :(得分:2)

O(logN)* O(logN)* O(logN)非常不同。 O(1)* O(1)* O(1)仍然是常数。 简单的快速排序式O(nlogn)也不同于O(n O(1))= O(n)。尝试排序1000和1000000个元素。后者不是慢1000倍,它是2000倍,因为log(n ^ 2)= 2log(n)

答案 11 :(得分:2)

问题的标题是误导性的(选择好鼓励辩论,请注意)。

O(log N)== O(1)显然是错误的(海报知道这一点)。根据定义,大O符号表示渐近分析。当你看到O(N)时,N被逼近无穷大。如果N被赋予常数,则不是大O.

请注意,这不仅仅是理论计算机科学家需要关注的细节。用于确定算法的O函数的所有算法都依赖于它。当您为算法发布O函数时,可能会省略 lot 有关其性能的信息。

Big O分析很酷,因为它可以让您比较算法而不会陷入平台特定问题(字大小,每个操作的指令,内存速度与磁盘速度)。当N进入无穷大时,这些问题就会消失。但是当N为10000,1000,100时,这些问题以及我们从O函数中遗漏的所有其他常量开始变得重要。

回答海报的问题:O(log N)!= O(1),你是对的,有O(1)的算法有时不比O(log N)的算法好,具体取决于关于输入的大小,以及在Big O分析期间省略的所有内部常量。

如果您知道自己要开N,那么请使用Big O分析。如果你不是,那么你需要一些实证测试。

答案 12 :(得分:2)

理论上

是的,在实际情况下,log(n)受常量限制,我们会说100.但是,在正确的情况下将log(n)替换为100仍然会丢弃信息,使得操作的上限你计算得更宽松,更少有用。在您的分析中用O(1)替换O(log(n))可能会导致您的大n个案例比您的小n案例差100倍。您的理论分析可能更准确,并且可能在您构建系统之前预测到了一个问题。

我认为big-O分析的实际目的是尽可能早地尝试预测算法的执行时间。你可以通过划掉log(n)项来简化分析,但是你已经降低了估算的预测能力。

在实践中

如果您阅读Larry Page和Sergey Brin关于Google架构的原始论文,他们会讨论如何使用哈希表来确保例如查找缓存的网页只需要一次硬盘搜索。如果您使用B树索引进行查找,则可能需要四到五个硬盘搜索来执行未缓存的查找[*]。从业务角度来看,在缓存的网页存储上将磁盘需求翻两番是值得关注的,并且如果不推出所有O(log(n))术语,则可以预测。

P.S。很抱歉以谷歌为例,他们就像计算机科学版Godwin's law中的希特勒一样。

[*]假设从磁盘读取4KB,索引中包含1000亿个网页,在B树节点中每个键约16个字节。

答案 13 :(得分:1)

Big-OH​​告诉你,在给定一些常数因子的情况下,一种算法比另一种算法更快。如果你的输入意味着一个足够小的常数因子,你可以通过线性搜索而不是对某个基数的log(n)搜索来看到很好的性能提升。

答案 14 :(得分:1)

你是对的,在很多情况下,它与实际目的无关。但关键问题是“GROWS N有多快”。我们知道的大多数算法都取决于输入的大小,因此它会线性增长。

但是有些算法具有以复杂方式导出的N值。如果N是“具有X个不同数字的抽奖的可能抽奖组合的数量”,那么如果您的算法是O(1)或O(logN)

突然重要

答案 15 :(得分:1)

对于任何可以接受不同大小N的输入的算法,它所采取的操作数量由某个函数f(N)限制。

所有大O都告诉你这个功能的形状。

  • O(1)表示存在一些数字A,使得f(N)<1。 A为大N。

  • O(N)表示存在一些A,使得f(N)<1。 AN为大N。

  • O(N ^ 2)表示存在一些A,使得f(N)<1。 AN ^ 2表示大N。

  • O(log(N))表示存在一些A,使得f(N)<1。 AlogN for large N。

Big-O没有说明A的大小(即算法有多快),或者这些函数彼此交叉的地方。它只是说当你比较两个算法时,如果它们的big-Os不同,则有一个N值(可能很小或者可能非常大),其中一个算法将开始优于另一个算法。

答案 16 :(得分:1)

假设在整个应用程序中,一个算法占用户等待最常见操作的90%。

假设O(1)操作在您的体系结构上实时占用一秒,而O(logN)操作基本上是.5秒* log(N)。好吧,在这一点上,我真的想在曲线和线的交叉点画一个带箭头的图形,说:“这里很重要。”在这种情况下,您希望对小型数据集使用log(N)op,对大型数据集使用O(1)op。

Big-O表示法和性能优化是一项学术练习,而不是为已经便宜的操作为用户提供真正的价值,但如果在关键路径上进行昂贵的操作,那么你认为这很重要!

答案 17 :(得分:1)

当您不确定O(log n)= O(1)时,确定Big-O表示法的规则更简单。

正如krzysio所说,你可以积累O(log n)s然后它们会产生非常显着的差异。想象一下,你做了二进制搜索:O(log n)比较,然后想象每个比较的复杂度O(log n)。如果忽略两者,则得到O(1)而不是O(log 2 n)。同样地,你可能会以某种方式到达O(log 10 n),然后你会发现不太大的“n”会有很大的不同。

答案 18 :(得分:1)

正如其他人所指出的,Big-O告诉你问题的表现如何扩展。相信我 - 这很重要。我遇到过几次算法很糟糕,无法满足客户的要求,因为它们太慢了。了解差异并找到O(1)解决方案很多次都是一个巨大的改进。

然而,当然,这不是全部 - 例如,您可能会注意到快速排序算法将始终切换到小元素的插入排序(维基百科说8 - 20),因为两种算法在小数据集上的行为

所以这是一个理解你将要做什么权衡的问题,这涉及到彻底了解问题,架构和&amp;经验,了解使用哪些,以及如何调整所涉及的常数。

没有人说O(1)总是比O(log N)好。但是,我可以保证O(1)算法也可以更好地扩展,所以即使您对系统中有多少用户或要处理的数据大小做出错误的假设,也无关紧要算法。

答案 19 :(得分:1)

是,log(N)&lt; 100用于大多数实际用途,不,你不能总是用常数替换它。

例如,这可能会导致估算程序性能时出现严重错误。如果O(N)程序在1 ms内处理了1000个元素的数组,那么您确定它将在1秒内(或左右)处理10个 6 元素。但是,如果程序是O(N * logN),那么处理10个 6 元素需要大约2秒。这种差异可能至关重要 - 例如,您可能认为自己拥有足够的服务器功率,因为​​每小时可获得3000个请求,并且您认为您的服务器最多可以处理3600个。

另一个例子。想象一下,你有函数f()在O(logN)中工作,并在每次迭代调用函数g(),它也在O(logN)中工作。然后,如果您用常量替换两个日志,您认为您的程序在恒定时间内工作。现实将是残酷的 - 两个日志可能会给你最多100 * 100乘数。

答案 20 :(得分:0)

O(log N)可能会产生误导。以Red-Black trees为例进行操作 操作是O(logN)但相当复杂,这意味着许多低级操作。

答案 21 :(得分:-1)

我不相信算法可以自由在具有大常数的O(1)和O(logN)之间进行选择。如果在开始时有N个元素可以使用,那么使它成为O(1)是完全不可能的,唯一可能的是将N移动到代码的其他部分。

我想说的是,在所有真实情况下,我都知道你有一些空间/时间权衡,或者一些预处理,例如将数据编译成更有效的形式。

也就是说,你真的没有去O(1),你只需将N部分移到别处。您要么将某些部分代码的性能与某些内存量进行交换,要么将算法的一部分与另一部分的性能进行交换。为了保持理智,你应该始终关注更大的图片。

我的观点是,如果你有N个项目,它们就无法消失。换句话说,你可以选择低效的O(n ^ 2)算法或更差的O和(n.logN):这是一个真正的选择。但你永远不会去O(1)。

我试图指出的是,对于每个问题和初始数据状态,都有一个“最佳”算法。你可以做得更糟,但永远不会更好。凭借一些经验,您可以很好地猜测这种内在复杂性是什么。然后,如果你的整体治疗方法符合这种复杂性,你就知道你有什你将无法降低这种复杂性,只能移动它。

如果问题是O(n)它不会变成O(logN)或O(1),你只需要添加一些预处理,使整体复杂性保持不变或更差,并可能在后面的步骤中得到改善。假设您想要数组的较小元素,您可以使用任何常见的O(NLogN)排序处理在O(N)中搜索或排序数组,然后使用O(1)进行排序。

随便这样做是个好主意吗?只有当您的问题也要求第二,第三等元素时。那么你最初的问题是真正的O(NLogN),而不是O(N)。

如果你等待十倍或二十倍的结果,那就不一样了,因为你简化了说O(1)= O(LogN)。

我正在等待一个例子;-)这是任何真实的情况,你可以在O(1)和O(LogN)之间做出选择,并且每个O(LogN)步骤都不会与O进行比较( 1)。您所能做的就是采用更糟糕的算法而不是自然算法,或者将一些重度处理移动到较大图片的其他部分(预先计算结果,使用存储空间等)。

答案 22 :(得分:-1)

假设您使用的是运行在O(log N)中的图像处理算法,其中N是图像的数量。现在......声明它在恒定的时间内运行会使人相信无论有多少图像,它仍然会在相同的时间内完成它的任务。如果在单个图像上运行算法假设需要一整天,并且假设O(logN)永远不会超过100 ...想象那个尝试在非常大的图像数据库上运行算法的人会感到惊讶 - 他希望它能在一天左右的时间内完成......但它需要数月才能完成。