为什么要进行复杂度分析?
有人会想,平时一个功能都会有相应的性能测试,跑一遍不就好了吗,为什么还要去分析呢?
首先,不同的设备配置如i3和i7处理器,同一个功能跑一遍,耗时肯定是不一样的,所以测试环境会影响判断。
再者,数据量的大小也会影响对功能性能的判断,极端的比如同步10000条数据和同步10000w条数据,性能波动也会影响判断。
在这种情况下,使用复杂度分析,至少能粗略计算不同方案的执行效率,进行对比和选择。
大O复杂度
算法的执行效率,容易理解点说,就是在相同数量情况下,代码块执行的时间。比如:
int cl(int n){
int m = 0 ;
for(int i = 0 ; i < n ; i ++){
m = m + 1;
}
return m;
}
这段代码按系统的角度进行了读取 、计算 、 赋值。假设每行执行的时间是一个单元u,第二行为u,第3和4行分别执行了n*u次
那么所有代码执行总时间 u + 2*n*u,那么这里得出了一个公式就是,所有代码的执行时间T(n)和每行代码的执行次数n成正比。
T(n) = O(f(n))
那么T(n)表示代码执行时间,n表示数据规模,f(n)表示每行代码执行次数总和。O表示T(n)和f(n)成正比。
那么上边的例子可以:
T(n) = O(1 + 2n)
这种就是大O复杂度表示。
需要搞清楚一点:大O时间复杂度实际上并不表示具体执行时间,它只表示代码执行时间随数据规模变化的一个趋势,简称时间复杂度。
当n非常大,那么公式中的低阶、常量、系数并不能改变这个变化的趋势,所有上边例子的时间复杂度又可以表示成:
T(n) = O(n)
再来一个:
int cl(int n){
int sum = 0 ;
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < n; j++){
sum = sum + 1;
}
}
return sum;
}
可以看到,第二行为1 , 第三行为n ,第四行为 n * n ,第五行为 n * n
T(n) = O(1 + n + 2 * n^2)
进化 : T(n) = O(n^2)
时间复杂度几种分析
只关注循环次数多的
前边讲过,通常忽略掉低阶、常量、系数,只关注最大阶的量级即可,所以能造成循环次数多的那一段即可反应问题。
int cl(int n){
int m = 0 ;
for(int i = 0 ; i < n ; i ++){
m = m + 1;
}
return m;
}
比如第二行执行1,第三行、四行为n,那么时间复杂度为n。
加法法则
其实和上边的差不多,即总的复杂度为量级最大的那个。
int cl(int n){
int m = 0 ;
for(int i = 0 ; i < 1000 ; i ++){
m = m + 1;
}
for(int i = 0 ; i < n ; i ++){
m = m + 1;
}
for(int i = 0 ; i < 2n ; i ++){
m = m + 1;
}
return m;
}
上边可以看到 一个是1000,一个是n,一个是2n,那么 最大次数 为 2n,去掉系数,最大量级还是n。
T(n) = O(1000 + n + 2n)
进化 : T(n) = O(n)
乘法法则
嵌套的代码时间复杂度等于嵌套内外时间复杂度的乘积
int cl(int n){
int sum = 0 ;
for(int i = 0 ; i < n ; i ++){
sum = sum + incal(n);
}
return sum;
}
int incal(int n){
int tmp = 0;
for(int i = 0 ; i < n ; i ++){
tmp = tmp + 1;
}
return tmp;
}
T(n) = Tcal(n) * Tincal(n) = O(n*n) = O(n^2)
几种常见时间复杂表示
常量阶 O(1)
对数阶 O(logn)
线性阶 O(n)
线性对数阶 O(nlogn)
k方阶 O(n^k)
指数阶 O(2^n)
阶乘阶 O(n!)
对上边的可分为 多项式量阶 和 非多项式量阶(指数阶 O(2^n) 阶乘阶 O(n!))
非多项式的成为NP (No - Deterministic polynomial),数据量越大,执行时间急剧上升。
int a = 0 ;
int b = 1;
int c = a * b;
复杂度: O(1)
对于对数阶
int i = 1;
while(i <= n){
i = i * 2;
}
第三行执行了多少次呢?
从代码中看到,变量从1开始,每次循环乘以2 ,那么可以表示 2^n = x,那么x = log2n所以时间复杂度为 log2n
int i = 1;
while(i <= n){
i = i * 5;
}
上边的时间复杂度为log5n
基于前边的理论 采用大O标记复杂度时,忽略系数,所以,上面的复杂度都是O(logn)
int cl(int n,int m){
int sum = 0 ;
for(int i = 0 ; i < m ; i ++){
sum = sum + 1;
}
for(int i = 0 ; i < n ; i ++){
sum = sum + 1;
}
return sum;
}
上边的复杂度为 O(m + n) ,为什么不符合加法法则呢,因为m 和 n 都是不确定量,这种情况不能简单的采用加法法则去忽略任何一个。
空间发杂度分析
时间复杂度分析的是算法的执行时间与数据规模之间的增长关系,而空间复杂度指的是算法的存储空间与数据规模之间的增长关系。
void ts(int n){
int i = 0 ;
int [] a = new int[n];
for (;i < n ; i ++){
a[i] = i*i;
}
}
第二行 i赋值 申请了一个存储变量空间,但是它是常量阶的,第三行 申请了n个存储变量空间,n阶。所以这个空间复杂度为n
常见的空间复杂度
O(1)
O(n)
O(n^2)