是否存在在以下类中性能大大优于实际的算法?[关闭]


39

昨晚我正在与另一位程序员讨论,即使O(1)可能是O(1),如果O(1)算法中存在一个较大的常数,则O(n)的运算也可能会胜过它。他不同意,所以我把它带到了这里。

是否有一些算法示例大大优于其下面的类?例如,O(n)比O(n)快或O(n 2)比O(n)快。

在数学上,当您忽略常数因子时,可以针对具有渐近上限的函数证明这一点,但是这样的算法在野外是否存在?在哪里可以找到它们的示例?它们用于什么类型的情况?


15
即使对于“大”算法,较小并不一定更好。例如,高斯消去是O(n ^ 3),但是有算法可以在O(n ^ 2)中做到,但是二次时间算法的系数是如此之大,以至于人们只喜欢O(n ^ 3)。 3)一。
BlackJack

11
您必须添加“ ...对于现实世界中的问题”或类似的内容,以使其成为一个明智的问题。否则,您只需要n足够大就可以补偿常数(这是big-O表示法的要点)。
starblue 2011年

8
不要以大O表示速度。
Codism 2011年

16
big-O表示法的目的不是要告诉您算法运行的速度如何,而是要告诉您扩展性如何。
BlueRaja-Danny Pflughoeft

4
我很惊讶没有人提到用于解决LP的单纯形算法。它具有指数期望的运行时间,是指数最坏的情况。实际上,这是非常快的。构造一个表现出最坏情况的运行时间的问题也很简单。另外,它也被大量使用。
ccoakley 2012年

Answers:


45

在很小的固定数据表中查找。优化的哈希表可能是O(1),但由于哈希计算的成本,它比二进制搜索甚至线性搜索要慢。


14
更准确地说,哈希表查找为O(m),其中m是密钥的大小。如果密钥大小不变,则只能调用O(1)。另外,通常将其摊销-否则表将无法增长/收缩。在找不到字符串的情况下,三叉树经常会击败哈希表进行字符串查找-三叉树搜索通常会在发现字符串的第一个或第二个字符的同时发现该键不存在。哈希表版本尚未计算哈希值。
2011年

2
我喜欢Loren Pechtel的回答和Steve314的第一条评论。我实际上已经看到了这种情况。如果您创建的Java类具有一个hashcode()方法,该方法花费很长时间才能返回哈希值(并且不能/不能缓存它),则可以在哈希类型集合中使用此类的实例(例如HashSet)会使该集合的运行速度比数组类型的集合(如ArrayList)慢。
Shivan Dragon 2011年

1
@ Steve314:为什么假设哈希函数为O(m),其中m是键的大小?即使您正在处理字符串(或其他复杂类型),哈希函数也可以是O(1)。将其放在正式定义中没有太大的价值,如果为您的输入选择了错误的数据结构(哈希表)(键大小是不可预测的),那么简单地实现哈希函数会大大改变复杂性。
Codism,2011年

1
@ Steve314:请注意,我说的是固定数据表。他们不成长。此外,如果可以优化密钥以确保没有冲突,则只能从哈希表获得O(1)性能。
罗兰·佩希特尔

1
@Loren-严格来说,如果表的大小是固定的,那么您可以花恒定的最大时间来寻找可用空间。也就是说,最多需要检查n-1个已经填充的插槽,其中n是恒定的表大小。因此,固定大小的哈希表实际上是O(1),无需进行摊销分析。这并不意味着您不必担心表填满后访问速度会变慢-只是这不是O所表示的。
Steve314,2011年

25

矩阵乘法。对于小矩阵,在实践中通常使用朴素的O(n ^ 3)算法要比Strassen的O(n ^ 2.8)更快。对于大型矩阵,使用Strassen代替O(n ^ 2.3)Coppersmith-Winograd算法。



