在任何情况下,您是否希望O(log n)
时间复杂度达到O(1)
时间复杂度?或O(n)
到O(log n)
?
你有任何例子吗?
答案 0 :(得分:261)
有很多理由可以选择具有较高O时间复杂度的算法而不是较低的算法:
10^5
中执行的算法在大O视角下比1/10^5 * log(n)
更好(O(1)
vs O(log(n)
),但对于最合理的n
,第一个会表现得更好。例如,矩阵乘法的最佳复杂度是O(n^2.373)
,但常量是如此之高,以至于没有(据我所知)计算库使用它。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来与你的算法一起使用O(n^2)
,比quicksort或mergesort差,但作为online algorithm,它可以在接收到值时对列表进行有效排序(作为用户输入)大多数其他算法只能在完整的值列表上有效运行。答案 1 :(得分:228)
总有隐藏的常量,在 O (log n )算法中可以更低。因此,它可以更快地在实际中为现实数据工作。
还有空间问题(例如在烤面包机上运行)。
还有开发人员时间问题 - O (log n )可能更容易实现和验证。
答案 2 :(得分:57)
我很惊讶没有人提到过内存限制的应用程序。
由于其复杂性,可能存在一个具有较少浮点运算的算法(即 O (1)< O (log n ))或因为复杂性前面的常数较小(即 2
我的意思是"内存限制"是你经常访问经常超出缓存的数据。为了获取此数据,您必须先将实际内存空间中的内存提取到缓存中,然后才能对其执行操作。这个提取步骤通常非常慢 - 比您的操作本身慢得多。
因此,如果您的算法需要更多操作(但这些操作是对已经在缓存中的数据执行的[因此不需要提取]),它仍然会以较少的操作(必须在在实际的挂壁时间方面,超出缓存数据[因此需要获取]。
答案 3 :(得分:42)
在数据安全性受到关注的情况下,如果更复杂的算法对timing attacks具有更好的抵抗力,则更复杂的算法可能优于不太复杂的算法。
答案 4 :(得分:37)
Alistra钉了它,但没有提供任何例子,所以我会。
您有一个10,000个UPC代码列表,用于商店销售的商品代码。 10位数字UPC,价格为整数(便士价格),收据描述为30个字符。
O(log N)方法:您有一个排序列表。如果是ASCII则为44字节,如果是Unicode则为84或者,将UPC视为int64,你得到42& 72个字节。 10,000条记录 - 在最高的情况下,您会在一兆字节的存储空间中查看。
O(1)方法:不要存储UPC,而是将其用作数组的入口。在最低的情况下,您可以查看几乎三分之一TB的存储空间。
您使用哪种方法取决于您的硬件。在大多数合理的现代配置中,您将使用log N方法。我可以想象第二种方法是正确的答案,如果由于某种原因你在一个RAM非常短但你有大量存储空间的环境中运行。磁盘上三分之一的TB是没什么大不了的,在磁盘的一个探针中获取数据是值得的。简单的二进制方法平均需要13个。 (但请注意,通过对键进行聚类,您可以将其保证为3次读取,实际上您将缓存第一个。)
答案 5 :(得分:36)
考虑一棵红黑树。它具有O(log n)
的访问权限,搜索,插入和删除功能。与数组进行比较,该数组具有O(1)
的访问权限,其余操作为O(n)
。
因此,给定一个我们插入,删除或搜索的应用程序比我们访问更频繁,并且只选择这两个结构,我们更喜欢红黑树。在这种情况下,您可能会说我们更喜欢红黑树的O(log n)
访问时间更加繁琐。
为什么呢?因为访问不是我们最重要的考虑因素。我们正在进行权衡:我们的应用程序的性能受到除此之外的因素的更大影响。我们允许这种特定算法的性能受到影响,因为我们通过优化其他算法来获得大量收益。
因此,您的问题的答案就是:当算法的增长率不是我们想要优化的内容时,当我们想要优化其他内容时。所有其他答案都是特殊情况。有时我们会优化其他操作的运行时间。有时我们会优化内存。有时我们会优化安全性。有时我们会优化可维护性。有时我们会优化开发时间。当你知道算法的增长率对运行时间的影响不是最大时,即使最重要的常数足够低也很重要。 (如果您的数据集超出此范围,您将优化算法的增长率,因为它最终将主导常量。)一切都有成本,在许多情况下,我们交易的成本更高的成本算法优化别的东西。
答案 6 :(得分:23)
是
在一个实际案例中,我们使用短字符串键和长字符串键进行了一些表查找。
我们使用std::map
,std::unordered_map
的哈希值在字符串长度上最多采样10次(我们的键往往像guid一样,所以这很不错),以及一个哈希对每个字符进行采样(理论上减少了冲突),一个未排序的向量,我们进行==
比较,并且(如果我没记错的话)一个未排序的向量,我们也存储一个哈希,首先比较哈希,然后比较人物。
这些算法的范围从O(1)
(unordered_map)到O(n)
(线性搜索)。
对于适度大小的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)算法(如哈希映射)很容易产生类似或更大的常数因子。
答案 7 :(得分:21)
并行执行算法的可能性。
我不知道是否有类O(log n)
和O(1)
的示例,但是对于某些问题,当算法更容易时,您选择具有更高复杂度类的算法并行执行。
某些算法无法并行化,但复杂度较低。考虑另一种算法,它可以实现相同的结果,并且可以轻松并行化,但具有更高的复杂性等级。在一台机器上执行时,第二种算法较慢,但在多台机器上执行时,实际执行时间越来越低,而第一种算法无法加速。
答案 8 :(得分:15)
让我们假设您在嵌入式系统上实施黑名单,其中0到1,000,000之间的数字可能会被列入黑名单。这留下了两个可能的选择:
访问bitset将保证持续访问。就时间复杂性而言,它是最佳的。从理论和实际的角度来看(O(1)具有极低的恒定开销)。
不过,您可能希望更喜欢第二种解决方案。特别是如果你希望黑名单整数的数量非常小,因为它会更有效。
即使你没有为内存稀缺的嵌入式系统开发,我也可以增加1,000,000到1,000,000,000,000的任意限制并提出相同的论点。然后bitset将需要大约125G的内存。保证O(1)的最坏情况复杂性可能无法说服你的老板为你提供如此强大的服务器。
在这里,我强烈希望在O(1)位集上进行二进制搜索(O(log n))或二叉树(O(log n))。而且,最糟糕的O(n)复杂度的哈希表可能会在实践中击败所有这些哈希表。
答案 9 :(得分:13)
我的答案Fast random weighted selection across all rows of a stochastic matrix是一个例子,当properties.map{case (key, value) => s"$key = $value"}
不是太大时,复杂度为O(m)的算法比复杂度为O(log(m))的算法要快。
答案 10 :(得分:12)
人们已经回答了你的确切问题,所以我会解决一个人们在来这里时可能会想到的一个稍微不同的问题。
很多" O(1)时间"算法和数据结构实际上只占用 预期的 O(1)时间,这意味着它们的平均运行时间为O(1),可能只有某些假设。
常见示例:哈希表,扩展"数组列表" (a.k.a.动态大小的数组/向量)。
在这种情况下,您可能更喜欢使用数据结构或算法,即使它们的平均表现可能会更差,但数据结构或算法的时间保证以绝对为对数。 因此,一个例子可能是平衡的二叉搜索树,其运行时间平均更差,但在最坏的情况下更好。
答案 11 :(得分:11)
更一般的问题是,如果有人希望将O(f(n))
算法改为O(g(n))
算法,即使g(n) << f(n)
为n
趋于无穷大。正如其他人已经提到过的那样,答案显然是&#34;是&#34;在f(n) = log(n)
和g(n) = 1
的情况下。即使在f(n)
是多项式但g(n)
是指数的情况下,有时也是如此。一个着名而重要的例子是用于求解线性规划问题的Simplex Algorithm。在20世纪70年代,它被证明是O(2^n)
。因此,其糟糕的行为是不可行的。但是 - 它的平均情况行为非常好,即使对于成千上万个变量和约束的实际问题也是如此。在20世纪80年代,人们发现了用于线性规划的多项式时间算法(例如Karmarkar's interior-point algorithm),但30年后,单纯形算法似乎仍然是首选算法(除了某些非常大的问题)。这是因为显而易见的原因,平均情况行为通常比更糟糕的行为更重要,但也有一个更微妙的原因,即单纯形算法在某种意义上更具信息性(例如,敏感性信息更容易提取)。 p>
答案 12 :(得分:10)
将我的2美分投入:
当算法在某个硬件环境中运行时,有时会选择更复杂的算法来代替更好的算法。假设我们的O(1)算法非顺序访问一个非常大的固定大小数组的每个元素来解决我们的问题。然后将该阵列放在机械硬盘驱动器或磁带上。
在这种情况下,O(logn)算法(假设它顺序访问磁盘)变得更有利。
答案 13 :(得分:9)
使用O(log(n))算法代替O(1)算法有一个很好的用例,其他许多答案都忽略了这一算法:不变性。哈希映射具有O(1)个put和gets,假设哈希值的分布很好,但它们需要可变状态。不可变树映射具有O(log(n))puts和gets,它渐近地变慢。但是,不变性可能足以弥补性能的下降,并且在需要保留多个版本的映射的情况下,不变性允许您避免必须复制映射,即O(n),因此可以< em>提高性能。
答案 14 :(得分:9)
简单地说:因为系数 - 与设置,存储和该步骤的执行时间相关的成本 - 可以通过较小的大O问题比使用较大的问题大得多。 Big-O只是算法可伸缩性的衡量标准。
考虑以下来自Hacker's Dictionary的例子,提出依赖Multiple Worlds Interpretation of Quantum Mechanics的排序算法:
- 使用量子过程随机置换数组
- 如果数组未排序,则销毁该Universe。
- 现在对所有剩余的宇宙进行排序[包括你所在的宇宙]。
醇>
(资料来源:http://catb.org/~esr/jargon/html/B/bogo-sort.html)
请注意,此算法的big-O为O(n)
,它胜过通用项目迄今为止已知的任何排序算法。线性步长的系数也非常低(因为它只是比较,而不是交换,线性完成)。事实上,类似的算法可用于在多项式时间内解决NP和co-NP中的任何问题,因为每个可能的解决方案(或可能证明没有解决方案)都可以使用量子过程,然后在多项式时间内验证。
然而,在大多数情况下,我们可能不想承担多个世界可能不正确的风险,更不用说执行第2步的行为仍然是作为练习读者&#34;。
答案 15 :(得分:7)
在n有界并且O(1)算法的常数乘数高于log(n)上的约束时的任何时刻。例如,将值存储在散列集中为O(1 ),但可能需要昂贵的散列函数计算。如果可以简单地比较数据项(关于某个顺序)并且n上的边界使得log n明显小于任何一个项上的散列计算,那么存储在平衡二叉树中可能比存储在一个哈希集。
答案 16 :(得分:6)
在您需要坚定上限的实时情况下,您可以选择例如与快速通道相反,而不是快速通道,因为海盗的平均行为也是最糟糕的行为。
答案 17 :(得分:6)
添加已经很好的答案。一个实际的例子是postgres数据库中的哈希索引与B树索引。
哈希索引形成哈希表索引以访问磁盘上的数据,而btree则顾名思义使用Btree数据结构。
在Big-O时间内,这些是O(1)vs O(logN)。
目前不鼓励使用哈希索引,因为在现实生活中,特别是在数据库系统中,实现无冲突的散列是非常困难的(可能导致O(N)最坏情况的复杂性),因此,它甚至更多更难以使它们安全崩溃(称为提前写入记录 - 在postgres中使用WAL)。
这种权衡是在这种情况下进行的,因为O(logN)对索引来说已经足够好了,实现O(1)非常困难,时间差异也不重要。
答案 18 :(得分:4)
当n
很小时,O(1)
一直很慢。
答案 19 :(得分:3)
或
答案 20 :(得分:3)
答案 21 :(得分:1)
对于我们想要设计问题的安全应用程序通常就是这种情况,这些问题的算法目的很慢,以阻止某人过快地获得问题的答案。
以下是几个例子。
O(2^n)
时间内被破解,其中n
是密钥的位长(这是蛮力)。在CS的其他地方,在最坏的情况下,快速排序为O(n^2)
,但在一般情况下为O(n*log(n))
。出于这个原因,&#34; Big O&#34;在分析算法效率时,分析有时并不是您唯一关心的事情。