拧一个螺母,扳手和钳子都可以,但用扳手效率更高。
所以我们要选择最合适的解决方法,即算法。
时间复杂度和空间复杂度是用来衡量一个算法的运行效率。
算法首先要能彻底解决这个问题,即准确性,且能任何情况下能稳定运行不崩溃,即健壮性。
满足这两者的基础上,尽可能提高程序的运行效率:
- 程序运行的时间:时间复杂度。
- 程序运行所需内存空间的大小:空间复杂度。
程序运行时间更短,运行期间占用的内存更少,算法的运行效率就更高。
时间复杂度:
表示一个算法所编程序的运行时间,但不是准确值(受软硬件甚至是不同时段系统环境的影响,无法具体得出),而是根据合理方法得到的预估值。
预估一个算法所需的运行时间:分别计算程序中每条语句的执行次数,然后用总的执行次数来表示程序的运行时间。
for(int i=0;i<n;i++) //从0到n,执行n+1次
{
a++; //从0到n-1,执行n次
}
整段代码执行总次数:2n+1 次。
for(int i = 0 ; i < n ; i++) // n+1
{
for(int j = 0 ; j < m ; j++) // n*(m+1)
{
num++; // n*m
}
}
整段代码执行总次数:2*n*m+2*n+1 次
不同程序的运行时间,多在最坏条件下程序的运行时间,即当n,m都趋向于正无穷大。
则:2n+1可简化为n,2*n*m+2*n+1 可简化为n²,实际上就是最内层循环中语句的执行次数。
数据结构中,语句执行的次数,称为该语句的频度。
大O标记法:O(频度)
则:程序1运行时间:O(n),程序2运行时间:O(n²)
常见的几种时间复杂度,及之间的大小关系:
O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n2)平方阶 < O(n3)(立方阶) < O(2n) (指数阶)
注意:这里仅介绍了以最坏情况下的频度作为时间复杂度,而在某些实际场景中,还可以用最好情况下的频度和最坏情况下的频度的平均值来作为算法的时间复杂度。
空间复杂度:
一个程序,运行过程中需要占用大小不等的存储空间:
- 程序代码本身所占用的存储空间:编写尽可能短的代码。
- 程序中如果需要输入输出,也会占用一定的存储空间:由要解决的问题觉得,与算法关系不大。
- 程序在运行过程中,可能还需要临时申请更多的存储空间:对算法的空间复杂度影响最大。
如果程序所占用的存储空间与输入值无关,则程序的空间复杂度为O(1);反之:
- 如果随着输入值n的增大,程序申请的临时空间成线性增长,则程序的空间复杂度用O(n)表示;
- 如果随着输入值n的增大,程序申请的临时空间成n²增长,则程序的空间复杂度用O(n²)表示;
- 如果随着输入值n的增大,程序申请的临时空间成n³增长,则程序的空间复杂度用O(n³)表示;
大多数时候,一个好的算法往往更注重时间复杂度的比较,空间复杂度保持在一个合理的范围内就好。