2
从未使用Coppersmith-Winograd。实施它本身将是一项艰巨的任务,并且该常数是如此糟糕,即使对于现代科学矩阵问题也将是不可行的。
tskuzzy 2011年

24

一个简单的例子是各种排序算法之间的区别。Mergesort,Heapsort和其他一些是O(n log n)。Quicksort是O(n ^ 2)最坏的情况。但是,通常Quicksort更快,并且实际上它的平均表现像O(n log n)更多信息

另一个示例是生成单个斐波那契数。迭代算法为O(n),而基于矩阵的算法为O(log n)。不过,对于前几千个斐波那契数,迭代算法可能会更快。当然这也取决于执行!

渐近性能更好的算法可能包含昂贵的运算,而性能较差的算法却不需要这些运算,但是运算更简单。最后,O运算符仅在其操作的参数急剧增加(逼近无穷大)时才告诉我们有关性能的信息。


这是Big-O的很好解释,但未能解决问题的实质,这是针对O(n)算法比O(1)更快的特定情况。
2011年

斐波那契第一名略有下降。输出大小与输入大小成指数关系,因此它是O(lg n * e ^ n)与O(lg lg n * e ^ n)之差。
彼得·泰勒

附录:充其量。基于矩阵的算法会与1.5 ^ n的数字相乘,因此O(lg lg n * ne ^ n)可能是可证明的最佳界。
彼得·泰勒

1
无论如何,快速排序通常被描述为O(n log n)预期的性能-最坏的情况对于随机输入几乎是不可能的,并且在预通过或枢轴选择中建立一些随机性意味着,对于大量的输入大小而言,最坏的情况总体上不太可能。最坏的情况是没有相关性的事实,因为quicksort(1)非常简单,并且(2)非常易于缓存,这两者都导致常数因子比许多其他排序算法好得多。
Steve314,2011年

(2)正是在查看big-O性能时必须考虑的外部因素。从算法上讲,Mergesort应该始终胜过Quicksort,但是资源使用和缓存位置通常会颠倒其实际性能位置。
丹·里昂斯

18

注意:请阅读下面@ back2dos和其他专家的评论,因为它们实际上比我写的要有用-感谢所有贡献者。

我认为从下面的图表(摘自:Big O表示法,搜索“算法的悲观本质:”),您可以看到O(log n)并不总是比说O(n)好。因此,我想您的论点是正确的。

图1


6
这个问题想要在现实世界中使用特定的算法示例。目前没有任何东西。
梅根·沃克

19
您在该图上看不到任何内容,可以回答这个问题。这是误导。此图仅绘出了功能y = 1y = log x等和的交点y = 1y = x实际上是点(1,1)。如果这确实是正确的,那么它会告诉您,对于0到2个条目,复杂度更高的算法可能会更快,这是人们几乎不会在乎的。图表完全无法考虑的因素(以及相关的可察觉的性能差异来自何处)是恒定因素。
back2dos

@Samuel Walker,感谢您的评论。提供的链接(Link-1)中每个类别都有一些算法示例。
2011年

5
@ back2dos:单独的图形不能回答问题,但可以用来回答问题。对于任何比例和常数因子,每个显示功能的形状都相同。借助此图,可以看到给定的功能组合,其中一个输入范围较小,而另一个输入范围较小。
Jan Hudec

2
@dan_waterworth,您是对的,我承认这一点,并删除该评论。然而,答案在两个方面都是错误的或令人误解的:1)Big-O的全部意义在于它给出了复杂性的上限;这仅对较大的n有意义,因为我们显式地舍弃了较小的项,随着n的增长,最大的项已不堪重负。2)问题的重点是找到两种算法的示例,其中具有较高Big-O界限的算法优于具有较低界限的一种算法。这个答案失败了,因为它没有给出任何这样的例子。
迦勒

11

对于的实际值n,可以。这在CS理论中很有用。通常,存在一种复杂的算法,该算法在技术上具有更好的big-Oh性能,但是恒定因素如此之大,以至于使其不切实际。

