衡量算法好坏的标准就是其复杂度,复杂度又分为:时间复杂度(Time Complexity)空间复杂度(Space Complexity)两种。
提高代码的效率,本质上就是降低代码的复杂度,时间宝贵,而空间相对廉价,时间是不可逆的,空间可以在开辟,所以当二者相碰撞时,我们选择用空间换取时间,首先降低时间复杂度。
一、时间复杂度
时间复杂度分析
我们知道常数项对函数的增长速度影响并不大,所以当 T(n) = c,c 为一个常数的时候,我们说这个算法的时间复杂度为 O(1);如果 T(n) 不等于一个常数项时,直接将常数项省略。
比如
printf("Hello,World\n");
printf("Hello,World\n");
T(n) = 2,所以我们说这个函数(算法)的时间复杂度为 O(1)。
T(n) = n + 29,此时时间复杂度为 O(n)。
我们知道高次项对于函数的增长速度的影响是最大的。n^3 的增长速度是远超 n^2 的,同时 n^2 的增长速度是远超 n 的。 同时因为要求的精度不高,所以我们直接忽略低项。
比如
T(n) = n^3 + n^2 + n +29,此时时间复杂度为 O(n^3)。
因为函数的阶数对函数的增长速度的影响是最显著的,所以我们忽略与最高阶相乘的常数。
比如
T(n) = 3n^3,此时时间复杂度为 O(n^3)。
综合起来:如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。
如何分析一段代码的时间复杂度?三个比较实用的方法
1、只关注循环执行次数最多的的一段代码
大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。所以,我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。这段核心代码执行次数的 n 的量级,就是整段要分析代码的时间复杂度。
{
int sum = 0;
int i ;
for (i=1; i <= n; i++) {
sum = sum + i;
}
return sum;
}
2、代码都是常量级别的执行时间,与n的大小无关,所以对于复杂度并没有影响。循环执行次数最多的是4、5行代码,所以这块代码要重点分析。那两行代码执行了n次,所以总的时间复杂度就是O(n)
加法法则:总复杂度等于量级最大的那段代码的复杂度
如果一整段代码由三小段代码构成,且三小段代码的时间复杂度(分别是O(1), O(n), O(n2)),我们取其中最大的量级。所以,整段代码的时间复杂度就为 O(n2)。也就是说:总的时间复杂度就等于量级最大的那段代码的时间复杂度。那我们将这个规律抽象成公式就是:
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).
乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
类似嵌套循环的,都是用乘法来处理
时间复杂度的计算:(就找执行次数最多的地方)
如果经历过以下代码片段,那么它的时间复杂度是多少呢?让我们依次分析
printf("A\n");
printf("A\n");
printf("A\n");
很显然,时间复杂度为o(1);
for(i=1;i<=n;i++)
{
printf("%d\n",i); //一共输出n次
}
最后时间复杂度为o(n);
for(i=1;i<=n;i++)
{
for(j=1;j<=i;j++)
{
printf("%d\n",j); //i=1,输出一次;i=2,输出两次;i=3,输出三次... ...
}
}
最后o(nx(n+1)/2),时间复杂度为o(n^2);
for(i=1;i<=n;i++)
{
for( j=1;j<=n;j*=2) //1*2*2*...=n; 2^k=n;k=log2n;
{
printf("%d\n",j); //每次循环输出log2n次,共n次循环;
}
}
时间复杂度为o(nlog2n);
for(i=1;i<=n;i++)
{
for( j=1;j<=i;j++)
{
for(k=1;k<=j;k++)
{
printf("%d\n",k);
}
}
}
其实这段代码看起来循环比较多,但是我们要透过现象看本质,这三层循环嵌套最终都作用在了输出语句上,当i=1时,输出了一次,i=2时,输出了1+2次,i=3时,输出了1+2+3次,i=4时,输出了1+2+3+4次… …,每一次的输出次数都是一次等差数列的和,则总次数为:
(妈妈!字怎么可以这么难看!)
二、空间复杂度
既然时间复杂度不是用来计算程序具体耗时的,那么我也应该明白,空间复杂度也不是用来计算程序实际占用的空间的。
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。
空间复杂度比较常用的有:O(1)、O(n)、O(n²),我们下面来看看:
空间复杂度 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
举例:
int i = 1;
int j = 2;
++i;
j++;
int k = i + j;
代码中的 i、j、k所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)
空间复杂度 O(n)
我们先看一个代码:
int[] m = new int[n]
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)
常见算法的复杂度及其稳定性