复杂度
- 快(代码执行效率),省(代码占用更少的存储空间),是数据结构和算法要解决的问题。
- 时间复杂度是算法执行的时间,空间复杂度是算法占用的内存的内存大小。
大 O 复杂度表示法
1: 算法的执行效率,初略的讲就是代码的执行时间。分析的时候,假设每行代码执行一次命令的时间都是一样的(unit_time),用代码执行的总次数乘以unit_time,即为代码执行的时间。可以得出结论,所有代码的执行时间T(n)与每行代码的执行次数n成正比。总结公示如下:
T(n) 代表代码执行的时间,n 表示数据规模的大小;f(n) 表示每行代码执行次数的相加总和。 O 表示代码执行时间T(n) 与 f(n) 表达式成正比。大O 表示法并不是代码实际的执行时间,而是代表代码执行时间随数据规模增长的变化趋势,所以也叫渐进时间复杂度,简称时间复杂度。
2:f(n) 公示中的低阶,常量,系数三部分并不左右增长趋势,所以都可以忽略,只需要记录一个最大量级就可以。
3: 时间复杂度分析比较实用的方法:
- 只关注循环执行次数最多的一段代码
- 总复杂度等于量级最大的那段代码的复杂度
- 嵌套代码的复杂度等于嵌套内外代码复杂度的乘机
4: 常见时间复杂度的量级
常量阶 O(1),对数阶O(logn),线性阶级O(n),线性对数阶O(nlogn),平方阶O(n*n),立方阶级(n*n*n)...
指数阶O(2的n次幂),阶乘阶O(n!)
5: 空间复杂度
表示算法存储空间与数据规模之间的增长关系。 例如 申请一个 大小为n 的数组
int[] a =new int[n]; 空间复杂度就位 O(n)
最好,最坏,平均,均摊时间复杂度
// 在数组长度为m中查找某一个指定元素
int find(int[] arr,int n , int x){
int position=-1;
for(int i=0;i<n;i++){
if(arr[i]==x){
position=i;
break;
}
}
return position;
}
如果x 正好在数组的第一个元素,那么时间复杂度为O(1), 如果不在数组中,那么就需要遍历整个数组,时间复杂度为为O(n)。所以不同情况下这段代码的时间复杂度是不同的。因此,引入三个概念:
最好时间复杂度(在最理想的情况下,执行这段时间的时间复杂度)。
最坏时间复杂度(在最糟糕的情况下,执行这段时间的时间复杂度)。
平均时间复杂度
最好,最坏时间复杂度都是在极端的情况下,发生的概率并不是很大,所以引入平均时间复杂度。平均时间复杂度,需要引入概率论的相关知识。同样对上面代码进行分析:
假设数据x 在数组中和不在数组中的概率都为 1/2;要查找的数据出现在 0~n-1 的位置上的概率为 1/n; 一共有n+1种情况,在 0~n-1 位置上出现,和第n 中情况,不在数组中,所以 最后的公示为:
这个值也叫加权平均值,也叫期望值。所以平均时间复杂度全称为加权平均时间复杂度,也叫期望时间复杂度。用大O表示发,去掉常量和系数阶,最后时间复杂度为O(n)。
实际上,我们并不需要考虑最好,最坏,平均时间复杂度,只有在同一段代码在不同情况下,时间复杂度出现量级的区别时候,才考虑使用这三种时间复杂度。
均摊时间复杂度(摊还分析法)
int[] array= new int[n];
int count=0;
void insert(int val){
if(count==array.length){
int sum=0;
for(int i=0;i<array.length;i++){
sum=sum+array[i];
}
array[0]=sum;
count=1;
}
array[count]=val;
++count;
}
最好情况时间复杂度O(1)(数组没满,直接插入),最坏时间复杂度O(n)(需要遍历所有数组的值求和保存到 array[0]),平均时间复杂度也为O(1)。平均时间复杂度计算如下:
假设数组长度为n,根据位置的不同(有n中情况,数组中有空闲位置),我们插入位置的时间复杂度为O(1)。还有一种情况是数组没有位置,这个情况的时间复杂度为O(n)。 这n+1种情况的加权平均时间复杂度为:
去掉常数阶和系数阶,平均时间复杂度为O(1).
insert 方法时间复杂度有一定规律,一般都是一个O(n) 之后,紧跟值n-1个O(1)。所以针对这个特殊的情况,我们引入摊还分析法,通过此法得到的时间复杂度,称为均摊时间复杂度,具体如下:
每一次O(n)的插入,都跟着n-1个O(1)的插入操作,所以把耗时多的那次操作均摊到接下来n-1次耗时少的操作上,均摊下来,这一组连续的操作均摊时间复杂度就是O(1)。均摊时间复杂特点,大部分时间复杂度都很低,只有个别情况时间复杂度比较高,而且这些操作存在前后连贯的性的时序关系