我曾经让我的计算几何学教授描述一种用于在线性时间内对多边形进行三角剖分的算法,但他的结论是“非常复杂。我认为没有人真正实现它”(!!)。

另外,斐波那契堆比普通堆具有更好的特性,但由于在实践中它们不如常规堆好,因此不是很受欢迎。这可以级联到其他使用堆的算法-例如,在使用斐波那契堆时,Dijkstra的最短路径在数学上更快,但实际上通常不行。


对于大约100,000个顶点的大型图形,它的速度更快。
tskuzzy 2011年

斐波那契堆也是我的第一个(实际上是第二个)想法。
康拉德·鲁道夫

10

比较插入链表和插入可调整大小的数组。

为了使链表O(1)插入值得,数据量必须相当大。

链表对于下一个指针和取消引用有额外的开销。可调整大小的数组必须复制数据。该复制为O(n),但实际上速度非常快。


1
可调整大小的数组在每次填充时大小都会加倍,因此每次插入调整大小的平均成本为O(1)。
凯文·克莱恩

2
@kevincline,是的,但是O(n)来自必须将所有插入点向前移动之后的元素。分配以O(1)时间摊销。我的观点是,运动仍然非常快,因此在实践中通常会跳动链表。
温斯顿·埃韦特

与链接列表相比,连续数组之所以如此之快是因为处理器缓存。遍历链接列表将导致每个元素的缓存丢失。为了获得两全其美的效果,您应该使用展开的链表
dan_waterworth 2012年

可调整大小的数组并不总是复制。这取决于它正在运行什么以及是否有任何阻碍。大小加倍时相同,具体取决于实现。翻滚是个问题。链表通常最适合大小不明的队列,尽管旋转缓冲区使队列可以运转。在其他情况下,链接列表很有用,因为分配或扩展根本不会一直让您拥有连续的内容,因此无论如何都需要一个指针。
jgmjgm

@jgmjgm,如果您插入可调整大小的数组的中间,则它绝对会在此之后复制元素。
温斯顿·埃韦特

8

Big-Oh表示法用于描述函数的增长率,因此O(1)算法可能会更快,但只能达到特定点(常数因子)。

常用符号:

O(1)-迭代次数(有时可以将其称为函数所花费的用户时间)不取决于输入的大小,并且实际上是恒定的。

O(n)-迭代次数与输入大小成线性比例增长。含义-如果算法对任意输入N进行2次N次迭代,则仍视为O(n)。

O(n ^ 2)(二次)-迭代次数是输入大小的平方。


2
举例来说,O(1)方法每次调用可能花费37年,而O(n)方法每次调用可能花费16 * n微秒。哪个更快?
卡兹龙

16
我完全看不出这如何回答问题。
2011年

7
我了解big-O。这没有解决实际的问题,这是函数的特定示例,其中big-O较低的算法要比big-O较高的算法要好。
2011年

当您以“是否有示例...”的形式提出问题时,不可避免地会有人回答“是”。不给任何。
rakslice 2011年

1
@rakslice:也许是。但是,此站点要求您对任何声明进行解释(或更好地证明)。现在证明这种例子的最好方法是举一个;)
back2dos

6

正则表达式库通常用于执行回溯,而回溯具有最坏情况下的指数时间,而不是DFA生成,回溯复杂度为O(nm)

当输入停留在快速路径上或在不需要过多回溯的情况下失败时,幼稚的回溯性能可能更好。

(尽管此决定不仅基于性能,还允许反向引用。)


我认为这也是部分历史原因-在开发某些较早的工具(我猜是sed和grep)时,将正则表达式转换为DFA的算法已申请了专利。当然,我不确定我的编译器教授的说法,因此这是第三手资料。
迪洪·耶尔维斯

5

一种O(1)算法:

def constant_time_algorithm
  one_million = 1000 * 1000
  sleep(one_million) # seconds
end

一种O(n)算法:

