1. 前言
时间复杂度与空间复杂度是衡量一个算法是否优秀的重要指标。
为什么要引入这两个概念呢?
因为对如一段代码,你无法通过代码长或其他因素来定义这段代码性能的优秀。
比如说对于同一个问题1-n区间的和有多种不同的解法:
代码片段一:
long long sum = 0;
for(int i = 1; i <= n; i++)
sum += i;
cout<<sum<<endl;
代码片段二:
long long sum = 0;
sum = n*(n+1)/2;
cout<<sum<<endl;
直观感代码片段二的运行时间会短于代码片段一的,的确如此,当n<=1e7左右的时候感觉并不明显,当n在1e9左右的时候,会有明显的差异。
那么如何用一个标准来衡量二者之间性能上的差异呢?答案是时间复杂度和空间复杂度
2. 大O()函数
在正式进入话题之前,我们先来了解一下大O()函数
以下来自百度百科
大O符号**(英语:Big O notation),又称为渐进符号,是用于描述函数渐近行为的数学符号。更确切地说,它是用另一个(通常更简单的)函数来描述一个函数数量级的渐近上界。在数学中,它一般用来刻画被截断的无穷级数尤其是渐近级数的剩余项;在计算机科学中,它在分析算法复杂性的方面非常有用。
通俗来说:大O()函数类似于求极限,去掉常数项即可,比如说
O(3*n^2) = O(n^2)
O(n+3) = O(n)
O(3) = O(1)
....
以下为常见的时间复杂度
| 程序执行次数 | 阶数 | 名称 |
|---|---|---|
| 3 | O(1) | 常数阶 |
| 2*n+3 | O(n) | 线性阶 |
| 3n^2+2n+1 | O(n^2) | 平方阶 |
| 5logn+2 | O(logn) | 对数阶 |
| 2n+3nlogn+1 | O(nlogn) | nlogn阶 |
| 6n^3+2n^2+3n+4 | O(n^3) | 立方阶 |
| 2^n | O(2^n) | 指数阶 |
| n! | O(n!) | 阶乘阶 |
以下为它们各自的性能上的差异
差异还是很明显的
其实在算法分析过程中,不止有大O()函数,还有其他的函数,但主要用到的是大O()函数,因此这里只谈大O()函数。
3. 时间复杂度
以下来自百度百科
在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。
官方话语可能云里雾里的,我们还是以代码片段一为例:
long long sum = 0; //执行了1次
for(int i = 1; i <= n; i++) //执行了n次
sum += i;
cout<<sum<<endl;//执行了1次
总的执行次数是n+2,那么它的时间复杂度就是O(n+2) = O(n)。Ps:我们这里不很学术化的让他们相等。
代码片段二
long long sum = 0;//执行了1次
sum = n*(n+1)/2;//执行了1次
cout<<sum<<endl; //执行了1次
总的执行次数是3次,那么它的时间复杂度就是O(3)=O(1)。Ps:我们这里不很学术化的让他们相等。
我们通过大O()函数的图像发现,我们的直觉是正确的,代码片段二的算法明显优于代码片段一。我们发现时间复杂度从O(n)降到了O(1)。这就是我们所说的算法优化,也就是对时间复杂度和空间复杂度进行优化,当然现在随着硬件的发展,计算机整体有了很大性能上的改进,空间复杂度除了特殊要求外一般都是不怎么做要求的,瓶颈在于时间复杂度上。
为什么这么说呢?
因为目前的个人电脑,1s的时间内大约跑1e7左右的指令,在大点可能就呀1s多了,而面试题目或者算法竞赛的题目一般都有时限要求,不同的数据范围,时限要求可能不一样。
还是以求和为例:面试官让你1s内跑出1到1e9的和,显然代码片段二是正解,代码片段一远远不行。
说完时间复杂度,我们再来谈谈空间复杂度
4. 空间复杂度
以下来自百度百科
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。
我们简单举个例子就是:王同学写了一个算法用了1s时间,1k的内存;郭同学写了一个算法用了0.6s时间,2k的内存。这里的1k和2k就是空间复杂度。具体到程序来说,就是我开了几个数组,用了多少变量。
比如说
代码片段一:
long long sum = 0;
for(int i = 1; i <= n; i++)
sum += i;
cout<<sum<<endl;
代码片段二:
long long sum = 0;
int a[n];
for(int i = 1; i <= n; i++)
a[i] = i;
for(int i = 1; i <= n; i++)
sum += a[i];
cout<<sum<<endl;
代码片段一只有一个变量sum,代码片段二中有一个数组a[n],那么二者的空间复杂度分别为O(1)和O(n)。
一般来说,除了面试官或者出题人特别卡你,空间复杂度要求并不高,简单了解以下即可。
5. 实战应用
最后,我们来分析一下一段代码,来看看你是否掌握
int a[n];
int b[n];
for(int i = 1; i < n; i++){
a[i] += a[i-1];
}
long long sum = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
b[i] += a[j];
}
sum += a[i];
}
答案是:时间复杂度O(n^2),空间复杂度O(n)。你答对了吗?
6. 附录
排序算法的时间空间复杂度。
更多文章:欢迎关注微信公众号:王同学的数学与算法小课堂
本文使用 mdnice 排版