假设我有两种算法:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
//do something in constant time
}
}
这自然是O(n^2)
。假设我也有:
for (int i = 0; i < 100; i++) {
for (int j = 0; j < n; j++) {
//do something in constant time
}
}
这是O(n) + O(n) + O(n) + O(n) + ... O(n) + = O(n)
似乎即使我的第二个算法是O(n)
,也需要更长的时间。有人可以扩展吗?我提出它是因为我经常看到算法,例如,他们将首先执行排序步骤或类似的事情,并且在确定总复杂度时,它只是限制算法的最复杂元素。
答案 0 :(得分:98)
渐近复杂度(大O和大Theta所代表的)完全忽略了所涉及的常数因素 - 它仅用于指示随着输入的大小变大,运行时间将如何变化。
对于某些给定的Θ(n)
,Θ(n2)
算法肯定可能需要比n
算法更长的时间 - n
这将会发生真正取决于涉及的算法 - 对于您的具体示例,n < 100
就是这种情况,忽略了两者之间不同的优化可能性。
对于分别花费Θ(n)
和Θ(n2)
时间的任何两个给定算法,您可能会看到的是:
Θ(n)
较小时,n
算法较慢,Θ(n2)
一个因n
增加而变慢
(如果Θ(n)
更复杂,即具有更高的常数因子),则会发生这种情况,或Θ(n2)
总是慢一些。尽管可能 Θ(n)
算法可能更慢,但Θ(n2)
一个,然后再Θ(n)
一个,依此类推{ {1}}增加,直到n
变得非常大,从那时起n
一个总是会变慢,尽管它不太可能发生。
假设Θ(n2)
算法对某些Θ(n2)
进行cn2
次操作。
c
算法对某些Θ(n)
进行dn
次操作。
这与the formal definition一致,因为我们可以假设这适用于大于0的d
(即对于所有n
)以及运行时间所在的两个函数是一样的。
根据您的示例,如果您要说n
和c = 1
,那么d = 100
算法会慢到Θ(n)
,此时n = 100
Θ(n2)
1}}算法会变慢。
(由WolframAlpha提供)。
技术上,big-O只是一个上限,这意味着您可以说O(1)
算法(或者实际上任何算法花费O(n2)
或更少的时间)也需要O(n2)
。因此我改为使用big-Theta(Θ)符号,这只是一个紧密的约束。有关详细信息,请参阅the formal definitions。
Big-O经常被非正式地视为或被教导成为一个紧张的界限,所以你可能已经基本上使用了big-Theta而不知道它。
如果我们只谈论一个上限(根据big-O的正式定义),那就更像是“任何事情” - O(n)
一个可以更快,{{ 1}}一个可以更快或者它们可以花费相同的时间(渐近) - 关于比较算法的大O,通常无法得出特别有意义的结论,一个只能比如说,给定一些算法的大O,该算法不会花费那么多的时间(渐近)。
答案 1 :(得分:19)
是,O(n)算法在运行时间方面可以超过O(n 2 )算法。这个发生在常数因子(我们在大O表示法中省略)很大时。例如,在上面的代码中,O(n)算法将具有很大的常数因子。因此,它将比在O(n 2 )中运行的算法表现更差,因为n 这里,n = 100是交叉点。因此,当任务可以在O(n)和O(n 2 )中执行时线性算法的常数因子多于二次算法,因此我们倾向于选择运算时间较差的算法。
例如,在对数组进行排序时,我们会切换到较小数组的插入排序,即使合并排序或快速排序运行渐进式更快。这是因为插入排序的常数因子比合并/快速排序小,并且运行速度更快。
答案 2 :(得分:17)
大O(n)
并不意味着比较不同算法的相对速度。它们用于测量当输入大小增加时运行时间增加的速度。例如,
O(n)
表示如果n
乘以1000,则运行时间大致乘以1000. O(n^2)
表示如果n
乘以1000,则运行大致乘以1000000。因此,当n
足够大时,任何O(n)
算法都会超过O(n^2)
算法。它并不代表任何固定n
的内容。
答案 3 :(得分:5)
长话短说,是的,它可以。 O
的定义基于O(f(x)) < O(g(x))
暗示g(x)
明确需要花费更多时间才能运行f(x)
给定足够大的x
。< / p>
例如,一个众所周知的事实是,对于小值,合并排序优于插入排序(如果我没记错的话,那应该适用于小于n
的{{1}}
答案 4 :(得分:3)
是。 O()仅表示渐近复杂度。如果线性算法具有相同的足够大的线性减速常数(例如,如果循环的核心运行时间长10倍,那么它的二次型会慢一些),线性算法可以像二次曲线一样慢。
O() - 符号只是一个推断,虽然是一个非常好的。
答案 5 :(得分:3)
唯一的保证就是 - 无论常数因素 - 足够大n
,O(n)算法将花费的操作少于O(n ^ 2)一个。
作为一个例子,让我们计算OPs整洁的例子中的操作。 他的两个算法只有一行不同:
for (int i = 0; i < n; i++) { (* A, the O(n*n) algorithm. *)
VS。
for (int i = 0; i < 100; i++) { (* B, the O(n) algorithm. *)
由于他的其他程序是相同的,实际运行时间的差异将由这两行决定。
当然,要使O(n)算法更快,n的大小取决于常数因子。如果您将B中的常量100
更改为1000
,则截止值也会更改为1000.