数据结构和算法之时间空间复杂度分析

数据结构和算法之时间空间复杂度分析

  • 复杂度分析

    • 算法本质上是一连串的计算步骤。对于同一问题,我们可以使用不同的算法来获得相同的结果,可是在计算过程中电脑消耗的时间和资源却有很大的区别。那么我们如何来比较不同算法之间的优势呢?
    • 目前分析算法主要从「时间」和「空间 」两个维度来进行分析。时间维度顾名思义就是算法需要消耗的时间,「时间复杂度」是常用的分析单位。空间维度代表算法需要占用的内存空间,我们通常用「空间复杂度」来分析。
    • 所以,分析算法的效率主要从「时间复杂度」和「空间复杂度」来分析。很多时候我们两者不可兼得,有时候要用时间换空间,或者空间换时间。下面我们一起来分别了解「时间复杂度」和「空间复杂度」的计算方式。
  • 时间复杂度

    • 想要从「时间维度」来了解一个算法,最简单的方法就是将算法运行一遍,然后计算花费的时间就可以了。

    • 此方法可行,可是有很多弊端。只计算运行时间特别容易受到运行环境的影响,高性能和低性能的机器上出来的结果相差甚远。而且与测试时使用的数据规模也有很大的关系。

    • 所以我们需要一种复杂度计算方式,不受计算性能和程序数据的影响,「大O符号表示法」(BigO)就是这种计算方法,既T(n)=O(f(n)),它表示一个算法的渐进时间复杂度。其中f(n)表示代码执行次数之和,O表示正比例关系。我们来看一个例子:

      for(int i = 1; i<=n; i++){
      	x++;
      }
      

      每个算法需要多少的运行时间呢?我们知道这个for loop有n个循环,假设其中x++计算的消耗是一个单位,那么第一次循环是1单位,第二次循环是2单位,所以整个循环语句就要消耗n个单位。可以发现,消耗的单位时间随着循环的次数而变化,循环次数为1,时间为1单位;循环次数为10,时间为10单位;循环次数为n,时间为n单位。所以这个算法的「时间复杂度」可以表示为:T(n) = O(n)

    • 有人可能不同意了,因为严格计算下,int i = 1也要消耗1单位时间,i <= ni++也都需要1单位时间,所以严格来说总时间是T(n) = 1+3n。但是我们依然会简化为n,因为「大O表示法」用与表示计算的增长变化趋势。

    • 在这个例子中,如果n无限大的时候,T(n) = 1 + 3n中的常数1就没有意义了,倍数3也影响不大。所以简化为T(n) = O(n)就可以了。

    • 我们再来看一个例子:

      for(int i = 1;i <= n;i++){
      		for(int j = 1;j <= n;j++){
      				x++;
      		}
      }
      

      在外层循环中,i总共需要n层循环,在每一次内层循环中,j也会循环n次。如果用「大O表示法」来计算,那么两个循环语句的复杂度就是O(n^2),如果我们将这两个算法合并到一起:

      for(int i = 1;i <= n;i++){
      		x++;
      }
      for(int i = 1; i<= n;i++){
      		for(int j = 1;j <= n;j++){
      				x++;
      		}
      }
      

      整个算法复杂度就变成为O(n + n^2),在n无限大的情况下,可以简化为O(n^2)

  • 常用的时间复杂度分析

    以下便是常见的时间复杂度量级:
    在这里插入图片描述

    • 常数阶O(1)
    • 对数阶O(logN)
    • 线性阶O(n)
    • 线性对数阶O(nlogN)
    • 平方阶O(n^2)
    • 立方阶O(n^3)
    • K次方阶O(n^k)
    • 指数阶(2^n)
    • 阶乘O(n!)

    上面的时间复杂从上到下复杂度越来越大,也意味着执行效率越来越低。以下我们来讲解常用的量级:

    1. 常数阶O(1)

      只要没有循环或递归等复杂逻辑,无论代码执行多少行,代码复杂度都为O(1),如下:

      int x = 0;
      int y = 1;
      int temp = x;
      x = y;
      y = temp;
      

      上述代码在执行的时候,所消耗的时间不会随着特定变量的增长而增长,即使有几万行这样的代码,我们都可以用O(1)来表示它的时间复杂度。

    2. 线性阶O(n)

      我们在上述的例子中讲解过O(n)的算法:

      for (int i = 1; i <= n; i++) {
          x++;
      }
      

      在这段代码中,for循环会执行n遍,因此计算消耗的时间是随着n的变化而变化,因此这类代码都可以用O(n)来表示其时间复杂度。

    3. 对数阶O(logN)

      来看以下的例子:

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

      在上面的循环中,每次i都会被乘以2,也意味着每次都离n更进一步。那需要多少次循环i才能等于或大于n呢,也就是求解2的x次方等于n,答案x=log2^n。也就是说循环log2^n次之后,i会大于等于n,这段代码就结束了。所以此代码的复杂度为:O(logN)

    4. 线性对数阶O(nlogN)

      线性对数阶O(nlogN)很好理解,也就是将复杂度为O(logN)的代码循环n遍:

      for(int i = 0;i <= n;i++){
      		int x = 1;
      		while(x < n){
      				x = x *2;
      		}
      }
      

      因为每次循环的复杂度为O(logN),所以n*logN = O(nlogN)

    5. 平方阶O(n^2)

      在之前的例子我们也讲过,O(n^2)就是将循环次数为n的代码再循环n遍:

      for(int i = 1;i <= n;i++){
      		for(int j = 1;j <= n;j++){
      				x++;
      		}
      }
      

      O(N^2)的本质就是n*n,如果我们将内层的循环次数改为m:

      for (int i = 1;i <= n;i++){
      		for(int j = 1;j <= m;j++){
      				x++;
      		}
      }
      

      复杂度就变为n*m = O(n*m)

      • 关于一些更高的阶级比如O(n^3)或者O(n^k),我们可以参考O(n^2)来理解即可,O(n^3)相当于三层循环,以此类推。
      • 除了「大O表示法」还有其他「平均时间复杂度」、「均摊时间复杂度」、「最坏时间复杂度」、「最好时间复杂度」等等分析指数,但是最常用的依然是「大O表示法」
  • 空间复杂度

    • 既然「时间复杂度」不是计算程序具体消耗的时间,『空间复杂度』也不是用来计算程序具体占用的空间。随着问题量级的变大,程序需要分配的内存空间也可能会变得更多,而『空间复杂度』反映的则是内存空间增长的趋势。
  • 常用的空间复杂度分析

    比较常用的空间复杂度有:O(1)、O(n)、O(n^2)。在下面的例子中,我们用S(n)来定义『空间复杂度』。

    1. O(1)空间复杂度

      如果算法执行需要的临时空间不随着某个变量n的大小而变化,此算法空间复杂度为一个常量,可表示为O(1)

      int x = 0;
      int y = 0;
      x++;
      y++;
      

      其实x,y所分配的空间不随着处理数据量变化,因此「空间复杂度」为O(1)

    2. O(n)空间复杂度

      以下的代码给长度为n的数组赋值:

      int[] newArray = new int[n];
      for (int i = 0;i < n;i++){
      		newArray[i] = i;
      }
      

      在这段代码中,我们创建了一个长度为n的数组,然后在循环中为其中的元素赋值。因此,这段代码的「空间复杂度」取决于newArray的长度,也就是n,所以S(n) = O(n)

    以上便是「时间复杂度」和「空间复杂度」的简单介绍啦,简单的说,这两个复杂反映的是,随着问题量级的增大,时间和空间增大的趋势。学会了复杂度的分析,我们就可以对比算法之间的优劣势啦~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yige001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值