首页 > 编程笔记 > C++笔记 阅读:25

时间复杂度和空间复杂度(新手必看)

算法是对特定问题解决步骤的一种体现方式,不依赖任何语言,既可以用自然语言、C、C++、Java、Python 等体现,也可以用流程图、框图体现。对于同一个问题,可以采用不同的算法解决。

那么,怎样才算一个好算法呢?好算法的衡量标准如下:
算法复杂度包括时间复杂度和空间复杂度。除前 3 条基本标准外,好算法的评判标准就是高效和低存储,高效即时间复杂度低,低存储即空间复杂度低。

时间复杂度

时间复杂度指算法运行需要的时间。一般将算法基本运算的执行次数作为时间复杂度的衡量标准。
int sum(int n){
    int sum=0; //1次
    for(int i=1;i<=n;i++) //n+1次
        sum+=i; //n次
    return sum; //1次
}
总执行次数为 2×n+3。若用一个函数 T(n)表达:

T(n)=2n+3

则当 n 足够大时,例如 n=105 时,T(n)=2×105+3。

算法运行时间主要取决于最高项,后面的可以忽略不计。因为若你告诉朋友买车花了 20万零199元,则朋友会认为你花了 20 万元,并不关心尾数。若一个人是亿万富翁,则不管其是有 2 亿元还是有 10 亿元,都是亿万富翁。

因此在表达时舍小项、舍系数,只看最高项就可以了。若用时间复杂度的渐进上界 O 表示,则该算法的时间复杂度为 O(n)

其实完全没有必要计算每行代码的运行次数,只计算出现频率最多的语句的运行次数即可。循环内层的语句往往是运行次数最多的,对运行时间贡献最大。

例如,在下面的算法中,“total=total+i*j”是对时间复杂度贡献最大的语句,只计算该语句的运行次数即可。该算法的时间复杂度为 O(n^2)
sum=0;        //运行1次
total=0;      //运行1次
for(i=1;i<=n;i++) { //运行n+1次,最后1次判断条件不成立,结束
    sum=sum+i;    //运行n次
    for(j=1;j<=n;j++) //运行n*(n+1)次
        total=total+i*j; //运行n*n次
}
并不是对每个算法都能直接计算运行次数。对于某些算法如排序、查找、插入等,可以按最好、最坏和平均情况分别求其渐进复杂度。但在考查一个算法时,通常考查最坏情况,而不是考查最好情况,因为最坏情况对于衡量算法的好坏具有实际意义。

空间复杂度

空间复杂度指算法在运行过程中占用了多少内存空间。

算法占用的内存空间包括:输入和输出数据占用的内存空间、算法本身占用的内存空间、额外需要的辅助空间。

输入和输出数据占用的内存空间是必需的。算法本身占用的内存空间可以通过精简算法来压缩,但压缩的量很小,可以忽略不计。

程序在运行时使用的辅助变量占用的内存空间就是辅助空间,是衡量空间复杂度的关键因素。一般将算法的辅助空间作为衡量空间复杂度的标准。

例如,将两个数交换。
swap(int x, int y) { // x 与 y 交换
    int temp;
    temp = x;       // ① temp 为辅助空间
    x = y;          // ②
    y = temp;       // ③
}
这两个数的交换过程如下图所示:


上图中的步骤标号与函数 swap() 中的语句标号一一对应,该算法使用了一个辅助空间 temp,空间复杂度为 O(1)

注意 在递归算法中,每次递推都需要一个栈空间来保存调用记彔,因此在计算空间复杂度时需要计算递归栈的辅助空间。

例如,计算 n 的阶乘。
long long fac(int n) {
    if(n < 0)
        return -1;
    else if(n == 0 || n == 1)
        return 1;
    else
        return n * fac(n - 1);
}
递推和回归在系统内部使用栈实现,栈空间的大小为递归树的深度。计算 n 的阶乘,其递归树如下图所示。


计算 n 的阶乘时,递归树的深度为 n,因此计算 n 的阶乘的递归算法的空间复杂度为 O(n)。

总结

常见算法的时间复杂度如下:
常见的时间复杂度函数曲线如下图所示:


从上图可以看出,指数阶增量随着 x 的增加而急剧增加,而对数阶增量增加缓慢。它们之间的关系为:

O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

相关文章