算法,作为一个programmer应该再清楚不过。一个算法的好坏是依靠什么标准 来评判的呢?那就是时间复杂度和空间复杂度。
今天就来探讨下时间复杂度,什么是时间复杂度呢?
在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函
数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的
情况。 --------百度百科
直接这么看太过晦涩难以理解,我们可以拆分成各个知识小块来理解,然后再回头看就会恍然大悟。
我们假设计算机运行一行基础代码需要执行一次运算。
int aFunc(void) {
printf("Hello, World!\n"); // 需要执行 1 次
return 0; // 需要执行 1 次
//总共是两次运算
}
int aFunc(int n) {
for(int i = 0; i<n; i++) { // 需要执行 (n + 1) 次,最后一次判断false也为一次
printf("Hello, World!\n"); // 需要执行 n * 1 次
}
return 0; // 需要执行 1 次
//总共是 n + 1 + (n * 1) + 1 = 2n + 2次运算
}
时间频度相信大家都有所认识了。但是在相同代码中,不同输入值仍可能造成算法的运行时间不同,因此我们通常使用算法的最坏情况复杂度,记为T(n),定义为任何大小的输入n所需的最大时间频度。(这是只考虑一种算法中时间频度多的那种情况)
时间频度 T(n)的几种自然特性:
线性:T(n) = 3n
void aFunc(int n){
for(int i = 0,i < n,i++){
System.out.println("hello world!");// 需要执行 1 * n 次
System.out.println("hello world!");// 需要执行 1 * n 次
System.out.println("hello world!");// 需要执行 1 * n 次
}
//总共是(1 * n) + (1 * n) + (1 * n) = 3n次运算
}
对数:T(n) = 2logn
扩充知识点:对数函数
void aFunc(int n){
for(int i = 0,i < n,i*=2){
System.out.println("hello world!");// 需要执行 logn 次
System.out.println("hello world!");// 需要执行 logn 次
}
//总共是logn + logn = 2logn次运算
}
常数:T(n) = 1
void aFunc(int n){
System.out.println("hello world!");//需要执行一次
}
//无论n是多少,程序都只执行一次
多项式:T(n) = 0.5n² + 0.5n
扩充知识点:等差数列
void aFunc(int n){
for(int i = 0,i < n,i++){
for(int y = 0,y < i,y++){
System.out.println("hello world!");//需要执行(1 + 2 + n-2 + n-1)次
}
System.out.println("hello world!");//需要执行n * 1次
}
}
//总共是(1 + 2 + n-2 + n-1) + n = n(n + 1)/2 = 0.5n² + 0.5n次运算
相信现在大家应该对时间复杂度有所理解,但是别着急,我们继续往下看。
大O表示法:算法的时间复杂度通常用大O符号表述,定义为T[n] = O(f(n))。称函数T(n)以f(n)为界或者称T(n)受限于f(n)。 如果一个问题的规模是n,
解这一问题的某一算法所需要的时间为T(n)。T(n)称为这一算法的“时间复杂度”。当输入量n逐渐加大时,时间复杂度的极限情形称为算法的“渐近时间复
杂度”。 --------百度百科
看到这是否有同学不理解了。我先举个例子:现在假设有一段代码的时间频度为T(n) = n + 2,又有某个辅助函数f(n) = n²,当n趋近于无穷大时,T(n) / f (n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),而O(f(n))则 为算法的渐进时间复杂度,简称时间复杂度。
在各种不同算法中,若算法中语句执行次数为一个常数,则时间复杂度为O(1),另外,在时间频度不相同时,时间复杂度有可能相同,如T(n) = n + 2与T(n) = n + 4它们的频度不同,但时间复杂度相同,都为O(f(n))。
在明白了什么是时间复杂度之后,我们还需要了解如何计算时间复杂度。
1、省略常数项
int aFunc(void) {
printf("Hello, World!\n");
return 0;
}
//T(n) = 2n 时间复杂度为 O(n)
2、忽略低次项
//T(n) = n² + n 时间复杂度为 O(n²)
3、只保留最高次项,同时忽略最高项的系数
void aFunc(int n){
for(int i = 0,i < n,i++){
for(int y = 0,y < i,y++){
System.out.println("hello world!");//需要执行(1 + 2 + n-2 + n-1)次
}
System.out.println("hello world!");//需要执行n * 1次
}
}
//T(n) = 0.5n² + 0.5n 时间复杂度为 O(n²)
总结:如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。无论是顺序执行的算法还是含有条件判断的算法,我们都按算法最坏情况复杂度来计算。如果涉及到函数调用,则需要把调用的函数一并按照上面的规则加进去来计算时间复杂度。
大家可以自己尝试着来算一算自己写的算法的时间复杂度是多少,从而进一步的加深对时间复杂度的理解,也能有目的性的去设计自己的算法。
参考链接:
(数据结构)十分钟搞定时间复杂度(算法的时间复杂度)
一套图 搞懂“时间复杂度”
时间复杂度
大O表示法