这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
本系列文章为个人学习总结,如果有发现错误或存在疑问之处,欢迎留言指点!
本文是重学数据结构系列的第一篇,系列文章如下:
1.算法时间复杂度和空间复杂度
2.重学数据结构--链表
3.重学数据结构--队列
4.重学数据结构--栈
5.重学数据结构--树
1.前言
刷算法的小伙伴都知道,算法有好有坏,我们刷算法的最高的一个追求是寻找一个最优解,那么我们怎么评判一个算法的好坏呢?那就是算法运行时间的长短和占用内存空间的大小 ,对应时间复杂度跟空间复杂度。
2.衡量方法
既然要衡量好坏那么肯定要有一种方法,我们算法中常常使用大O(字母)表示法来衡量算法的时间复杂度(也称渐进时间复杂度)和空间复杂度(也称渐进空间复杂度)。那么大O表示法是什么呢?我们从时间复杂度角度看,其定义如下:
若存在函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为 不等于零的常数,则称f(n)是T(n)的同数量级函数。记作
T(n)=O(f(n)),称为O(f(n)),O为算法的渐进时间复杂度,简称为时间复杂度
常见的时间/空间复杂度量级有:
- 常数阶O(1)
- 对数阶O(logN)
- 线性阶O(n)
- 线性对数阶O(nlogN)
- 平方阶O(n²)
- 立方阶O(n³)
- K次方阶O(n^k)
- 指数阶(2^n)
下面通过通过举一些例子来进行常用时间/空间复杂度的判断。
3.时间复杂度
在 大O符号表示法中,时间复杂度的公式是: T(n) = O( f(n) ),其中f(n) 表示每行代码执行次数之和,而 O 表示正比例关系 。
3.1.常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
3.2.线性阶O(n)
线性阶一般为执行n次的循环或者执行n次递归,如:
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
3.3.对数阶O(logN)
还是先来看代码:
int i = 1;
while(i<n)
{
i = i * 2;
}
从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n 也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)
3.4.线性对数阶O(nlogN)
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是O(nlogN)。
就拿上面的代码加一点修改来举例 :
for(m=1; m<n; m++)
{
i = 1;
while(i<n)
{
i = i * 2;
}
}
3.5.平方阶O(n²)
平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。 举例:
for(x=1; i<=n; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
4.空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,它的公式为:S(n)=O(f(n))。
空间复杂度比较常用的有:O(1)、O(n)、O(n²),下面我们举例看看。
4.1.常量空间
当算法的存储空间大小固定,和输入规模没有直接的关系时,空间复杂度记作O(1)。举例如下:
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
4.2.线性空间
当算法分配的空间是一个线性的集合(如数组),并且集合大小和 输入规模n成正比时,空间复杂度记作O(n)。
举例如下:
void fun(int n){
int[] array = new int[n];
//....
}
注意:递归也属于线性空间
4.3.二维空间
当算法分配的空间是一个二维数组集合,并且集合的长度和宽度都 与输入规模n成正比时,空间复杂度记作O(n2)。举例如下:
void fun(int n){
int[][] array = new int[n][n];
//....
}
5.时间复杂度和空间复杂度的取舍
我们之所以花大力气去评估一个算法的时间复杂度和空间复杂度,其根本原因是因为计算机的速度和空间是有限。时间复杂度和空间复杂度经常是矛盾的。如果需要降低算法的时间复杂度,则通常需要增加算法的空间复杂度;如果想减少额外空间的使用,则通常会导致算法的时间复杂度增加。 因此我们需要在它们两个之间作一个取舍,大多数情况下我们会选择牺牲空间复杂度而成全时间复杂度,因为程序运行期间时间是无法回收的,而空间是可以回收的。
参考:《漫画算法》