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

pho*_*oku 47 algorithm performance big-o computer-science

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

所有实际用途的log(无穷大)<100.

我真的很好奇现实世界的例子,这是不成立的.

澄清:

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

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

Bri*_*ndy 61

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个元素.但它可能包括文件中的字节.

  • 测量仅告诉您使用*this*size输入运行算法的速度有多快.如果输入大小加倍,它不会告诉你它的执行速度有多快.big-O表示法.你不能用另一个替换一个.我认为Brian R. Bondy明白了这一点. (26认同)
  • 不,你宁愿在循环中做O(1)而不是O(logN).你宁愿做任何一个实际上更快,这需要测量.这是OP的重点.你完全忽略了这一点. (13认同)
  • @Brian:我觉得你认为O(log n)对于实际输入大小来说可以忽略不计是完全奇怪的.二进制搜索是O(log n).变量用法是O(1).如果你需要多次一些值,你会每次都使用二进制搜索,还是将它粘贴在变量中?**在回答之前你需要测量吗?**...如果N变得足够大,O(1)最终会胜出.说你的输入"永远不会"变得足够大,这对于任何人都说*640k应该足够了!* (5认同)
  • 测量仅适用于您事先知道的恒定输入. (3认同)
  • 我并不想建议您需要资格认证(例如'大投入'),我试图建议你做错了.:)在实践中,采用logN步骤的算法总是优于采用100步的算法,无论输入大小如何(在极其合理的假设下,输入大小永远不会大于2 ^ 64个元素). (2认同)
  • 我没有声称"无论输入大小如何",你都自己补充说.Big O表示法告诉您输入增长时需要多少操作.使用O(1)您的输入大小无关紧要.使用O(logn),您的输入大小确实很重要.是的我完全同意您可以使用O(1)算法,该算法优于O(logn)算法,适用于与您的确切问题相关的所有输入.但是有一个很重要的区别. (2认同)
  • @Brian:我们明白了,你明白这一点"对于某些输入,你可以找到一个O(1)算法,它的性能不如O(logn)算法".在实践中,当常数因子开销相同时,我将继续在O(logn)算法上选择O(1)算法,并且您可以选择O(logn)算法. (2认同)

Bri*_*ian 23

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

使用阿克曼函数?

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

[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)算法可避免页面错误,它将围绕它运行圆圈.

(但请阅读上下文中的文章).

  • 我道歉,如果我不清楚,我对孙子们的意思是,今天你可能使用的最大数据集可能大约10 ^ 9,我可以想象50年后它可能是10 ^ 20,或者其他什么,但即便如此,我的断言仍然存在.即使是非常庞大的数字,logN仍然足够小,您无法根据复杂性理论在logN和1之间做出实际决策. (2认同)

Fal*_*ina 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(LGN)算法可能会超过一个O(1)算法中使用.这种退约的绝对性能点之上:只要知道一个算法是O(1),另一种算法是O(LGN)是足够的声明,你应该(1)在O(LGN)使用O,它肯定可能给出您可能的输入范围,O(lgN)可能最适合您.


San*_*nto 7

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

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

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

  • 我不确定它有什么不同.如果您构造了一个具有低OR高常数的算法,则此log(n)乘数会产生很大的差异.我不明白为什么100是神奇的数字.如果需要10分钟才能完成算法最里面部分的一次通过,为什么16*10分钟看起来像4*10分钟一样无害?还需要2个小时才能运行! (8认同)

Mic*_*kis 5

平等,你描述的方式,是一种常见的符号滥用.

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

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

  • 对不起,这是一个非常错误的陈述.Big O非常用于实际目的,它是衡量2种不同算法的可扩展性的一种非常重要的方法.但我确实同意,OP是一种非常常见的滥用行为. (4认同)
  • 你怎么认为大O符号不是用于实际目的?我已经直接使用了几次,间接多次作为指导,我看到其他人犯了愚蠢的错误,因为他们不理解. (2认同)

Tho*_*mas 5

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

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

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

你的陈述"我理解O(f(N))"显然是错误的.

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

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


com*_*orm 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年前,有人开发了一种用于线性规划的多项式时间算法,但最初并不实用,因为结果太慢了.

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


yfe*_*lum 5

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).