和我一起学习前端算法 —— 空间复杂度

367 阅读4分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」。

本来想取名为《前端算法入门》的,但是又担心自己水平不够把大家带沟里了,所以此系列文章就改名为和我一起学习前端算法

昨天肝到 11点半😹,终于将时间复杂度讲完了,今天继续讲讲空间复杂度。

空间复杂度

计算机科学中,一个算法程序空间复杂度定性地描述该算法或程序运行所需要的存储空间大小。空间复杂度是相应计算问题输入值的长度的函数,它表示一个算法完全执行所需要的存储空间大小。

时间复杂度类似,空间复杂度通常也使用大O记号渐进地表示。

和时间复杂度相比,空间复杂度的分析要简单很多。

因为一般情况下我们算法执行需要的存储空间大小,就是我们运行过程中申请的变量占用的内存。再进一步的简化就可以直接数简单变量的个数以及数组变量\对象变量的属性个数(可能需要递归)。虽然说申请一个数字变量和一个字符串变量需要的内存是有差异的,但是推导渐近空间复杂度的时候是忽略系数的,所以可以将所有简单变量都看作占用相同的内存。

下面举几个简单例子方便大家理解:

const add = (a, b) => a + b; // 2 => O(1)
const getArr = (n) => {
  const arr = [];
  let i = 0;
  while (i < n) {
    arr[i] = 1;
  }
  return arr;
} // n + 2 => O(n)
const convert = (arr) => arr.map(n => n * 2); // 2n + X(X 是 map 运行产生的变量) => O(n)

n 指的是问题规模,不是简单的指入参个数。可能是入参的数字值,也可能是入参的数组的长度。总之这个值决定了 “循环” 的次数。

同样,时间复杂度的计算规律,也适用于空间复杂度:如果两种算法进行组合,一般只能是相加或者相乘,当两者相加时,直接取更高的那种复杂度,如果是相乘则复杂度相乘。

举个例子:

  • O(1) + O(n) = O(n)
  • O(n) * O(log n) = O(n log n)

对了,对于递归的情况,如果是 尾调用 递归,则整体空间复杂度和空间复杂度最大的那一层相等。

如果没有 尾调用 ,由于调用栈一直保留,会导致空间占用为所有调用层占用只和!!!例如每一层的复杂度为O(n),而需要递归 n 次,则空间复杂度提升为了 O(n ^ 2)!!!

这也是有些写的不好的递归会导致内存溢出的原因。

辅助空间复杂度

术语辅助空间是指除被输入数据占据的空间之外使用的存储空间。 例如,对于平衡二叉树深度优先搜索算法,若二叉树有个节点,则其辅助空间复杂度是O(logn)。

要时间还是要空间?

经过今天和昨天的内容我们可以发现,在评估复杂度的时候都是单独评估的,评估时间复杂度不考虑空间复杂度,评估空间复杂度不考虑时间复杂度。那么两者是成正比关系的嘛?

答案是否定的,大部分情况,时间复杂度和空间复杂度会成反比。也就是时间复杂度比较低的时候,可能就会导致空间复杂度比较高。反之亦然。

当然,有些很差的算法时间空间都占用很高,这种的就不在讨论范围之内了🤣

所谓鱼和熊掌不可兼得,很多时候我们需要在时间和空间之间来做选择。大家可能都听过用空间来换时间这种说法,这就是一种取舍。

一般来说,时间会更重要一点,想想我们平时写程序是不是经常要求更快?专门要求占用内存更小的场景不是很常见(chrome 笑了)。毕竟时间就是金钱嘛?给用户节省了时间就为公司赢得了金钱。

结语

了解时间复杂度和空间复杂度很有必要,了解如何计算这两者可以很好的对自己写出来的算法进行评估,也能够去为优化算法提供方向。

关于这两者就说到这里了,下一篇将简单介绍一下数据结构基础。

本文为系列文章,将和大家一起逐步的学习前端算法相关知识。

系列所有文章都将收录在 专栏 里方便大家查看,大家可以关注我或者收藏本专栏。