比较O(n + m)和O(max(n,m))的复杂度

时间:2016-11-01 19:03:32

标签: time-complexity big-o

我今天接受了面试。并被问及std:set_intersection的复杂性。当我回答时,我提到了

O(N + M)

等于:

O(MAX(N,M))

我被告知这是不正确的。我没有成功地试图表现出对等:

O(0.5 *(n + m))≤O(max(n,m))≤O(n + m)

我的问题是:我真的不对吗?

3 个答案:

答案 0 :(得分:10)

对于所有 m n ≥0,max( m n )≤< O 中的em> m + n →max( m n m + n ), m + n ≤2max( m n )→ m + n O (最大值( m n ))。

因此 O (max( m n ))= O m + n )。

ADDENDUM :如果 f 属于 O m + n ),那么常数 D &gt; 0存在, f n m )&lt; D *( m + n m n 足够大。因此 f n m )&lt; 2 D * max( m n )和 O m + n )必须是 O 的子集(max( m n ))。 O 的证明(max( m n ))是 O 的子集( m + n )类似。

答案 1 :(得分:9)

你完全正确的O(n + m)等于O(max(n,m)),更准确地说我们可以证明Θ(n + m)=Θ(max(n,m)更加严密,证明你的判断。数学证明(对于大O和Θ)非常简单,易于理解。因为我们有mathematical proof这是一种说话的方式,但在一种更明确,更严格的方式doesn't leaves any ambiguity

虽然你(错误地)告诉我这是不正确的,因为如果我们想要非常精确,这不是合适的 - 数学方式来表达max(m,n)的顺序与m + n相同。您使用了“等于”这个词来指代大O符号,但是大O符号的定义是什么?

  

它被称为Sets。说max(n + m)属于O(m + n)是   最正确的方式,反之亦然m + n属于O(max(m,n))。在大O.   通常使用和表示m + n = O(max(n,m))的符号。

引起的问题是你没有尝试引用函数的顺序,比如f是O(g),但是你试图比较集合O(f)和O(g)。但是证明两个无限集是平等并不容易(这可能会使面试官感到困惑)。

我们可以说当包含相同的元素时,集合A和B是相同的(或相等)(我们不会尝试比较,而是引用它们包含的元素,因此它们必须是有限)。在谈论Big O Sets时,甚至不能轻易应用识别。

  

F的大O用于表示我们正在谈论的是Set   包含顺序大于或等于F的所有函数   功能有吗??

Infinite since F+c is contained and c can take infinite values.
  

你怎么能说两个不同的集合相同(或相等)   无限,这不是那么简单。

So I understand what you are thinking that n+m and max(n,m) have same 
order but **the right way to express that** is by saying n+m is    
O(max(n,m)) and max(n,m)is O(m+n) ( O(m+n) is equal to O(max(m,n)) 
may better requires a proof).

还有一件事,我们说这些函数具有相同的顺序,这在数学上是正确的,但在尝试优化算法时,您可能需要考虑一些较低阶因子,那么它们可能会给您略微不同的结果但渐近行为被证明是相同的。

  

<强> 结论

正如您可以阅读wikipedia(以及所有大学或每本算法书中的所有课程)Big O /θ/Ω/ω/ο标记helps us compare functions并找到它们的增长顺序而不是功能集,这就是你被告知错了的原因。虽然很容易说O(n ^ 2)是O(n)的子集,但如果两个集合相同则很难比较无穷大。康托尔致力于对无限集进行分类,例如我们知道自然数是无数的,而实数是无数无数的,所以即使两者都是无穷大,实数也不仅仅是自然数。在尝试对无限集进行排序和分类时,它变得非常复杂,这将更多地是数学研究,而不是比较函数的方法。

<强>更新

事实证明你可以简单地证明O(n + m)等于O(max(n,m)):

对于属于O(n + m)的每个函数F,这意味着有常数c和k这样:

 F <= c(n+m) for every n>=k and m>=k

然后也站着:

 F <= c(n+m)<= 2c*max(n,m) 

因此F属于O(max(n,m)),因此O(m + n)是O的子集(max(n,m))。 现在考虑F属于O(max(n,m))然后有常数c和k这样:

 F <= c*max(n+m) for every n>=k and m>=k

我们也有:

F <= c*max(n+m)<=2c(m+n) for every n>=k and m>=k

因此根据定义存在c'= 2c且具有相同的k:F是O(m + n)并且因此O(max(n,m))是O(n + m)的子集。因为我们证明O(m + n)是O(max(n,m))的子集,我们证明 O(max(m,n))和O(m + n)相等 <强大>这个数学证明证明你毫无疑问是完全正确的。

最后请注意,证明m + n为O(max(n,m))且max(n,m)为O(m + n)并不能立即证明集合相等(我们需要一个证明),因为你的说法只是证明函数具有相同的顺序,但我们没有检查集合。虽然很容易看到(一般情况下)如果 f是O(g)而g是O(F)那么你可以很容易证明那个大O设置相等< / strong>就像我们在前一段中所做的那样。

答案 2 :(得分:5)

我们通过严格的Big-O分析表明您确实是正确的,在您的分析中给出了一个可能的增长参数选择。然而,这并不一定意味着访谈者的观点是不正确的,而是他/她对增长参数的选择不同。然而,他/她提示您的答案完全不正确是值得怀疑的:您可能只是使用两种稍微不同的方法来分析std::set_intersection的渐近复杂性,这两种方法都导致了算法运行的普遍共识在线性时间。

制剂

让我们先看一下cppreference上的std::set_intersection的引用(强调我的

  

参数

     

first1last1 - 要检查的第一个元素范围

     

first2last2 - 要检查的第二个元素范围

     

复杂性

     

最多 2·(N1+N2-1)比较,

N1 = std::distance(first1, last1)
N2 = std::distance(first2, last2)

std::distance本身就是线性的(最坏情况:没有随机访问)

  

std::distance

     

...

     

返回firstlast之间的元素数。

我们将简要回顾一下Big-O符号的基本原理。

Big-O表示法

我们松散地说明f中的函数或算法O(g(n))的定义(挑剔,O(g(n))函数集,因此{ {1}},而不是通常误用的f ∈ O(...))。

  

如果<{em>} f(n) ∈ O(...)中的函数f ,那么O(g(n))就是上层   绑定在c · g(n)上,用于某些非负常数f(n) ,以便c   保持,表示足够大f(n) ≤ c · g(n) (即n表示常量   n ≥ n0)。

因此,要显示n0,我们需要找到一组(非负)常量f ∈ O(g(n))来实现

(c, n0)

但是,我们注意到,此设置并非唯一;找到常量f(n) ≤ c · g(n), for all n ≥ n0, (+) 使得(+)成立的问题是退化。实际上,如果存在任何这样的常数对,那么将存在无限量的不同这样的对。

我们继续对(c, n0)进行Big-O分析,基于已知的最坏情况下的算法比较(我们将这样的比较视为基本操作)。

将Big-O渐近分析应用于std::set_intersection示例

现在考虑两个元素范围,例如set_intersectionrange1,并假设这两个范围中包含的元素数分别为range2m

  • 注意!在分析的初始阶段,我们做出选择:我们选择用两个不同的增长参数来研究问题(或者更确切地说,关注最大的这两个中的一个)。正如我们将要看到的那样,这将导致与OP所述的渐近复杂性相同的渐近复杂性。但是,我们也可以选择让n作为选择的参数:我们仍然会得出结论:k = m+n具有线性时间复杂度,而是std::set_intersection(其中是k 不是 m+n),而是max(m, n)m中的最大值。这些只是我们在继续进行Big-O表示法/渐近分析之前自由选择设置的前提条件,并且很可能访问者倾向于选择使用n作为复杂性进行分析。增长参数而不是其两个组成部分中的最大值。

现在,从上面我们知道,在最坏的情况下,k将运行std::set_intersection比较/基本操作。让

2 · (m + n - 1)

由于目标是根据Big-O(上限)找到渐近复杂度的表达式,我们可以在不失一般性的情况下假设h(n, m) = 2 · (m + n - 1) 和定义

n > m

我们继续用Big-O表示法分析f(n) = 2 · (n + n - 1) = 4n - 2 > h(n,m) (*) 的渐近复杂性。让

f(n)

并注意

g(n) = n

现在(选择)让f(n) = 4n - 2 < 4n = 4 · g(n) c = 4,我们可以陈述以下事实:

n0 = 1

鉴于f(n) < 4 · g(n) = c · g(n), for all n ≥ n0, (**) ,我们从(**)知道我们现在已经证明

(+)

此外,由于`(*)成立,自然

f ∈ O(g(n)) = O(n)

成立。

如果我们改变我们的初始假设并假设h ∈ O(g(n)) = O(n), assuming n > m (i) ,那么重新跟踪上面的分析将反过来产生类似的结果

m > n

结论

因此,给定两个范围h ∈ O(g(m)) = O(m), assuming m > n (ii) range1分别持有range2m元素,我们已经证明了{{1}的渐近复杂性应用两个这两个范围的确是

n

我们选择了最大的std::set_intersectionO(max(m, n)) 作为我们分析增长的参数。

然而,当谈到Big-O表示法时,这并不是真正有效的注释(至少不常见)。当我们使用Big-O表示法来描述某些算法或函数的渐近复杂性时,我们就某个单一的增长参数(不是其中两个)这样做。

我们可以在不失一般性的情况下假设m描述范围内具有最多元素的元素数量,而不是回答复杂性为n,并且假设这个假设,只是状态而不是O(max(m, n))的渐近复杂度的上限是n(线性时间)。

关于面试反馈的猜测:如上所述,面试官可能只是坚定地认为Big-O符号/渐近分析应该基于{{1} }作为增长的参数而不是其两个组成部分中的最大部分。当然,另一种可能性是,面试官只是混淆地询问std::set_intersection的实际比较次数的最坏情况,同时将其与Big-O表示法和渐近式的单独事项相混淆。复杂性。

最后的评论

最后请注意,对O(n)的最坏情况复杂性的分析并不代表通常研究的非有序集交集问题:前者适用于已经排序的范围(参见Boost&#引用) 39;下面的k = m+nstd::set_intersection的起源,而在后者中,我们研究非有序集合的交集的计算。

  

描述

     

std::set_intersect构造一个排序范围即交集   已排序范围set_intersectionstd::set_intersection。返回值是   输出范围的结束。

作为后者的示例,Python的set_intersection set方法适用于非有序集合,并应用于集合rng1rng2it has an average case and a worst-case complexity of O(min(len(s), len(t)) and O(len(s) * len(t)) , 分别。这种实现中平均和最差情况之间的巨大差异源于这样一个事实,即基于散列的解决方案在实践中通常运行良好,但对于某些应用,理论上可能具有非常差的最坏情况性能。

有关后一问题的其他详细信息,请参阅例如