1.考考你
在开篇中提到了复杂度分析,与大O表示法的概念。具体要如何进行复杂度分析,以及大O表示法的公式推导,我们在这一篇详细来看。
#考考你:
1.你知道大O表示法,公式是如何来的吗?
2.你知道时间复杂度分析的常用原则吗?
3.你知道常见复杂度的度量级吗?
2.案例
2.1.大O表示法公式推导
2.1.1.案例代码
我们根据以下案例代码推导大O表示法的公式。代码很简单,有没有?
// 传入参数n,求解1..n的累加和
public int sum(int n){
int sum = 0;
for(int i = 1; i <= n; i++){
sum += i;
}
return sum;
}
2.1.2.推导过程
简述:
1.对于程序代码中的每一行代码,从cpu的角度来看,执行的时候都有:读数据->运算->写数据过程
2.我们假定每一行代码的执行时间都相同,都是一个单位时间:unit_time
3.那么sum方法中,代码执行的总时间是多少呢
1 public int sum(int n){
2 int sum = 0;// 第二行代码执行,需要1 个unit_time
3 for(int i = 1; i <= n; i++){// 第三行代码执行,需要n 个unit_time
4 sum += i;// 第四行代码执行,需要n 个unit_time
5 }
6 return sum;// 第五行、第六行代码暂时忽略,不影响
7 }
4.根据3推导,sum方法的总执行时间是:
T(n)=(n + n + 1) * unit_time = (2n +1) * unit_time=O(f(n))
5.结论:所有代码的执行时间T(n) ,与每行代码的执行次数成正比
6.提取出公式即:T(n) = O(f(n))
#公式解读:
T(n):代表代码执行时间
n:代表数据规模
f(n):代表每行代码执行的次数总和
O:表示代码执行时间T(n),与代码执行次数f(n)成正比
7.这就是大O表示法的公式来源,表示代码的执行时间T(n),与代码的执行次数f(n)成正比
8.进一步理解:
1.大O表示法:时间复杂度,表示数据规模n的增长,与算法执行时间的增长趋势
2.大O表示法:空间复杂度,表示数据规模n的增长,与算法存储空间的增长趋势
2.1.3.约定
大O表示的公式,以及含义我们已经推导出来了。它表示的是数据规模n的增长,与算法执行时间,或者存储空间的增长趋势。这里需要关注两个字:趋势。
根据常理我们知道,常量、系数、低阶不会影响趋势,因此在实际复杂度分析中,往往忽略常量、系数、低阶。
那么上面案例的时间复杂度大O表示法公式,省略系数、常量:
可以从:T(n) = O(f(n)) = O(2n+1)
简化成:T(n) = O(n)
2.2.时间复杂度案例
在复杂度分析中,有时间复杂度分析和空间复杂度分析。它们是从两个维度来衡量算法的优劣。实际分析方式类似,我们以时间复杂度分析为例。
时间复杂度分析,有几个基本的原则,你都知道吗?
#时间复杂度分析基本原则
1.只关注循环次数最多的代码
2.加法法则:总复杂度等于量级最大的那段代码的复杂度
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
2.2.1.只关注循环次数最多的代码
案例代码:
还是上面的案例代码,最简单的代码,也是最能说清楚问题的代码。
1 public int sum(int n){
2 int sum = 0;// 第二行代码执行,需要1 个unit_time
3 for(int i = 1; i <= n; i++){// 第三行代码执行,需要n 个unit_time
4 sum += i;// 第四行代码执行,需要n 个unit_time
5 }
6 return sum;// 第五行、第六行代码暂时忽略,不影响
7 }
复杂度分析:
1.这里的原则:只关注循环次数最多的代码
2.第二行代码执行,需要1 个unit_time
3.第三行代码执行,需要n 个unit_time
4.第四行代码执行,需要n 个unit_time
5.第五行、第六行代码暂时忽略,不影响
6.通过以上分析,第三行、第四行代码循环执行次数最多:n。因此时间复杂度为:O(n)
2.2.2.加法法则
简述:
加法法则:总复杂度等于量级最大的那段代码的复杂度
案例代码:
public int sum(int n){
// 第一段代码
int sum_1 = 0;
for(int i=1; i< 100; i++){
sum_1 += i;
}
// 第二段代码
int sum_2 = 0;
for(int j = 1; j <= n; j++){
sum_2 += j;
}
// 第三段代码
int sum_3 = 0;
for(int k = 1; k <= n;k++){
for(int h = 1; h <= n; h++){
sum_3 += k * h
}
}
return sum_1 + sum_2 + sum_3;
}
复杂度分析:
1.在sum方法中有三段代码
2.第一段代码,复杂度是:O(100)
3.第二段代码,复杂度是:O(n)
4.第三段代码,复杂度是:O(n^2)
5.总复杂度是:O(100) + O(n) +O(n^2)
6.根据加法法则,最终复杂度是:O(n^2)
2.2.3.乘法法则
简述:
乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
案例代码:
public int sum(int n){
int sum_1 = 0;
for(int i=1; i< n; i++){// 第一层循环
sum_1 += multi(i) ;// multi方法中,有第二层循环
}
}
public int multi(int n){
int multi_1 = 0;
for(int i = 1; i<= n; i++){// 第二层循环
multi_1 *= i;
}
return multi_1;
}
复杂度分析:
1.在sum方法中,有第一层循环:for(int i=1; i< n; i++){
2.在sum方法中,调用multi方法
3.在multi方法中,有第二层循环:for(int i = 1; i<= n; i++){
4.根据乘法法则,总时间复杂度等于,第一层循环,乘以第二层循环
5.因此总时间复杂度是:O(n*n) = O(n^2)
3.讨论分享
#考考你答案:
1.你知道大O表示法,公式是如何来的吗?
1.1.参考【2.1.大O表示法公式推导】
2.你知道时间复杂度分析的常用原则吗?
2.1.只关注循环次数最多的代码
2.2.加法法则:总复杂度等于量级最大的那段代码的复杂度
2.3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
3.你知道常见复杂度的度量级吗?
3.1.常数阶:O(1)
2.2.对数阶:O(logn)
2.3.线性阶:O(n)
2.4.线性对数阶:O(nlogn)
2.5.平方阶:O(n^2)
2.6.立方阶:O(n^3)