• 时间复杂度和空间复杂度


    一.时间复杂度

    1.1 什么是时间复杂度

    时间复杂度计算的是程序运行所花费的时间。但是,同一个程序在不同电脑上运行的时间也是不同的,因为不同电脑的性能不同。所以,一般说时间复杂度并不是真正的代码运行的时间,而是一个程序中代码所运行的次数。将这个次数写成一个数学函数表达式,这个表达式就是此程序的时间复杂度。

    1.2 大O渐进表示法

    1.2.1 大O表示法的规则

    • 仅保留最高次数项。
    • 最高项的系数为 1。
    • 当代码的运行次数为常数时,统一记为 O(1)。
    • 对数阶的时间复杂度不管是以几为底,通常都将对数的底数忽略,也就是都记为 O(logn)。

    常见的渐进时间复杂度大小顺序为:

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

    1.2.2 示例解释

    1.O(n)

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int n; // 运行次数为1
    6. cin >> n; // 运行次数为1
    7. for(int i=0; i < n; i ++)
    8. {
    9. printf("%d ", i); // 运行次数为n
    10. }
    11. int m = 10; // 运行次数为1
    12. while(m --)
    13. {
    14. printf("Hello World\n"); // 运行次数为10
    15. }
    16. return 0;
    17. }

    整个程序的运行次数为 n + 13,运行次数的函数表达式为 F(n) = n + 13,大 O 表示法记作 O(n)。

    2.O(n^2)

    1. void Func(int n)
    2. {
    3. int count = 0; // 执行次数为1
    4. for(int i =0; i < n; i++) // 总执行次数为n*n
    5. {
    6. for(int j = 0; j < n;j++)
    7. {
    8. count ++;
    9. }
    10. }
    11. for(int k = 0; k < 2 * n; k++) // 执行次数为2n
    12. {
    13. count ++;
    14. }
    15. int m = 10; // 执行次数为1
    16. while(m --) // 执行次数为10
    17. {
    18. count ++;
    19. }
    20. cout << count << endl; // 执行次数为1
    21. }

    整个程序的运行次数为 n*n + 2*n + 13,运行次数函数表达式为 F(n) = n*n + 2*n + 13,大 O 表示法记作 O(n^2)。

    假设,n = 10,n = 100,n = 1000 ...... 时:

    n = 10 时,F(n)=133

    n = 100 时,F(n) = 10213;

    n = 1000 时,F(n) = 1002013;

    ......

    随着 n 的增大,n*n 的占比越大,剩余项可以完全忽略,只需要保留该函数表达式中的最高次项。

    上述程序的运行次数函数表达式可以简化为 O(n^2)。

    1.2.3 最坏情况、最好情况、平均情况

    有时,算法运行的次数是不确定的。例如,利用遍历方式在长度为 n 的数组中找一个数字。

    分为以下几种情况:

    • 最好的情况,我们要找的数字正好是数组第一个元素,此时算法的时间复杂度为 O(1)。
    • 最坏的情况,我们要找的数字正好是数组的最后一个元素,此时算法的时间复杂度为 O(n)。
    • 在最好和最坏情况间取平均值,即 n/2,利用大 O 表示法时,省略最高项系数,时间复杂度仍为 O(n)。

    所以,算法的时间复杂度可分为三种情况

    • 最坏情况:任意输入规模的最大运行次数(上界);
    • 平均情况:任意输入规模的期望运行次数;
    • 最好情况:任意输入规模的最小运行次数(下界); 

    1.3 常见的时间复杂度分析

    1.O(1)

    1. void f(int n)
    2. {
    3. int count = 0;
    4. for (int k = 0; k < 100; ++ k)
    5. {
    6. count ++;
    7. }
    8. printf("%d\n", count);
    9. }

    2.O(n)

    1. void f(int n)
    2. {
    3. int count = 0;
    4. for (int k = 0; k < 2 * n; ++ k)
    5. {
    6. count ++;
    7. }
    8. int m = 10;
    9. while (m --)
    10. {
    11. count ++;
    12. }
    13. printf("%d\n", count);
    14. }

    3.O(log n)

    不管是以几为底,把所有对数阶的时间复杂度都记为 O(logn)。

    在算法分析中,我们通常将对数的底数忽略,因为对于渐进式增长来说,不同对数底数之间的差异是可以忽略的。这是因为我们主要关注算法的增速和趋势,而不是具体的常数因子或底数。因此,当我们说一个算法的时间复杂度是对数阶时,通常表示为 O(log n),而不考虑底数。无论是以2为底、以10为底、还是以其他任何常数为底,对数增长的趋势都是相同的。

    1. int i = 1;
    2. while (i <= n) {
    3. i = i * 2;
    4. }

    4.O(m + n)

    m 和 n 表示两个数据规模。

    1. void f(int n, int m)
    2. {
    3. int count = 0;
    4. for (int k = 0; k < m; ++ k)
    5. {
    6. count ++;
    7. }
    8. for (int k = 0; k < n; ++ k)
    9. {
    10. count ++;
    11. }
    12. printf("%d\n", count);
    13. }

    二.空间复杂度

    在计算机发展初期,计算机的存储容量很小,所以对空间复杂度很是在乎。但是经过这几十年计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

    2.1 空间复杂度案例

    先来看一个例子,现在有一个算法是这样的,给定一个数组,将数组中每个元素都乘以2返回,我实现了下面两种形式:

    1. private static int[] multi1(int[] array) {
    2. int[] newArray = new int[array.length];
    3. for (int i = 0; i < array.length; i++) {
    4. newArray[i] = array[i] * 2;
    5. }
    6. return newArray;
    7. }
    8. private static int[] multi2(int[] array) {
    9. for (int i = 0; i < array.length; i++) {
    10. array[i] = array[i] * 2;
    11. }
    12. return array;
    13. }

    暂且不论这两个算法孰好孰坏,你来猜猜他们的空间复杂度各是多少?

    你可能会说第一个算法的空间复杂度为O(n),第二个算法的空间复杂度为O(1)。

    错!两个算法的空间复杂度都是O(n)。也不能说你完全错了,因为大部分书籍或者资料都弄错了。

    是时候了解真正的空间复杂度了。

    2.1 什么是空间复杂度

    空间复杂度,是指一个算法运行的过程占用的空间,这个空间包括输入参数的占用空间和额外申请的空间。

    所以,针对上面两个算法:

    • 第一个算法,输入参数n,额外空间n,两者相加为2n,去除常数项,空间复杂度为O(n);
    • 第二个算法,输入参数n,额外空间0,两者相加为n,空间复杂度为O(n)。

    可以看到,使用空间复杂度很难判断这两个算法的好坏,所以,诞生了另一个概念:额外时间复杂度。

    2.2 什么是额外空间复杂度

    额外空间复杂度:是指一个算法运行过程中额外申请的空间。

    使用额外空间复杂度,针对上面两个算法:

    • 第一个算法,额外空间为n,额外空间复杂度为O(n);
    • 第二个算法,额外空间为0,额外空间复杂度为O(1);

    可以看到,从空间占用的角度,使用额外空间复杂度能够很轻易地判断两个算法的好坏。所以,是时候纠正错误的概念了,以后与人交流的时候最好使用“额外空间复杂度”这个概念,但是刷题的时候的要求是空间复杂度,那还是要使用空间复杂度的概念。

  • 相关阅读:
    中兴通讯5G为何要开拓第二曲线业务?一切都是为了得到更好的发展!
    【Verilog基础】【计算机体系架构】一文搞懂 Cache 缓存(cache line、标记Tag、组号/行号index,块内地址offset)
    C语言源代码系列-管理系统之学生信息管理系统
    git 把项目托管到码云
    如何快速高效全面的学习云计算和虚拟化技术
    卷王杯 easy unserialize
    js之原型链
    微信内测朋友圈内容转发功能;快手前副总裁侵占756万余元,一审获刑7年;​俄罗斯法院驳回苹果上诉,将继续进行反垄断调查|极客头条
    Spring 更简单的读取和存储对象
    drawio快捷键
  • 原文地址:https://blog.csdn.net/m0_59749089/article/details/133825148