复杂度分析
-
事后统计法:把代码跑一遍,通过统计、监控,就能得到算法执行的时间和占用的内存大小
-
弊端:
- 测试结果非常依赖测试环境(不同芯片电脑运行程序)
- 测试结果受数据规模的影响很大(数据规模大和小可能测试结果不同)
-
-
解决办法:需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法
时间复杂度分析
-
概述:所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比(算法的执行时间与数据规模之间的增长关系)
- T(n) = O(f(n))
- T(n):代码执行的时间
- n:数据规模的大小
- f(n):每行代码执行的次数总和
- O:代码的执行时间 T(n) 与 f(n) 表达式成正比
- T(n) = O(f(n))
-
大 O 时间复杂度表示法:并不代表代码真正的执行时间,表示 代码执行时间与数据规模增长的变化趋势,也叫 渐进时间复杂度,简称 时间复杂度
-
时间复杂度分析方法
- 只关注循环执行次数最多的一段代码(忽略常量、低阶、系数)
- 加法法则:总复杂度等于量级最大的那段代码的复杂度
- 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
-
几种常见时间复杂度实例分析(多项式量阶 和 非多项式量级)
- 常量阶 O(1)
- 对数阶 O(logn)
- 线性阶 O(n)
- 线性对数阶 O(nlogn)
- 平方阶 O(n2) 立方阶 O(n3) … k次方阶 O(nk)
- 指数阶 O(2n)
- 阶乘阶 O(n!)
注意:当数据规模 n 越来越大时,非多项式量级算法的执行时间会急剧增加。非多项式量级只有两个:O(2n) 和 O(n!)
多项式量级时间复杂度
O(1)
- 只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
O(logn)、O(nlogn)
i = 1;
while(i <= n){i = i*2;
}
// 时间复杂度:O(logn)
注意:对数阶时间复杂度表示法,可以忽略对数的底,统一表示为 O(logn)
O(m+n)、O(m*n)
-
概述:代码的复杂度由两个数据的规模来决定
int cal(int m , int n){int sum1 = 0;int i = 1;for(;i<m;++i){sum1 = sum1 + i;}int sum2 = 0;int j = 1;for(;j<n;++j){sum2 = sum2 + j;}return sum1 + sum2; }
注意:本算法无法使用 加法法则,因为我们无法断定 m 和 n 他们两个谁大谁小,所以时间复杂度就是O(m+n)
最好、最坏情况时间复杂度
- 最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度
- 最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度
平均情况时间复杂度
- 平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度
- 什么时候使用这三种复杂度分析?
- 只有同一块代码在不同的情况下,时间复杂度有量级的差距,我们才会使用这三种复杂度表示法来区分
均摊时间复杂度
- 概述:出现多次 O(1),出现少次 O(n), O(n) 可以均摊给其他 O(1),这样就是均摊时间复杂度分析
- 使用场景:对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。
空间复杂度分析
- 概述:算法的存储空间与数据规模之间的增长关系
- 例如:
- int a = 0;
- O(1)
- int[] a = new int[n];
- O(n)
- int a = 0;
- 例如:
复杂度分析案例
// 全局变量,大小为 10 的数组 array 长度 len 下标 i
int array[] = new int[10];
int len = 10;
int i = 0;// 往数组中添加一个元素
void add(int element){// 数组空间不够if(i >= len){// 重新申请一个 2 倍大小的数组空间int new_array[] = new int[len*2];// 把原来 array 数组中的数据依次 copy 到 new_array 中for(int j = 0; j < len; ++j){new_array[j] = array[j];}array = new_array;len = 2*len;}// 将 element 放到下标为 i 的位置,下标 i 加一array[i] = element;++i;
}
最好情况时间复杂度:我们可以看到,在 i = (0~len-1) 时,时间复杂度都是 O(1)
最坏情况时间复杂度:如果在 i = len 时,时间复杂度为 O(len)
平均情况时间复杂度:总共有 n+1 种情况1*1/(n+1) + 1*1/(n+1)+...+n*1/(n+1) = 2n/(n+1)O(1)
均摊情况时间复杂度:前 n 种时间复杂度为 O(1) , 最后一种情况时间复杂度为 O(n),我们可以将 O(n) 平均分配给 前 n 种,所以时间复杂度为 O(1)