得知道的时间复杂度计算

本文深入浅出地介绍了时间复杂度的计算方法,包括常数项、低次项和最高项常数乘的省略原则,以及如何通过分析和数学运算得到时间复杂度。覆盖简单执行次数、循环、递归算法的时间复杂度计算。

1. 概述

从大一到大三,迷迷糊糊看了时间复杂度三年多,今天终于把它搞清楚了。
首先,我们需要知道的是 时间复杂度要计算的是一个程序大致执行了多少个语句(之前我认为是要计算到底执行了多少秒,是我天真了,怎么可能了),时间复杂度的计算允许省去一些影响小的因素(比如说后面会提到的n对n2的影响小,这时我们就只要考虑n2就行)。

2. 时间复杂度的计算步骤

关于时间复杂度的计算可以分为三类:

  • 简单由执行次数得到时间复杂度
  • 通过分析和数学运算得到时间复杂度
  • 递归算法的时间复杂度

2.1 简单由执行次数得到时间复杂度

假设一些简单的算法我们能一眼看出总执行次数,我们拿到总执行次数根据下面的原则可以得到最后的时间复杂度。

  1. **常数项省略:**我们知道常数项对函数的增长速度影响并不大,所以当 T(n) = c,c 为一个常数的时候,我们说这个算法的时间复杂度为 O(1);如果 T(n) 不等于一个常数项时,可以直接将常数项省略。
  2. **低次项省略:**我们知道高次项对于函数的增长速度的影响是最大的。n^3 的增长速度是远超 n^2 的,同时 n^2 的增长速度是远超 n 的。 同时因为要求的精度不高,所以我们直接忽略低此项。
  3. **与最高项的常数乘省略:**因为函数的阶数对函数的增长速度的影响是最显著的,所以我们忽略与最高阶相乘的常数。

总结:如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n)),我们将这种方法称为大O推导法

2.2 通过分析得到时间复杂度

插播:常见时间复杂度的大小性质和比较:

由执行次数得到时间复杂度很容易,但是大多数情况都不能看出时间复杂度,需要通过一定的分析和数学运算得到执行次数T(n)。对此,我们提供了下面四个便利的法则来推导出T(n)。

  1. **循环的计算:**对于一个循环,假设循环体的时间复杂度为 O(n),循环次数为 m,则这个
    循环的时间复杂度为 O(n×m)。
// 此时时间复杂度O(n*1)
void aFunc(int n) {
    for(int i = 0; i < n; i++) {         // 循环次数为 n
        printf("Hello, World!\n");      // 循环体时间复杂度为 O(1)
    }
}
  1. 多个循环的计算:对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c…,则这个循环的时间复杂度为 O(n×a×b×c…)。分析的时候应该由里向外分析这些循环
// 此时时间复杂度为O(n*n*1)
void aFunc(int n) {
    for(int i = 0; i < n; i++) {         // 循环次数为 n
        for(int j = 0; j < n; j++) {       // 循环次数为 n
            printf("Hello, World!\n");      // 循环体时间复杂度为 O(1)
        }
    }
}
  1. 对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度
// 时间复杂度为max(O(n^2),O(n)),即O(n^2)
void aFunc(int n) {
    // 第一部分时间复杂度为 O(n^2)
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            printf("Hello, World!\n");
        }
    }
    // 第二部分时间复杂度为 O(n)
    for(int j = 0; j < n; j++) {
        printf("Hello, World!\n");
    }
}
  1. 对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。
// 此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)。
void aFunc(int n) {
    if (n >= 0) {
        // 第一条路径时间复杂度为 O(n^2)
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                printf("输入数据大于等于零\n");
            }
        }
    } else {
        // 第二条路径时间复杂度为 O(n)
        for(int j = 0; j < n; j++) {
            printf("输入数据小于零\n");
        }
    }
}

2.3 通过数学运算得到时间复杂度

在一些情况下,我们需要使用简单数列求和对数求解来得到时间复杂度

void aFunc(int n) {
    for (int i = 2; i < n; i++) {
        i *= 2;
        printf("%i\n", i);
    }
}
/*
假设循环次数为 t,则循环条件满足 2^t < n。
可以得出,执行次数t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。
*/
void aFunc(int n) {
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            printf("Hello World\n");
        }
    }
}
/*
当 i = 0 时,内循环执行 n 次运算,当 i = 1 时,内循环执行 n - 1 次运算……当 i = n - 1 时,内循环执行 1 次运算。
所以,执行次数 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。
根据上文说的 大O推导法 可以知道,此时时间复杂度为 O(n^2)。
*/
这里涉及到数列求和的运算,对于数列求和的公式为Sn=n*a1+1/2*n(n-1)d。其中d为两个相邻数的等差值

2.4 递归算法的时间复杂度

设递归算法的时间复杂度为T(n),则可以设递归算法内部的递归为T(m) (m为在调用递归时传入的规模大小,常见的m有n-1或n/2等),则可以根据T(m)的运算得到一个T(n)。我们通过一道具体汉若塔问题解释迭代法具体是什么样的。

// 执行次数为T(n)
void hano(char A,char B,char C,int n){
    if(n>0){
        hano(A,C,B,n-1);	//执行次数为T(n-1)
        move(A,C);			//执行次数为1
        hano(B,A,C,n-1);	//执行次数为T(n-1)
    }
}

根据上面的算法,我们设递归算法的执行次数为T(n)。则有T(n)=2*T(n-1)+1。

(1)迭代法

如果使用迭代法,我们就需要将T(n)=2 * T(n-1)+1展开,得到最终的T(n)=2k*T(n-k)+(2k-1)=>T(n)=(2 * 2^n) - 1=>O(n)=2^n

(2)公式法

在很多情况下,迭代法使用起来比较困难。当递归算法的执行次数函数T(n)满足如下公式时,就可以利用公式法得到算法的时间复杂度:

T(n)=a*T(n/b)+f(n) f(n)值每次递归完毕后,额外的计算执行时间

当参数a、b都确定时,递归部分时间复杂度为:O(n^logba)

那么最后的时间复杂度为多少了?O(n^logba)?不是的,因为f(n)也需要考虑,具体分为三种情况:

  • 情况一:当O(nlog<sub>b</sub>a)>f(n)的时候,最终时间复杂度为O(nlogba)
  • 情况二:当O(n^logba)<f(n)的时候,最终时间复杂度为f(n)
  • 情况三:当O(nlog<sub>b</sub>a)=f(n)的时候,最终的时间复杂度为O(nlogba)logn

参考资料:

(数据结构)十分钟搞定时间复杂度(算法的时间复杂度)

300分钟搞定算法面试

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值