有没有哪种情况下,您更喜欢O(log n)时间复杂度而不是O(1)时间复杂度?还是从O(n)到O(log n)?
你有什么例子吗?
- 如果理解前者,我更喜欢O(log n)算法而不是O(1)算法,但不喜欢后者……
- 从理论上讲,计算机科学中有大量不实用的O(1)运算数据结构。一个例子是位向量上的select(),它可以在o(n)额外空间和o(1)每个操作中支持,使用5层间接寻址。根据简洁的数据结构库作者的观点,简单的二进制搜索与o(1)rank()结合在一起在实践中变得更快。
- 较低的渐进复杂性不能保证更快的运行时间。研究矩阵乘法的具体例子。
- 另外……任何算法都可以转换为O(1),只要有足够大的表查找;)
- 具体来说,smoothsort具有最佳情况O(n)性能和最差情况O(n log n)性能,而quicksort具有最佳情况O(n log n)性能和最差情况O(n ^ 2)性能。你在实践中看到了什么?
- @Hoten——假设表查找是O(1),这对于您所说的表的大小来说根本不是给定的!:)
- @因为它将独立于实际输入,所以它将是O(1)。它将取决于输入空间的大小(即函数的域)。
- 我将采用一种算法,我可以随时从语言库调用任何自实现的或第三方的算法,除非它在我的应用程序中是一个实际的性能问题。更快的书写、更容易记录、更容易维护、更好的测试。
- O(1)和O(log n)之间的差异只对理论计算机科学家感兴趣。在现实世界中工作的人有更好的事情需要担心,比如O(n)和O(n^2)之间的区别。
- @hoten如果输入空间是有界的,那么每个算法都是$O(1)$。复杂性只有在输入空间不受限制时才有趣,在这种情况下,查找表肯定不是$O(1)$。例如,固定字大小体系结构上的任何查阅表格都是$omega(log(n))$,这是清晰的,因为任何最大扇出值等于字大小的分支程序在输出数中都必须是对数。
- @霍滕。假设我们有任何一种图算法,它依赖于所有节点来决定解决方案。在查找表中查找结果的唯一方法是至少访问输入中的每个节点一次(否则,您如何知道要选择哪个解决方案?)因此,您的复杂性至少为O(V)。显然,如果输入的大小是有界的,那么o(v)是一个常数,它在o(1)中,但是对于每个算法来说,同样的事情是正确的,并且您不需要任何类型的查找表,
- @VOO绝对正确。进一步考虑,我所说的仅适用于某些算法,其中输入是标量值。
- @但是整数的大小增加了输入大小的对数;)
- @VOO Big-O分析通常忽略实现细节(缓存、内存等);
- @Hoten与实现细节无关。如果您的输入是单个数字,如果该数字可以任意大,则需要表示O(log n)位,这需要读取O(log n)时间。很多分析都忽略了这一点,这是有原因的,但是如果你不小心忽略了这一点,你很容易证明你有一个NP硬算法的多项式解。例如,参见伪多项式时间。
- 或者,如果另一个示例更简单:我们前面提到的图形问题的输入可以很容易地表示为一个数字(毕竟这就是计算机内存的基本情况),因此它不能与以前的参数一样位于o(1)中。
- 这个问题有点太宽泛了(从非常模糊的答案可以看出,比如:速度、空间、算法长度等等)。也许你有一些更具体的应用领域?
- @VOO您必须有一个表,其中查找的键包括图形本身。它必须枚举所有可能的图,因为图是输入的一部分。唯一的问题是,如果可以使用的图是无限集。实际上,在计算机程序设计中,我们可以断言域在技术上是有限的,因为计算机是有限的。所以霍顿仍然是对的,至少从某个角度来看是这样。
- @JPMC26只要您假设输入域是有限的,不管您是否使用查找表——在这种情况下,所有算法的定义都是O(1)!Quicksort、Knappsack、旅行推销员—所有O(1)和不涉及任何查找表的O(1)。如果您不假设域是有限的,那么查找表也不会将大多数问题归结为O(1)(不过,您可以将它们归结为O(n))。
- @hoten关于表查找的语句没有意义,因为我们使用big-o表示法来显示渐进的运行时间增长,而不是运行时间本身。也就是说,你的表达必须对任意大的n有效。
- 有一个可爱的数据结构叫做bloom过滤器,它可以在o(1)时间内添加并概率地检查元素的存在,但是当元素实际上不存在时,可能会错误地响应"present"。树会以o(log n)给出确定的答案。
- 我可能更喜欢O(log n)-time / O(1)-space算法而不是O(1)-time / O(10^(n^999))-space算法:—)
- 这个著名的问题是高度相关的,它演示了这样一种情况:分支预测效果导致日志n运行时间(对数组进行排序)比n(在数组上循环)更快,即使对于较大的n。
- 在排序网络中,AKS网络是理论工作者的最佳构造深度O(log n),而对于批处理排序和位排序,AKS网络是O((log n)**2),但常量使其完全不可行。Knuth:"除非n超过地球上所有计算机的总内存容量,否则batcher的方法要好得多!
- 数学上的问题是,一个对数(x)是否可以小于一个常数,或者x是否可以小于一个对数(x)或一个常数。看看这个图,你告诉我fooplot.com/plot/m3dwiddyny
- 有时,较慢的算法可能具有其他重要特性。例如,quicksort通常比mergesort快,但mergesort本质上是稳定的,而quicksort不稳定;因此,如果具有相同键的条目顺序,则mergesort可能更可取。
与较低的时间复杂度相比,选择大o时间复杂度较高的算法有很多原因:
- 大多数情况下,较低的大O复杂性更难实现,需要熟练的实现、大量的知识和大量的测试。
- big-o隐藏了常数的细节:从big-o的角度来看,在10^5中执行的算法比1/10^5 * log(n)(O(1)vs O(log(n))更好,但对于最合理的n,第一个算法会更好。例如,矩阵乘法的最佳复杂度是O(n^2.373),但是这个常数太高了,以至于没有(据我所知)计算库使用它。
- 当你计算一些大的事情时,big-o是有意义的。如果您需要对三个数字的数组进行排序,那么使用O(n*log(n))或O(n^2)算法就没什么关系了。
- 有时,小写时间复杂性的优点可以忽略不计。例如,有一个数据结构探戈树,它给O(log log N)时间复杂度来查找一个项目,但也有一个二叉树,它在O(log n)中查找相同的项目。即使对于大量的n = 10^20,差异也可以忽略不计。
- 时间复杂性并不是一切。设想一个在O(n^2)中运行并需要O(n^2)内存的算法。当n不太大时,它可能比O(n^3)时间和O(1)空间更好。问题是,您可以等待很长时间,但高度怀疑您能否找到一个足够大的RAM来与您的算法一起使用。
- 并行化是分布式世界的一个很好的特点。有些算法很容易并行,有些算法根本不并行。有时,在1000台复杂度比使用一台复杂度稍好的机器更高的商品机器上运行算法是有意义的。
在某些地方(安全性),复杂性可能是一项要求。没有人想拥有一个可以快速散列的散列算法(因为这样其他人就可以更快地残酷地攻击你).
- 虽然这与复杂度的切换无关,但某些安全功能的编写方式应能防止定时攻击。它们大多停留在同一个复杂度类中,但是被修改的方式总是需要更糟的情况才能做一些事情。一个例子是比较字符串是否相等。在大多数应用程序中,如果第一个字节不同,快速断开是有意义的,但在安全性方面,您仍将等待最后一个字节告诉坏消息。
- 有人申请了低复杂度算法的专利,公司使用高复杂度比花钱更经济。
- 有些算法很好地适应特定的情况。例如,插入排序的平均时间复杂度为O(n^2),比快速排序或合并排序更差,但作为一种在线算法,它可以在接收到值时(作为用户输入)对值列表进行有效排序,其中大多数其他算法只能对完整的值列表进行有效操作。
- 另外,我也见过几次人们关注他们的中心算法的大O,但忽略了设置成本。例如,如果不需要一次又一次地构建哈希表,那么构建哈希表的成本可能比线性地遍历数组要高。事实上,由于现代CPU的构建方式,即使二进制搜索在排序后的阵列上也可以像线性搜索一样快——分析是必要的。
- @Luaan"事实上,由于现代CPU的构建方式,即使二进制搜索在排序后的数组上也可以像线性搜索一样快——分析是必要的。"有趣!你能解释二进制搜索和线性搜索如何在现代CPU上花费相同的时间吗?
- @不要紧,我发现了这个:schani.wordpress.com/2010/04/30/linear-vs-binary-search
- @Denisdebernardy:不,事实上不是。它们可能是p中的算法。即使它们不是,在合理的定义下,并行化意味着什么,这并不意味着p!= NP。还请记住,搜索非确定性图灵机可能运行的空间是相当可并行的。
在O(log n)算法中,隐藏常数总是存在的,它可以降低。因此,它可以在实际数据中更快地工作。
还有空间方面的问题(例如在烤面包机上运行)。
还有开发人员时间问题——O(log n)可能1000×更容易实现和验证。
- 很好,谢谢。我想也值得考虑一个O(logn)算法来确保程序的稳定性(例如在自平衡二叉树中)。
- 这个答案可以从一些例子中受益…
- 我可以想到一个例子:对于一个小的排序数组,程序员实现一个二进制搜索函数要比编写一个完整的哈希图实现并使用它更容易、更紧凑。
- 复杂度的一个例子:在O(n*log n)中查找未排序列表的中值很容易,但在O(n)中却很难。
- @但那会使它变得越来越长,越来越少。请参阅其他答案以获取示例…
- -不要把原木放在烤面包机里…开玩笑吧,这是现场直播。lg n是如此,如此,如此接近k,对于大型n来说,大多数操作都不会注意到差异。
- 还有一个事实是,大多数人熟悉的算法复杂性不考虑缓存效果。根据大多数人的说法,在二叉树中查找某个东西是O(log2(n)),但实际上情况更糟,因为二叉树的位置很差。
- @Doval,是的,虽然这不是渐进式的。
- @pauldraper你说得对,但是由于缓存丢失比缓存命中慢一个数量级(并且有多个级别的缓存),糟糕的缓存性能会使常量因子非常高。关键是,你不能盲目地比较渐进行为,而忽略非任意大尺寸的n会发生什么。
- 我认为斐波那契堆是高常数的一个很好的例子。对于某些操作,它有比标准树/堆更好的o(),但实际运行时间几乎总是更糟。
- 特别是,log n实际上是一个常数64或更小。
- @第三十二列:不好的例子。二进制搜索是O(log n)。散列图是O(N);它只是"典型的"恒定时间。
- @这不取决于你的碰撞策略吗?无冲突散列映射具有O(1);通过二进制搜索树解决冲突将是O(log n)。
- @第三种情况:没有碰撞是不可能有效增加的。
我很惊讶还没有人提到内存绑定的应用程序。
可能有一种算法由于其复杂性(即o(1)我所说的"内存绑定"是指您经常访问不断超出缓存的数据。为了获取这些数据,必须先将实际内存空间中的内存拉到缓存中,然后才能对其执行操作。这个提取步骤通常非常慢——比您的操作本身慢得多。
因此,如果您的算法需要更多的操作(但这些操作是对已经在缓存中的数据执行的[因此不需要提取]),那么就实际的壁时间而言,它仍然会以更少的操作(必须对缓存外的数据执行的操作[因此需要提取])来执行您的算法。
- 阿利斯拉在谈到"空间问题"时间接地提到了这一点。
- 大量的缓存未命中只会将最终执行乘以一个常量值(对于具有1.6GHz RAM的4核3.2GHz CPU,该值不大于8,通常要低很多),因此在big-o表示法中,它被计为一个固定常量。因此,缓存未命中的唯一原因是移动阈值n,其中O(N)解决方案开始慢于O(1)解决方案。
- @你当然是对的。但这个问题要求我们在某种情况下,更倾向于使用O(logn)而不是O(1)。您可以很容易地想象这样一种情况:对于所有可行的n,内存限制较少的应用程序运行的时间更快,甚至更复杂。
- @Marianspanik缓存是否会错过多达300个时钟周期?8号从哪里来?
在数据安全性受到关注的情况下,如果更复杂的算法具有更好的抗定时攻击能力,则更复杂的算法可能比不复杂的算法更好。
- 虽然您所说的是正确的,但在这种情况下,在O(1)中执行的算法在定义上是不受攻击时间限制的。
- @justinlessard:o(1)意味着有一些输入大小,在这些输入大小之后,算法的运行时被一个常量限定。低于此阈值的情况未知。此外,对于算法的任何实际应用,甚至可能都不满足阈值。例如,该算法可能是线性的,因此会泄漏有关输入长度的信息。
- 运行时也可能以不同的方式波动,但仍然是有界的。如果运行时与(n mod 5) + 1成比例,则它仍然是O(1),但会显示有关n的信息。因此,更复杂的运行时更平滑的算法可能更可取,即使它可能渐进地(甚至在实践中)较慢。
- 这就是为什么Bcrypt被认为是好的;它使事情变慢。
- @这就是为什么使用BCRYPT的原因,并且符合这个问题。但这与这个答案无关,这个答案谈论的是定时攻击。
- 一些O(1)算法的另一个弱点是它可能正在执行单个或固定数量的表查找。攻击者可能会小心地污染缓存并给目标进程计时,或者识别哪个缓存行是由自己的哪些数据被逐出而加载的。
- 注意幽灵和熔毁攻击。两者都是针对O(1)程序的定时攻击。
阿利斯特拉说得很清楚,但没有提供任何例子,所以我会的。
你有一张10000个UPC代码的清单,上面列出了你的商店销售的商品。10位UPC,整数表示价格(单位为便士),30个字符表示收据说明。
O(log n)方法:您有一个排序列表。如果是ASCII,则为44字节;如果是Unicode,则为84字节。或者,将upc视为Int64,得到42&72字节。10000条记录——在最高的情况下,您看到的是一个兆字节的存储空间。
o(1)方法:不要存储upc,而是将其用作数组的条目。在最低的情况下,您将看到几乎三分之一兆字节的存储空间。
您使用哪种方法取决于您的硬件。在大多数合理的现代配置中,您将使用log n方法。如果出于某种原因,您运行的环境中RAM非常短,但您有大量的存储空间,那么我可以想象第二种方法是正确的答案。磁盘上三分之一兆字节并不重要,在磁盘的一个探针中获取数据是值得的。简单的二进制方法平均需要13个。(但是,请注意,通过集群化您的密钥,您可以将其降至保证的3次读取,实际上,您可以缓存第一次读取。)
- 我有点困惑。您是在讨论创建一个100亿条目的数组(其中大部分是未定义的)并将UPC作为该数组的索引吗?
- DavidZ是的。如果使用稀疏数组,可能无法获得O(1),但它只使用1MB内存。如果您使用的是实际的阵列,则可以保证O(1)访问,但它将使用1/3 TB的内存。
- 在现代系统中,它将使用1/3 TB的地址空间,但这并不意味着它将在任何地方接近如此分配的后备内存。大多数现代操作系统在需要时才为分配提交存储。执行此操作时,实际上是在操作系统/硬件虚拟内存系统中隐藏数据的关联查找结构。
- @Novelocrat是真的,但如果您以RAM速度进行查找,则查找时间无关紧要,没有理由使用40MB而不是1MB。阵列版本只有在存储访问很昂贵的情况下才有意义——您要离开磁盘。
- 或者,如果这不是一个性能关键的操作,而且开发人员的时间也很昂贵——比如说malloc(search_space_size),订阅它返回的内容就很容易了。
考虑一棵红黑相间的树。它可以访问、搜索、插入和删除O(log n)。与可以访问O(1)的数组相比,其余的操作是O(n)。
因此,如果应用程序的插入、删除或搜索频率高于访问频率,并且只在这两种结构之间进行选择,那么我们更喜欢红黑树。在这种情况下,您可能会说我们更喜欢红黑树的更繁琐的O(log n)访问时间。
为什么?因为访问不是我们最关心的问题。我们正在权衡:我们的应用程序的性能受除此之外的其他因素的影响更大。我们允许这个特定的算法受到性能的影响,因为我们通过优化其他算法获得了很大的收益。
所以你的问题的答案很简单:当算法的增长率不是我们想要优化的,当我们想要优化其他东西的时候。所有其他答案都是这方面的特例。有时我们会优化其他操作的运行时间。有时我们会优化记忆。有时我们会优化安全性。有时我们优化可维护性。有时我们会优化开发时间。当您知道算法的增长率对运行时间的影响不是最大的时候,即使覆盖常数足够低,也需要对运行时间进行优化。(如果您的数据集超出此范围,您将针对算法的增长率进行优化,因为它最终将主导常量。)每件事都有成本,在许多情况下,我们将较高增长率的成本与算法进行权衡,以优化其他内容。
对。
在实际情况中,我们对使用短字符串键和长字符串键进行表查找进行了一些测试。
我们使用了一个std::map、一个std::unordered_map和一个哈希(哈希在字符串长度上最多采样10倍)(我们的键趋向于guid类型,所以这是很好的),以及一个哈希(在理论上减少了冲突),一个未排序的向量(在这里我们进行==比较),以及(如果我记得正确的话)一个未排序的向量,其中we还存储一个哈希,首先比较哈希,然后比较字符。
这些算法的范围从EDOCX1(无序映射)到EDOCX1(线性搜索)。
对于中等大小的N,O(N)经常打败O(1)。我们怀疑这是因为基于节点的容器需要我们的计算机在内存中跳跃更多,而基于线性的容器则没有。
两者之间存在O(lg n)。我不记得是怎么回事。
性能差异不大,在较大的数据集上,基于哈希的数据集的性能要好得多。所以我们坚持使用基于散列的无序映射。
实际上,对于合理尺寸的N,O(lg n)是O(1)。如果您的计算机在您的表中只有40亿个条目,那么O(lg n)在上面被32限定。(lg(2^32)=32)(在计算机科学中,lg是基于对数的2的简称)。
实际上,lg(n)算法比o(1)算法慢,不是因为对数增长因子,而是因为lg(n)部分通常意味着算法有一定程度的复杂性,并且这种复杂性比lg(n)项中的任何"增长"增加了更大的常量因子。
然而,复杂的O(1)算法(如散列映射)很容易具有类似或更大的常量因子。
并行执行算法的可能性。
我不知道类O(log n)和O(1)是否有一个例子,但是对于一些问题,当算法更容易并行执行时,您可以选择一个复杂度更高的类。
有些算法不能并行化,但复杂度很低。考虑另一种算法,它可以获得相同的结果,并且易于并行化,但具有更高的复杂性。当在一台机器上执行时,第二个算法会变慢,但当在多台机器上执行时,实际执行时间会越来越短,而第一个算法无法加速。
- 但是,并行化所做的只是减少其他人讨论过的常数因子,对吗?
- 是的,但是每次执行机器的数量翻倍时,并行算法都可以将常量因子除以2。另一种单线程算法可以以恒定的方式将常数因子减少一次。因此,使用并行算法,您可以动态地对n的大小作出反应,并加快墙时钟的执行时间。
假设您正在嵌入式系统上实现黑名单,其中0到1000000之间的数字可能被列入黑名单。这给你两个可能的选择:
使用1000000位的位集
使用已排序的黑名单整数数组并使用二进制搜索访问它们
对位集的访问将保证恒定访问。就时间复杂性而言,它是最优的。无论从理论上还是从实际的角度来看(它是O(1),具有非常低的恒定开销)。
不过,您可能希望使用第二种解决方案。特别是如果您希望黑名单整数的数量非常小,因为它将更节省内存。
即使您不为内存不足的嵌入式系统开发,我也可以增加1000000到10000000000的任意限制,并提出相同的论点。然后位集需要大约125g内存。有了O(1)最坏情况下的复杂性保证可能无法说服您的老板为您提供如此强大的服务器。
在这里,比起O(1)位集,我更喜欢二进制搜索(O(logn))或二进制树(O(logn))。而且,一个最坏情况下复杂度为O(n)的哈希表在实践中可能会击败所有哈希表。
这里我的答案是快速随机加权选择随机矩阵的所有行是一个例子,当m不太大时,复杂度为o(m)的算法比复杂度为o(log(m))的算法更快。
人们已经回答了你的确切问题,所以我将处理一个稍有不同的问题,当人们来到这里时,他们可能会真正想到这个问题。
许多"O(1)时间"算法和数据结构实际上只需要预期的O(1)时间,这意味着它们的平均运行时间是O(1),可能只有在某些假设下。
常见示例:哈希表,"数组列表"的扩展(也就是说,动态调整数组/向量大小)。
在这种情况下,您可能更喜欢使用数据结构或算法,这些数据结构或算法的时间保证是绝对对数有界的,即使它们的平均性能可能更差。因此,一个例子可能是一个平衡的二进制搜索树,它的运行时间平均较差,但在最坏的情况下会更好。
一个更普遍的问题是,在某些情况下,即使g(n) << f(n)作为n趋向于无穷大,人们还是更喜欢O(f(n))算法而不是O(g(n))算法。正如其他人已经提到的,在f(n) = log(n)和g(n) = 1的情况下,答案显然是"是"。有时是的,即使在f(n)是多项式,但g(n)是指数的情况下。一个著名而重要的例子是求解线性规划问题的单纯形算法。在20世纪70年代,它被证明是O(2^n)。因此,更糟的情况是不可行的。但是——它的平均情况行为是非常好的,即使对于具有数万个变量和约束的实际问题也是如此。在20世纪80年代,人们发现了线性规划的多项式时间算法(如Karmarkar的内点算法),但30年后,simplex算法似乎仍然是首选算法(除了某些非常大的问题)。这是明显的原因,平均事例行为通常比糟糕的事例行为更重要,但也有一个更微妙的原因,即单纯形算法在某种意义上更具信息性(例如,敏感性信息更容易提取)。
把我的2美分放进去:
有时,当算法在某个硬件环境中运行时,会选择更复杂的算法来代替更好的算法。假设我们的O(1)算法不按顺序访问一个非常大的固定大小数组的每个元素来解决我们的问题。然后把这个阵列放在机械硬盘或磁带上。
在这种情况下,O(logn)算法(假设它按顺序访问磁盘)变得更加有利。
- 我可以在这里补充一下,在顺序访问驱动器或磁带上,O(1)算法变为O(N),这就是为什么顺序解决方案变得更有利的原因。许多O(1)操作依赖于将添加和索引查找作为一个常量时间算法,而该算法不在顺序访问空间中。
简单地说:因为系数——与设置、存储以及该步骤的执行时间相关的成本——在一个较小的大O问题上可能比在一个较大的问题上大得多。big-o只是算法可伸缩性的一个度量。
考虑黑客字典中的以下例子,提出一种基于量子力学多世界解释的排序算法:
Permute the array randomly using a quantum process,
If the array is not sorted, destroy the universe.
All remaining universes are now sorted [including the one you are in].
(来源:http://catb.org/~esr/jargon/html/b/bogo sort.html)
请注意,该算法的大O是O(n),它比任何已知的排序算法都要好。线性阶跃的系数也很低(因为它只是一个比较,而不是线性交换)。事实上,类似的算法可以在多项式时间内解决np和co-np中的任何问题,因为每个可能的解(或可能的证明没有解)都可以使用量子过程生成,然后在多项式时间内进行验证。
然而,在大多数情况下,我们可能不想冒多个世界可能不正确的风险,更不用说实施步骤2的行为仍然"留给读者作为练习"。
对于使用O(log(n))算法而不是许多其他答案都忽略的O(1)算法来说,有一个很好的用例:不变性。散列映射有O(1)个PUT和GET,假设散列值分布良好,但它们需要可变状态。不可变树映射有O(log(n))放置和获取,这是渐进地慢。但是,不变性可能非常有价值,足以弥补更差的性能,在需要保留多个版本的映射的情况下,不变性允许您避免复制映射,即O(N),因此可以提高性能。
当n是有界的,且o(1)算法的常数乘法器高于log(n)上的界时的任何点。例如,在哈希集中存储值是O(1),但可能需要对哈希函数进行昂贵的计算。如果可以对数据项进行琐碎的比较(相对于某个顺序),并且n上的界限使得log n明显小于任何一个项上的哈希计算,那么在平衡二叉树中存储可能比存储在哈希集中更快。
在需要公司上限的实时情况下,您可以选择Heapsort,而不是Quicksort,因为Heapsort的平均行为也是其最坏的行为。
一个实用的例子是哈希索引和Postgres数据库中的B树索引。
哈希索引形成一个哈希表索引来访问磁盘上的数据,而btree(如名称所示)使用btree数据结构。
在大O时期,这是O(1)对O(logn)。
目前,Postgres不鼓励使用哈希索引,因为在现实生活中,尤其是在数据库系统中,实现无冲突哈希非常困难(可能导致最坏情况下的O(N)复杂性),因此,更难保证它们的崩溃安全(在Postgres中称为提前写入日志-wal)。
在这种情况下进行这种权衡,因为O(logn)对于索引来说已经足够好了,实现O(1)非常困难,时间差也不重要。
当n很小,O(1)很慢时。
当o(1)中的"1"工作单元相对于o(log n)中的工作单元非常高,并且预期的集合大小很小时。例如,如果只有两个或三个项,计算字典哈希代码可能比迭代数组慢。
或
当O(1)算法中的内存或其他非时间资源需求相对于O(logn)算法特别大时。
当重新设计一个程序时,发现一个程序是用O(1)而不是O(lgn)来优化的,但是如果它不是这个程序的瓶颈,并且很难理解O(1)算法。那么你就不用使用O(1)算法了
当O(1)需要很多您无法提供的内存时,而O(lgn)的时间可以被接受。
对于安全应用程序来说,这种情况通常是这样的,我们希望设计一些算法缓慢的问题,以便阻止某人快速获得问题的答案。
下面是一些我头脑中的例子。
- 密码散列有时会任意变慢,以便更难用暴力猜测密码。这个信息安全站有一个要点(以及更多)。
- 比特币使用一个可控的慢问题来解决计算机网络,以便"挖掘"硬币。这使得货币可以由集体系统以可控的速度开采。
- 非对称密码(如RSA)的设计目的是为了在没有密钥的情况下进行解密,从而防止没有私钥的其他人破解加密。这些算法被设计成在希望的O(2^n)时间内被破解,其中n是密钥的位长度(这是蛮力)。
在CS中,最坏的情况下,快速排序是O(n^2),但一般情况下是O(n*log(n))。因此,在分析算法效率时,"大O"分析有时不是你唯一关心的事情。