我试图估计一个看起来像这样的算法的最坏情况(评论中的估计复杂度是我的,其中V
是顶点数,E
是数字图中的边缘):
while(nodes.size()!=0) { // O(V) in which nodes is a LinkedList
Vertex u = Collections.min(nodes); // O(V)
nodes.remove(u); // O(1)
for(Map.Entry<Vertex, Integer> adjacency : u.adj.entrySet()) { // O(E)
// Some O(1) Statement
if(nodes.contains(v)) { // O(V)
// Some O(1) Statement
}
}
}
我的问题很简单:
在while
循环中的每一轮之后,nodes
LinkedList
将变得越来越小。
最终,Collections.min()
和nodes.contains()
操作每轮都会花费更少的时间。
我的理解是Big O Notation始终考虑 最差 ,因此上述复杂性应该是正确的。
否则,请您解释一下如何在上述场景中找出正确的复杂性?
答案 0 :(得分:1)
是的,这些看起来是正确的。把它们放在一起你就会得到时间O(V*(V+E))
。 (更正,O((1+E)*V^2)
- 我错过了O(V)
内循环中的O(E)
。)
然而,对您的理解有一个重要的修正。大O符号并非总是最坏的情况。符号是一种估计数学函数增长的方法。这些功能是最坏的情况,还是平均值,或者他们衡量的是完全取决于手头的问题。例如,快速排序可以在O(n^2)
最差情况下运行时间实施,平均运行时间O(n log(n))
,平均使用O(log(n))
额外内存,最坏情况下使用O(n)
额外内存。
答案 1 :(得分:1)
您可以在每一步获取尽可能大的值,但这可能会给出一个过高估计的最终值。为了确保该值是准确的,您可以将上限保留到最后,但通常它最终都是相同的。
当V
的值发生变化时,引入另一个变量v
,这是一个特定迭代的值。然后,每次迭代的复杂度为v+(E*v)
。然后,总复杂度是每次迭代的总和:
sum(v = 1...V) v+(E*v)
= 1+1E + 2+2E + 3+3E + ... + V+VE - Expand the sum
= (1 + 2 + 3 + ... + V) + (1 + 2 + 3 + ... + V)E - Regroup terms
= (V^2 + V)/2 + (V^2 + V)E/2 - Sum of arithmetic series
= (V^2 + V + EV^2 + EV)/2 - Addition of fractions
= O(EV^2) - EV^2 greater than all other terms
答案 2 :(得分:1)
nodes.contains
在Θ(V)
中的最坏情况时间复杂度,for循环在Θ(E)
中运行多次,因此Θ(V*E)
中的最坏情况时间复杂度,Collections.min
在Θ(V)
中具有最坏情况时间复杂度,因此while循环的主体在Θ(V+V*E)
中具有最坏情况时间复杂度,但V+V*E
本身是{{1} (见后面),所以while循环的主体在Θ(V*E)
中具有最坏情况的时间复杂度。 while循环执行Θ(V*E)
次。因此,执行算法的最坏情况时间是V
。
那里的简化 - 将Θ(V^2*E)
替换为Θ(V+V*E)
- 是可以接受的,因为我们正在查看Θ(V*E)
的一般情况。也就是说,V>1
总是比V*E
更大的数字,因此我们可以将V
吸收到有限常数因子中。说最差演员时间是V
也是正确的,但是由于简化形式更有用,所以不会使用它。
顺便提一下,作为一个直觉问题,你通常可以忽略容器被用完的影响&#34;在算法期间,例如插入排序具有越来越少的要查看的项目,或者该算法具有越来越少的要扫描的节点。这些影响变成了不变因素并消失了。只有当您每次都消除有趣数字元素时,例如使用快速选择算法或二进制搜索,这种事情才会开始影响渐近运行时。