数据结构与算法|第一章:复杂度分析

数据结构与算法|第一章:复杂度分析

1.项目环境

2.大 O 复杂度表示法

如何计算一段代码的时间复杂度?

2.1 示例1

  • 假设每行代码执行的时间一样,为 unit_time
  • 本方法执行总时间为多少
public static int cal(int n) {
    int sum = 0;
    int i = 1;
    for (; i <= n; i++) {
        sum = sum + i;
    }
    return sum;
}

第 2 行和第 3 行执行时间为 2*unit_time

第4 行和第 5 行是一个 for 循环,执行时间为 2n*unit_time

所以方法执行的总时间 T(n) = (2+2n)*unit_time

2.2 示例2

    public static int cal2(int n) {
        int sum = 0;
        int i = 1;
        for (; i <= n; i++) {
            int j = 1;
            for (; j < n; j++) {
                sum = sum + i;
            }
        }
        return sum;
    }

第一段(2行,3行):2*unit_time

第二段(4行,5行):2n*unit_time

第三段(6行,7行): 2 n 2 2n^2 2n2*unit_time

方法执行的总时间 T(n) = ( 2 + 2 n + 2 n 2 (2+2n+2n^2 (2+2n+2n2)*unit_time

2.3 复杂度公式

将上面的例子总结成一个公式:
T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n))

  • T(n) 它表示代码执行的时间;n 表示数据规模的大小

  • f(n) 表示每行代码执行的次数总和

因为这是一个公式,所以用 f(n) 来表示。公式中的 O,表示代码的执行时间 T(n) 与 f(n) 表达式成正比。

第一个例子复杂度公式:

T ( n ) = O ( 2 n + 2 ) T(n) =O(2n+2) T(n)=O(2n+2)

第二个例子复杂度公式:

T ( n ) = O ( 2 + 2 n + 2 n 2 ) T(n) = O (2+2n+2n^2) T(n)=O(2+2n+2n2)

当 n 很大时,你可以把它想象成10000、100000。而公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个最大量级就可以了,如果用大 O 表示法表示刚讲的那两段代码的时间复杂度,就可以记为: T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n) T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)

3.时间复杂度分析

3.1 只关注循环执行次数最多的一段代码

public static int cal(int n) {
    int sum = 0;
    int i = 1;
    for (; i <= n; i++) {
        sum = sum + i;
    }
    return sum;
}

这个例子中循环次数最多的是第 4 行和第 5 行代码,所以时间复杂度 T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n)

3.2 加法法则:总复杂度等于量级最大的那段代码的复杂度

    /**
     * 加法法则:T(n) = O(max(f(n), g(n)))
     * @param n
     */
    public static void cal3(int n){
        cal(n);//T1(n)=O(f(n))
        cal2(n);//T2(n)=O(g(n))
    }

方法1 cal(n) 的时间复杂度: T 1 ( n ) = O ( f ( n ) ) T1(n)=O(f(n)) T1(n)=O(f(n))

方法2 cal2(n) 的时间复杂度: T 1 ( n ) = O ( g ( n ) ) T1(n)=O(g(n)) T1(n)=O(g(n))

方法3 = 方法1+方法2: T ( n ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n) = O(max(f(n), g(n))) T(n)=O(max(f(n),g(n)))

3.3 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

    /**
     * 乘法法则:T(n) = T1(n) * T2(n) = O(n*n) = O(n2)
     * @param n
     */
    public static void cal4(int n){
        for (int i = 0; i < n; i++) {//T1(n)
            cal(n);//T2(n)
        }
    }

方法 cal4 循环中嵌套了 cal(n) 的执行。

T ( n ) = T 1 ( n ) ∗ T 2 ( n ) = O ( n ∗ n ) = O ( n 2 ) T(n) = T1(n) * T2(n) = O(n*n) = O(n^2) T(n)=T1(n)T2(n)=O(nn)=O(n2)

4.常见时间复杂度分析

4.1 复杂度量级

  • 常量阶 O ( 1 ) O(1) O(1)
  • 对数阶 O ( l o g n ) O(logn) O(logn)
  • 线性阶 O ( n ) O(n) O(n)
  • 线性对数阶 O ( n l o g n ) O(nlogn) O(nlogn)
  • 平方阶 O ( n 2 ) 、 立 方 阶 O ( n 3 ) . . . k 次 方 阶 O ( n k ) O(n^2)、立方阶 O(n^3) ... k 次方阶 O(n^k) O(n2)O(n3)...kO(nk)
  • 指数阶 O ( 2 n ) O(2^n) O(2n)
  • 阶乘阶 O ( n ! ) O(n!) O(n!)

4.2 O(1)

O(1) 只是常量级时间复杂度的一种表示方法,并不是指只执行了一行代码。比如这段代码,即便有 3 行,它的时间复杂度也是 O(1),而不是 O(3)。

int i = 8;
int j = 6;
int sum = i + j;

4.3 O(logn) & O(nlogn)

    public static int calLogn(int n) {
        int i = 1;
        while (i <= n) {
            i = i * 2;
        }
        return i;
    }

每次循环 i=i*2,i 的值都会增大一倍,当 i 的值大于 n 时,结束循环

方法的执行次数 x = l o g 2 n log_2n log2n

如果将 i = i * 2 -> i = i * 3;

    public static int calLogn(int n) {
        int i = 1;
        while (i <= n) {
            i = i * 3;
        }
        return i;
    }

方法的执行次数 x = l o g 3 n log_3n log3n

l o g 3 n = l o g 3 2 ∗ l o g 2 n log_3n = log_32 * log_2n log3n=log32log2n

根据前面的理论 l o g 3 2 log_32 log32 是一个常数可以忽略,所以无论 log 的底数是多少,此方法的时间复杂度都可以表示为

T ( n ) = O ( l o g n ) T(n) = O(logn) T(n)=O(logn)

假设 calLogn 方法执行 n 次,那么时间复杂度为

T ( n ) = O ( n l o g n ) T(n) = O(nlogn) T(n)=O(nlogn)

4.4 O(m+n) & O(m*n)

    public static int calMN(int m, int n) {
        int sum = 0;
        
        for (int i = 0; i < m; i++) {
            sum = sum + i;
        }

        for (int i = 0; i < n; i++) {
            sum = sum + i;
        }

        return sum;
    }

m 和 n 是表示两个数据规模。我们无法事先评估 m 和 n 谁的量级大,所以我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以,上面代码的时间复杂度就是 O(m+n) 。这种情况下,加法法则修改为 T1(m) + T2(n) = O(f(m) + g(n)),乘法法则没有影响。

5.空间复杂度分析

    public static int[] calSpace(int n) {
        int[] sum = new int[n];
        for (int i = 0; i < n; i++) {
            sum[i] = i;
        }
        return sum;
    }

第 2 行代码 new int[n] 申请了一个大小为 n 的数组空间,其他代码占用的空间都很小,所有整段代码的空间复杂度为 O ( n ) O( n) O(n),常见的空间复杂度就是 O ( 1 ) 、 O ( n ) 、 O ( n 2 ) O(1)、O(n)、O(n^2 ) O(1)O(n)O(n2)

6.小结

程序代码执行效率的瓶颈可能发生在 时间空间 两个维度,但是相对于空间,时间更加宝贵;假设一段程序计算需要1G 的内存和一年时间,如果增加内存到10G,就可以缩短计算时间为半年,相信大家都会这样选择,所以大部分情况下,我们会以时间复杂度为更重要的标准。

7.参考

  • 极客时间 -《数据结构与算法之美》王争
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值