我最近了解了算法的正式Big-O分析;但是,我不明白为什么这两个几乎完全相同的算法会有非常不同的运行时间。算法都将数字0打印到n。我将用伪代码编写它们:
Algorithm 1:
def countUp(int n){
for(int i = 0; i <= n; i++){
print(n);
}
}
Algorithm 2:
def countUp2(int n){
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
... (continued so that this can print out all values 0 - Integer.MAX_VALUE)
for(int z = 0; z < 10; z++){
print("" + i + j + ... + k);
if(("" + i + j + k).stringToInt() == n){
quit();
}
}
}
}
}
因此,第一个算法在O(n)
时运行,而第二个算法(取决于编程语言)在接近O(n^10)
的情况下运行。是否有任何代码导致这种情况发生,或者仅仅是我的例子的荒谬,&#34;打破&#34;算术?
答案 0 :(得分:2)
在countUp
中,循环命中[0,n] 范围内的所有数字,从而导致运行时间为O(n)。
在countUp2
中,你做了一些完全相同的事情,很多次。所有循环的边界都是10.
假设您有3个循环运行,其边界为10.因此,外循环执行10
,内部执行10x10
,最内部执行10x10x10
。所以,最坏的情况是你的最内层循环将运行1000次,这基本上是恒定的时间。因此,对于带有边界[0,10]的n
循环,您的运行时间为10 ^ n,同样可以称为常量时间O(1),因为它不依赖于n
最坏情况分析。
假设您可以编写足够的循环并且n
的大小不是一个因素,那么您需要为n的每个数字创建一个循环。 n
中的位数为int(math.floor(math.log10(n))) + 1
;我们称之为dig
。因此,对迭代次数的更严格的上限将是10 ^ dig(可以有点简化为O(n);证据留给读者作为练习)。
答案 1 :(得分:0)
在分析算法的运行时时,要寻找的一个关键因素是循环。在算法1中,您有执行n次的代码,使运行时为O(n)。在算法2中,您有嵌套循环,每个循环运行10次,因此您的运行时间为O(10 ^ 3)。这是因为对于中间循环的每次运行,您的代码为最内层循环运行10次,而对于最外层循环的每次运行,该循环又运行10次。所以代码运行10x10x10次。 (但这纯粹是一个上限,因为你的if语句可能在循环完成之前结束算法,具体取决于n的值。
答案 2 :(得分:0)
要在n
中计算最多countUp2
,那么您需要与n
中的位数相同的循环次数:所以log(n)
循环。每个循环可以运行10
次,因此迭代总数为10^log(n)
,即O(n)
。
答案 3 :(得分:0)
第一次在O(n log n)时间运行,因为print(n)
输出O(log n)数字。
第二个程序假定n的上限,因此通常为O(1)。当我们进行复杂性分析时,我们假设一个更抽象的编程语言版本,其中(通常)整数是无界的,但算术运算仍然在O(1)中执行。在你的例子中,你将实际的编程语言(有界的整数)与这个更抽象的模型混合在一起(不是没有)。如果你重写了程序[*],那么根据n有一个动态可调的循环次数(所以如果你的数字n有k个数字,那么那里有k + 1个嵌套循环),那么它会进行一次迭代n之后的每个数字的最内码,从0到10的下一个幂。内部循环在构造字符串时执行O(log n)工作[**],因此整个程序也是O(n log n)。
[*]你不能用于循环和变量来做到这一点;你必须使用递归或类似的东西,以及数组而不是变量i,j,k,...,z。
[**]假设您的编程语言优化了k长度为1的字符串的添加,以便它在O(k)时间内运行。显而易见的字符串连接实现将是O(k ^ 2)时间,这意味着您的第二个程序将在O(n(log n)^ 2)时间内运行。