def linear_time_algorithm(n)
  sleep(n) # seconds
end

显然,对于任何值n,其中n < one_million,所述O(n)实例中所给出的算法将是更快O(1)算法。

尽管此示例有些麻烦,但其实质与以下示例等效:

def constant_time_algorithm
  do_a_truckload_of_work_that_takes_forever_and_a_day
end

def linear_time_algorithm(n)
  i = 0
  while i < n
    i += 1
    do_a_minute_amount_of_work_that_takes_nanoseconds
  end
end

必须知道你的常数和系数O表达,你必须知道的预期范围n,以确定先验哪种算法最终会被更快。

否则,您必须使用n期望范围内的值对这两种算法进行基准测试,以确定后验哪种算法最终会更快。


4

排序:

插入排序为O(n ^ 2),但对少数元素的性能优于其他O(n * log(n))排序算法。

这就是为什么大多数排序实现都使用两种算法的组合的原因。例如,使用合并排序将大数组分解,直到它们达到一定大小为止,然后使用插入排序对较小的单元进行排序,然后再通过合并排序将其合并。

有关使用此技术的Python和Java 7排序的当前默认实现,请参见Timsort



3

当程序被交换到磁盘上时,或进行比较时需要从磁盘读取每个项目时,内存中的Bubblesort的性能可能会优于quicksort。

这应该是他可以举例说明的一个例子。


Quicksort和Bubbles上引用的复杂性难道不假定O(1)随机内存访问吗?如果情况不再如此,是否不需要重新检查quicksorts的复杂性?
维克多·达尔

@ViktorDahl,项目访问时间不是传统上以排序算法复杂度衡量的内容的一部分,因此“ O(1)”在这里不是正确的单词选择。改为使用“恒定时间”。PHK不久前写了一篇有关排序算法的文章,因为知道某些项目比其他项目(虚拟内存)要昂贵得多(queue.acm.org/detail.cfm?id=1814327),您可能会发现它很有趣。

我现在看到了我的错误。通常会比较一次,比较次数当然不受存储介质速度的影响。另外,感谢您的链接。
维克多·达尔

3

通常,更高级的算法假定一定数量的(昂贵)设置。如果只需要运行一次,则使用蛮力方法可能会更好。

例如:二进制搜索和哈希表查找每次查找都比线性查找快得多,但是它们分别要求您对列表进行排序或构建哈希表。

排序将花费您N log(N),而哈希表将至少花费N。现在,如果您要进行数百或数千次查找,那仍然是摊销的节省。但是,如果您只需要进行一两次查找,则只进行线性搜索并节省启动成本就有意义。


1

解密通常为0(1)。例如,DES的密钥空间为2 ^ 56,因此解密任何消息都是恒定时间操作。只是您的因子为2 ^ 56,所以它的常数很大。


解密不是O(n)的消息,其中n与消息的大小成正比吗?只要您拥有正确的密钥,密钥的大小甚至都不会被考虑;因此,如果您使用的是正确的密钥,则不会更改密钥的大小。有些算法的密钥设置/扩展过程很少或没有(DES,RSA-注意,密钥生成可能仍然是一个复杂的任务,但与密钥扩展无关),而另一些算法则极其复杂(想到了Blowfish),但是完成后,实际工作的时间与消息的大小成正比,因此为O(n)。
的CVn

您可能是指密码分析而不是解密?
左右左转

3
好吧,是的,可以采取多种措施来保持常数并将算法声明为O(1)。[隐式排序是假设元素要花费固定的时间进行比较,例如,或者使用具有非大数的任何数学运算进行比较]
Random832,2011年

1

我想到了set的不同实现。最幼稚的方法之一是在向量上实现它,这意味着removecontains因此add都取O(N)。
一种替代方法是在一些通用哈希上实现它,该哈希将输入哈希映射到输入值。这样的一组实施执行与O(1) addcontainsremove

