第二章 算法(下)
2.9算法的时间复杂度
判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注最高阶项的阶数。某个算法,随着n的增大,它会越来越优于另一个算法,或者越来越差于另一算法。
2.9.1 算法时间复杂度定义
算法的时间复杂度,也就是算法的时间量度,记作T(n) = O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和n的某个函数f(n)的增长率相同。
2.9.2 推导大O阶方法
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。得到的结果就是大O阶。
2.9.3 常数阶
以求和代码为例:
这个代码的运行次数函数 f(n) = 3。根据推导大O阶方法,用1取代3,无最高阶项,因此该算法的时间复杂度为O(1)。假设该算法中计算sum这一步有10句,即:
事实上,无论n为多少,上述两段代码就是3次和12次执行的差异。这种与问题规模n大小无关,执行时间恒定的算法,称之为具有O(1)的时间复杂度,又叫常数阶。不论这个常数是多少,我们都记作O(1),而不能是O(3)、O(12)等其他任何数字!
对于分支结构,无论真、假,执行次数都是恒定的,不会随n的变化而变化,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是O(1)。
2.9.4 线性阶
我们要分析算法的复杂度,关键就是分析循环结构的运行情况。
这段代码的循环的时间复杂度为O(n)。
2.9.5 对数阶
这段代码每循环一次 i * 2 之后 i 就距离 a 更近了一点,循环 x 次后直到 i 大于或者等于 a 后,循环结束。所以有 2^x = a,x = log2^n。所以这个代码的时间复杂度为O(logn)。
2.9.6 平方阶
这是一个循环嵌套,外层循环执行了 m 次,内层循环执行了 n 次,因此,时间复杂度即为O(m*n)
这个循环 i 从0开始循环,j 从 i 开始循环。当i = 0时,内循环执行了n次,i= 1时,内循环执行n-1次,.......当i= n-1 时,内循环执行1次。所以总的执行次数为n + (n-1) + (n-2) + ... +1 = n(n+1)/2 = n^2/2 +n/2。根据推导大O阶的方法,这段代码的时间复杂度为O(n^2)。
看到这,我们发现,确实理解大O阶推导并不难,难的是对数列的一些相关运算,这更多考验数学知识和能力。
下面是一个关于方法调用的时间复杂度的计算。
function()函数的时间复杂度为O(1),所以整体的时间复杂度为O(n)。
假如function()是这样
这就和上文嵌套函数是一样的,只不过把内循环放到了函数里,所以最终的时间复杂度为O(n^2)。
下面这段相对复杂的语句:
它的执行次数 1 + n + n^2 + n(n+1)/2 = (3/2)*n^2 + (3/2)*n + 1,根据大O阶的方法,最终代码的时间复杂度为O(n^2)。
2.10 常见的时间复杂度
常见的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O( n!) < O(n^n)
一般在没有特殊说明的情况下,时间复杂度都指最坏时间复杂度。