算法时间复杂度空间复杂度总结
一、算法
-
什么是算法,什么是程序?
算法是为了解决某类问题而规定的一个有限长的操作序列。
程序就是算法+数据,不然只能叫做代码 -
一个算法必须满足以下五个重要特性
- 有穷性。一个算法必须总是在执行有穷步后结束,且每一步都必须在有穷时间内完成。
- 确定性。对于每种情况下所应执行的操作,在算法中都有确切的规定,不会产生二义性,使算法的执行者或阅读者都能明确其含义及如何执行。
- 可行性。算法中的所有操作都可以通过已经实现的基本操作运算执行有限次来实现。
- 输入。一个算法有零个或多个输入。当用函数描述算法时,输入往往是通过形参表示的,在它们被调用时,从主调函数获得输入值。
- 输出。一个算法有一个或多个输出,它们是算法进行信息加工后得到的结果,无输出的算法没有任何意义
-
一个算法的优劣应该从以下几方面来评价
- 正确性。在合理的数据输入下,能够在有限的运行时间内得到正确的结果。
- 可读性。一个好的算法,首先应便于人们理解和相互交流,其次才是机器可执行性。可读性强的算法有助于人们对算法的理解,而难懂的算法易于隐藏错误,且难于调试和修改。
- 健壮性。当输入的数据非法时,好的算法能适当地做出正确反应或进行相应处理,而不会产生一些莫名其妙的输出结果。
- 高效性。高效性包括时间和空间两个方面。时间高效是指算法设计合理,执行效率高,可以用时间复杂度来度量;空间高效是指算法占用存储容量合理,可以用空间复杂度来度量。时间复杂度和空间复杂度是衡量算法的两个主要指标。
二、时间复杂度
-
算法效率分析的目的是看算法实际是否可行,并在同一问题存在多个算法时,可进行时间和空间性能上的比较,以便从中挑选出较优算法。
-
衡量算法效率的方法主要有两类:事后统计法和事前分析估算法。事后统计法需要先将算法实现,然后测算其时间和空间开销。这种方法有两大缺陷:
- 一是必须把算法转换成可执行的程序。
- 二是时空开销的测算结果依赖于计算机的软硬件等环境因素,这容易掩盖算法本身的优劣。
-
所以我们通常采用事前分析估算法,通过计算算法的渐近复杂度来衡量算法的效率。
-
我们要搞懂算法的时间复杂度,还需要了解问题规模和语句频度。
-
不考虑计算机的软硬件等环境因素,影响算法时间代价的最主要因素是问题规模。问题规模是算法求解问题输入量的多少,是问题大小的本质表示,一般用整数n表示,问题规模n对不同的问题含义不同,例如:
- 一个数组存放n个数据元素,遍历这个数组。
- 对树的遍历,n为树的节点数。
- 在排序运算中,n为排序的记录数。
所以n越大,算法执行的时间越长。也就是问题规模越大,时间复杂度越大。
-
一条语句的重复执行次数称作语句频度。
-
一个算法的执行时间大致上等于其所有语句执行时间的总和,而语句的执行时间则为该条语句的重复执行次数和执行一次所需时间的乘积。
-
也可以简单的说算法所有语句的频度之和记作T(n),T(n)是问题规模n的函数,时间复杂度主要是为了分析函数T(n)的数量级。
-
为什么说时间复杂度是为了分析函数T(n)的数量级,因为语句的执行要由源程序经编译程序翻译成目标代码,目标代码经装配再执行,因此语句执行一次实际所需的具体时间是与机器的软、硬件环境密切相关的,并且算法中的基本运算(通常是程序的最深处循环的语句)的执行频度与T(n)是一个数量级,所以一般采用算法中的基本运算的频度f(n)来分析时间复杂度。
一般记作T(n)=O(f(n))
举个例子:下面代码不用关注什么意思,只用关注所有语句执行次数(实际是求两个n阶矩阵的乘积)
//第一个for循环频度为n+1 for(i=1;i<=n;i++) { //第二for执行n*(n+1) for(j=1;j<=n;j++){ //执行n*n次 c[i][j]=0; //执行n*n*(n+1) for(k=1;k<=n;k++){ //执行n*n*n c[i][j]=c[i][j]+a[i] } } }
所以上面这个例子的所有语句的频度之和为f(n),
f ( n ) = 2 n 3 + 3 n 2 + 2 n + 1 f(n)=2n^3+3n^2+2n+1 f(n)=2n3+3n2+2n+1
对于这种比较好分析的算法,可以轻松计算出所有语句频度之和,但是一旦复杂了,就不好分析了,为了客观地反映一个算法的执行时间,可以只用算法中的“基本语句”的执行次数来度量算法的工作量 ,所谓“基本语句”指的是算法中重复执行次数和算法的执行时间成正比的语句,它对算法运行时间的贡献最大。所以取算法的时间复杂度为T(n)=O(f(n)),这个O()其实是数学中的佩亚诺余数,是一种极限的思维,例如上面的例子,对所有语句频度之和f(n)对 n 3 n^3 n3取极限,,极限为2,所以当 n → ∞ n \to \infty n→∞ 时,也就是问题规模无限大的时候,f(n)和 n 3 n^3 n3等价的,所以一般用O( n 3 n^3 n3)表示上面例子的时间复杂度。
lim n → ∞ f ( n ) n 3 = lim n → ∞ ( 2 n 3 + 3 n 2 + 2 n + 1 ) n 3 = 2 \lim_{n \to \infty} \frac{f(n)}{n^3} = \lim_{n \to \infty} \frac{(2n^3+3n^2+2n+1)}{n^3}=2 n→∞limn3f(n)=n→∞limn3(2n3+3n2+2n+1)=2 -
-
最好、最坏和平均时间复杂度
- 最坏时间复杂度是指在最坏情况下,算法的时间复杂度。
- 最好时间复杂度是指在最好情况下,算法的时间复杂度。
- 平均复杂度,是指在所有可能的输入出现的概率是相等的情况下,算法期望运行的时间。
- 我们一般考虑算法的时间复杂度,是指最坏时间复杂度
-
时间复杂度运算规则
- 加法规则
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n)=T_1(n)+T_2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n))) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n))) - 乘法规则
T ( n ) = T 1 ( n ) ∗ T 2 ( n ) = O ( f ( n ) ) ∗ O ( g ( n ) ) = O ( f ( n ) ∗ g ( n ) ) T(n)=T_1(n)*T_2(n)=O(f(n))*O(g(n))=O(f(n)*g(n)) T(n)=T1(n)∗T2(n)=O(f(n))∗O(g(n))=O(f(n)∗g(n))
- 加法规则
-
时间复杂度排序
O ( 1 ) < O ( l o g 2 n ) < O ( n ) < O ( n l o g 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)<O(log2^n)<O(n)<O(nlog2^n)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n) O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
三、空间复杂度
-
空间复杂度
一个程序在机器上执行时,除了需要寄存本身所用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的辅助存储空间。其中,对于输入数据所占的具体存储量取决于问题本身,与算法无关,这样只需分析该算法在实现时所需要的辅助空间就可以了,所以空间复杂度就是算法在运算时所需辅助空间的规模。 -
原地工作
若算法执行时所需要的辅助空间相对于输入数据量而言是个常数,则称这个算法为原地工作,辅助空间为O(1)
例如:for(i=0;i<n;i++){ t=a[i]; a[i]=a[n-i-1]; a[n-i-1]=t; }
辅助空间为n的,如下:
for(i=0;i<n;i++){
b[i]=a[n-i-1];
}
for(i=0;i<n;i++){
a[i]=b[i]
}
因为借助一个长度为n的b数组,作为中间桥梁,来实现a数组的逆序,所有辅助空间为n,当 n → ∞ n \to \infty n→∞,所需要的辅助空间为O(n)。一般现在计算机内存都比较充足,ssd速度也比较快,所以在考虑算法的优劣,一般只考虑算法的时间复杂度。
四、计算时间复杂度例子
-
下面算法的时间复杂度是多少
void fun(int n){ int i=1; while(i<=n) i=i*2; }
A.O(n) B.O( n 2 n^2 n2) C.O( n log 2 n n\log2^n nlog2n) D.O( log 2 n \log2^n log2n)
-
求下面算法的时间复杂度
x=2 while(x<n/2){ x=2*x }
-
求下面的算法的时间复杂度
int fact(n){ if(n<=1) return 1; return fact(n-1); }
-
求下面的算法的时间复杂度
c=0; for(i=1;i<=n;i*=2) for(j=1;j<=n;j++) c++;