【算法之旅】如何衡量程序运行的效率?

227 阅读4分钟

说在前面

哈喽,大家好,你们的米饭又双叒叕回来了。接下来一段时间,我会学习一些编程基础。为了验证学习的掌握程度,过程中会将每次的学习记录下来,会讲明白了就说明我已经学会了。作一行,爱一行,无论世界如何变化,不变的就是这些基础,这些编程过程之中的哦~啊哈哦耶~

如果你们有兴趣,就跟着一起学习吧。亦或是当个看官,笑笑就过去了吧!

什么是复杂度

复杂度是衡量代码运行效率的重要度量因素。在计算机中,每一个程序都是通过代码构成的,就是经过一些列的计算,对数据进行加工处理,最后的结果就是你所看到的成品-- 电脑软件、手机app、网站、等等。

可见编程的核心就是要完成计算。那对于一个计算任务得到计算结果的过程,如果这个过程复杂,计算得慢,就会大大影响到效率和体验,这便是所有编程人员都该考虑和看重的问题,你代码的复杂度

如何衡量复杂度

一般说来,代码在计算过程中会消耗计算时间,和计算空间。所以要衡量的就是时间复杂度空间复杂度。举个例子:

生活中的十字路口有红绿灯,所有车辆依据红绿灯来通行,当大量的车通过时就会消耗每个人的时间,红绿灯越久消耗的时间越多。这便是消耗了时间资源。

当城市规划者为这个路口设计了立交桥时,每一个方向的车辆都可以通行,不用等待红绿灯,没有时间的消耗,但是却消耗了大量的土地空间。这便是消耗了空间资源。

计算机中也一样,要衡量的就是代码运行的时间和所占用的存储空间。输入的数据越大,处理加工的时间就越长,可能消耗的空间也越大,所以复杂度和输入的数据有关,我们需要用数学的方式来定义它。

假设我们代码的是f(n)n代表输入的数据,那么复杂度就用O(f(n))来表示。例如: O(n)表示计算的复杂度与输入个数n成线性相关;O(logn)表示计算的复杂度与输入个数n成对数相关。

再则,我们记一下几个原则:

  • 复杂度与具体的常量无关

O(2n)O(n)表示的是相同的复杂度,O(n)和O(n/2)也是相同的复杂度,因为复杂度与常量无关。O(2n) 表示一段复杂度为n的代码要执行2遍,它的复杂度还是n,O(n/2)虽然好像是少了,执行了一半的次数就可以,但是它的复杂度仍然为O(n)

  • 多项复杂度相加时,取影响较大的复杂度

O(n²)+O(n)O(n²) 表示的是同样的复杂度。因为O(n²)的影响要远大于O(n)对代码复杂度的影响,所以取O(n²)表示复杂度即可。

  • O(1)是特俗的复杂度

O(1)是特俗的复杂度,表示复杂度与输入n无关,一个数据进来要花5s时间,一千万个数据进来还是花5s时间。这便是O(1)复杂度。

代码是如何影响复杂度

代码编写的好坏,程序人员的思维和解题方式是会影响运行效率的,所以才有低级工程师和高级工程师之分。我们来看一个例子。

任务:输入 a=[1,2,3,4,5],输出 [5,4,3,2,1]。

方法一:新建一个数组b,将a的数据颠倒顺序地拷贝到b,然后输出b的结果。代码如下:

const func = (a) => {
    let b = []
    for(let i = 0; i < a.length; i++) {
        b[a.length - i - 1] = a[i]
    }
    return b
}

console.log( func([1, 2, 3, 4, 5]) )   //  [5, 4, 3, 2, 1]

代码中的执行次数与数组a的长度有关,所以时间复杂度就是O(n),空间复杂度呢?因为代码中定义了一个新的数组b,它与数组a的长度一样,所以空间复杂度也是O(n)

方法二:定义一个缓存,直接交换a中的数据。代码如下:

const func = (a) => {
    let tmp = 0
    for(let i = 0; i < (a.length / 2); i++) {
        tmp = a[i]
         a[i] = a[a.length - i - 1]
         a[a.length - i - 1] = tmp
    }
    return a
}

console.log( func([1, 2, 3, 4, 5]) )   //  [5, 4, 3, 2, 1]

这段代码中的执行次数与为数组长度的一半,所以时间复杂度就是O(n/2),根据复杂度与常量无关原则,它的时间复杂度也是O(n)。空间复杂度呢?代码中定义了一个tmp变量,它与数组a的长度无关,所以空间复杂度是O(1)

所以对于同一个问题,采用不同的计算方法,对时间和空间复杂度的影响是不一样的。每一个优秀的编码人员都必须尽可能的减少时间和空间的损耗。

我们给出一些经验的结论,你分析代码时会简单一些:

  • 顺序结构的代码,时间复杂度是O(1)
  • 二分查找,分治,时间复杂度是O(logn)
  • 一个简单for循环,时间复杂度是O(n)
  • 两个顺序执行的for循环,时间复杂度是O(2n),也就是O(n)
  • 两个嵌套执行的for循环,时间复杂度是O(n²)

降低复杂度的必要性

很多人可能没有这么强的意识,因为他们不需要处理大量的数据,没有见到复杂度所带来的影响。我们来看一组数据。假设你的老板要你处理10万条数据,从10万个电话号码中,找到某个人:

  • 如果是O(n²)的时间复杂度,计算的次数就大概是 100 亿次左右
  • 如果是O(n)的时间复杂度,计算的次数就大概是 10万
  • 如果厉害点,在O(log n)的时间复杂度里完成计算,计算的次数就大概是 17次左右(log2 100000 = 16.61)

时间复杂度与 代码的设计结构相关, 空间复杂度与 代码的数据结构相关。要想上升到高级工程师,这些是必须打好的基础。愿我们都学以致用,乘风破浪,在代码中找到乐趣!

本文为学习《重学数据结构与算法》的学习记录!