在初学数据结构与算法的时候,复杂度分析的学习被我选择性忽略了,刷算法题看题解时也是半知半解,也让我在学习数据结构与算法的时候走了不少弯路。在最近迫于实习就业的压力下,我也决定要把这块难啃的骨头啃下。
为什么要进行复杂度分析
常常有这样的疑问,对于一段代码执行的时间和占用的空间大小,通过测试完全可以实现监控,为什么还要做复杂度的分析呢?原因如下。
测试结果受到多个条件限制
对于同一段代码,在不同性能的计算机上的运行时间会截然不同。
同一段代码在同一台机器上,在遇到不同的数据也可能会有截然不同的运行速度。以排序算法为例,在有序和无序的前提条件下运行时间是不同的,因为所要进行操作的次数不同。
所以说,我们需要一个不需要依赖具体测试就能对代码的运行时间有个直观判断的方法,这就是复杂度分析。
渐进时间复杂度
渐进时间复杂度也称时间复杂度,它表示的是代码的执行时间随着数据规模增长的变化程度,注意,它指的不是代码具体的执行时间。时间复杂度通过大O复杂度表示法来表示,下面通过两段代码来表示。
public int cal(int n){
int sum = 0;
for(int i = 0;i < n;i++){
sum+=i;//求和
}
return sum;
}
在上面这段代码中,每一行代码对应的执行时间实际上应该都是不一样的,但是我们是粗略估计,我们假设每一行代码的执行时间都是一样的为unit_time,考虑每行代码的执行次数,在这个假设下代码的执行时间应为(2n+2)*unit_time。
public int cal(int n){
int sum=0;
for(int i=0;i < n;i++){
for(int j=0;j < n;j++){
sum = sum + i * j;
}
}
return sum;
}
再来看一下这段代码,根据上面的分析方法,这段代码的总执行次数应该为2n²+n+2,执行时间为(2n²+n+2)*unit_time。
通过这两段代码我们可以知道,代码的执行时间应该与代码的总执行次数成正比。总结为公式就是:
T(n)=O(f(n))
其中T(n)表示代码的执行时间,n表示数据规模的大小,f(n)表示代码的执行次数总和,O表示代码的执行时间T(n)与f(n)成正比。这就是大O复杂度表示法,用于表示时间复杂度。
时间复杂度分析的方法
只关注循环执行次数最多的一段代码
我们在进行时间复杂度分析的时候,通常分析的是一种变化的趋势,往往会忽略常量、低阶和系数的影响,只记录一个大的高阶量级。
public int cal(int n){
int sum = 0;
for(int i = 0;i < n;i++){
sum+=i;//求和
}
return sum;
}
比如这段代码,我们只需要关心执行循环n次的那两行代码,所以取时间复杂度为O(n)。
加法法则
public int cal(int n, int m){
int sum_1 = 0;
for(int i = 0;i < n;i++){
sum_1+=i;//求和
}
int sum_2 = 0;
for(int i = 0;i < m;i++){
sum_2+=i;//求和
}
return sum_1+sum_2;
}
对于这段代码,由于我们无法评估m和n两个数据规模哪个大,所以我们采用加法法则T1(n)+T2(m)=O(f(n)+f(m))。
可知此段代码时间复杂度为O(n+m)。
乘法法则
public int cal(int n){
int sum = 0;
for(int i = 0;i < n;i++){
sum = sum + f(i);
}
return sum;
}
public int f(int n){
int sum = 0;
for(int i = 0;i < n;i++){
sum = sum + i;
}
return sum;
}
我们可以发现这是一组嵌套的代码,cal函数在循环中调用了f函数,这时候我们就需要用到乘法法则,cal函数的时间复杂度就是O(n²)。
复杂度分析可以说是我们学习数据结构与算法的精髓,也是我们进行算法分析的敲门砖,本blog是我学习王争老师的《数据结构与算法之美》做的学习笔记,发表之后希望能与大家互相探讨,共同领略算法之美。