前言:为什么会出现时间复杂度这个概?
我们在编写代码的时候为了完成一个函数功能,可能用到不同的处理方式:顺序多次执行、使用for循环执行、使用递归调用等等。当循环次数取值范围不同时,各种算法处理所耗费的时间也不一样,不同的算法所执行的次数通过分析可以用数学函数表达。
时间复杂度概念:我们把 算法需要执行的运算次数 用 输入大小n 的函数 表示,即 T(n) 。此时为了 估算算法需要的运行时间 和 简化算法分析,我们引入时间复杂度的概念。
时间复杂度定义:存在常数 c 和函数 f(N),使得当 N >= c 时 T(N) <= f(N),表示为 T(n) = O(f(n)) 。
实际应用中带来的优势
如下图:假设现在有两个算法均可以实现一个功能,其运算次数n的函数表达分别为T(n) = n + 2与T(n) = n^2。可以这样理解,同一个功能,一个人只用了一层for循环执行N次,再多运行两次;另一个人用了for循环嵌套for完成,所以执行次数为n^2。
当 N >= 2 的时候,f(n) = n^2 总是大于 T(n) = n + 2 的,于是我们说 f(n) 的增长速度是大于或者等于 T(n) 的,也说 f(n) 是 T(n) 的上界,可以表示为 T(n) = O(f(n))。
因为f(n) 的增长速度是大于或者等于 T(n) 的,即T(n) = O(f(n)),所以我们可以用 f(n) 的增长速度来度量 T(n) 的增长速度,所以我们说这个算法的时间复杂度是 O(f(n))。
算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 输入大小n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。
总结:通过对算法进行数学函数分析,可以得到N在某个范围内,使用适合的算法,而N的值使我们在函数传参时,写入的,所以我们事先设计2套算法,当N<2时用算法1,N>时用算法2,这样提高的运行效率。
实际代码中常出现的函数表达式
场景1:T(n) = 3n,执行次数是线性的。
该函数只有一层for循环,且次数i线性增长,没有嵌套循环语句,所以是线性函数(这里执行一次打印函数为一个循环单位,for包含3个,所以为 3n)
void aFunc(int n) {
for(int i = 0; i < n; i++) { // 循环次数为 n
printf("Hello, World!\n"); // 循环体时间复杂度为 O(1)
}
}
场景2:T(n) = logn,执行次数是对数。
该函数变量i指数增长,<n,两边去对数,执行次数i =
,即 T(n) =
,可见时间复杂度为 O(
),即表示对数函数表达式,统一称为 O(log n),不用带上底数2。
void eat2(int n){
for(int i=1; i<n; i*=2){
System.out.println("等待一天");
System.out.println("等待一天");
System.out.println("等待一天");
System.out.println("等待一天");
System.out.println("吃一半面包");
}
场景4:T(n) = n^2 / 2 + n / 2,执行次数是一个多项式。
当 i = 0 时,内循环执行 n 次运算,当 i = 1 时,内循环执行 n - 1 次运算……当 i = n - 1 时,内循环执行 1 次运算。
所以,执行次数 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。根据上文说的 大O推导法 可以知道,此时时间复杂度为 O(n^2)。
void aFunc(int n) {
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
printf("Hello World\n");
}
}
}
场景5:T(n) = T(n - 1) + T(n - 2),递归调用。
当T(0) = T(1) = 1,同时 T(n) = T(n - 1) + T(n - 2) + 1,这里的 1 是其中的加法算一次执行。
当 T(n) = T(n - 1) + T(n - 2) 是一个斐波那契数列,通过归纳证明法可以证明,当 n >= 1 时 T(n) < (5/3)^n,同时当 n > 4 时 T(n) >= (3/2)^n。
所以该方法的时间复杂度可以表示为 O((5/3)^n),简化后为 O(2^n)。
long aFunc(int n) {
if (n <= 1) {
return 1;
} else {
return aFunc(n - 1) + aFunc(n - 2);
}
}
最后举过一个例子:
算法A的相对时间规模是T(n)= 100n,时间复杂度是O(n)
算法B的相对时间规模是T(n)= 5n^2,时间复杂度是O(n^2)
算法A运行在小灰家里的老旧电脑上,算法B运行在某台超级计算机上,运行速度是老旧电脑的100倍。
那么,随着输入规模 n 的增长,两种算法谁运行更快呢?
从表格中可以看出,当n的值很小的时候,算法A的运行用时要远大于算法B;当n的值达到1000左右,算法A和算法B的运行时间已经接近;当n的值越来越大,达到十万、百万时,算法A的优势开始显现,算法B则越来越慢,差距越来越明显。