如果我们假设N大约为10,那么第一个实现可能会更快。查找元素所需要做的就是将10个值与1个值进行比较。
另一个实现将必须启动各种巧妙的转换,这比进行10次比较可能要昂贵得多。在所有开销的情况下,您甚至可能会遇到高速缓存未命中的问题,那么从理论上讲解决方案的速度实际上并不重要。

这并不意味着,如果N足够小,您能想到的最差的实现将胜过体面的实现。这仅意味着对于足够小的N,与将可扩展性放在首位的实现相比,具有低占用空间和开销的天真的实现实际上可以需要更少的指令并导致更少的缓存丢失。

您无法真正知道现实世界中的事物有多快,直到您将其整合并进行简单测量。通常结果令人惊讶(至少对我而言)。


1

是的,对于适当小的N。将始终存在N,在该N之上,您将始终具有以下顺序:O(1)<O(lg N)<O(N)<O(N log N)<O(N ^ c )<O(c ^ N)(其中O(1)<O(lg N)表示在O(1)上,当N适当大且c是大于1的某个固定常数时,算法将执行较少的运算)。

假设特定的O(1)算法正好执行f(N)= 10 ^ 100(googol)个运算,而O(N)算法正好执行g(N)= 2 N + 5个运算。O(N)算法将提供更好的性能,直到您将N近似为googol(实际上是当N>(10 ^ 100-5)/ 2时),因此,如果您仅期望N在1000到十亿之间,使用O(1)算法将遭受重大损失。

或进行实际比较,例如您要将n位数字相乘。所述karatsuba算法是至多3 N ^(LG 3)的操作(即大致为O(n ^ 1.585)),而Schönhage-Strassen的算法是O(N为log N log日志N)这是一种速度更快次序,但是要报价维基百科:

实际上,对于超过2 ^ 2 ^ 15到2 ^ 2 ^ 17(10,000到40,000个十进制数字)的数字,Schönhage–Strassen算法开始优于旧方法(例如Karatsuba和Toom-Cook乘法)。[4] [5] [6 ]

因此,如果将500位数字相乘,则使用大O参数“更快”的算法没有意义。

编辑:通过取极限N-> f(N)/ g(N)的无穷大,可以找到确定g(N)与f(N)的关系。如果极限为0,则f(N)<g(N);如果极限为无穷大,则f(N)> g(N);如果极限为其他常数,则f(N)〜g(N)在大O表示法方面。


1

在最坏的情况下,用于线性规划的单纯形法可能是指数式的,而相对较新的内部点算法可能是多项式的。

但是,在实践中,单纯形法的指数最坏情况不会出现-单纯形法是快速而可靠的,而早期的内部点算法太慢而无法竞争。(现在有更多现代的内部点算法具有竞争力-但单纯形方法也是如此...)


0

Ukkonen建立后缀尝试的算法为O(n log n)。它具有“在线”的优点-也就是说,您可以增量地追加更多文本。

最近,其他更复杂的算法声称在实践中速度更快,这在很大程度上是因为它们的内存访问具有更高的局部性,从而提高了处理器缓存的利用率并避免了CPU管道停顿。参见,例如,该调查,它声称花费70-80%的处理时间来等待内存,并且本文描述了“ wotd”算法。

后缀尝试在遗传学中(对于匹配基因序列)很重要,而在拼字词典的实现中则不太重要。


0

对于任何明确定义的问题,总会有最快和最短的算法。不过,从理论上讲,它只是(渐近地)最快的算法。

鉴于问题的任何说明P和实例对这个问题,它列举所有可能的算法,一个和证明,检查每对是否是一个有效的证明,一个是渐近最快的算法P。如果找到这样的证据,则它执行一个

搜索该问题对对的复杂度为O(1)(对于固定的问题P),因此对于该问题,您始终使用渐近最快的算法。但是,由于该常数几乎在所有情况下都非常巨大,因此该方法在实践中完全没有用。


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.