【js-算法】时间复杂度、空间复杂度快速入门

425 阅读2分钟

大 O 计算法

算法的复杂度通常用大 O 符号表述,定义为 T(n) = O(f(n))

  • f(n) 可以有的值:复杂度由好到坏
常数型 O(1)
对数型 O(log n)
线性型 O(n)
线性对数型 O(n log n)
平方型 O(n^2)、立方型 O(n^3)、K 次方型 O(n^k)
平方底指数型 O(2^n)、立方底指数型 O(3^n)、K 次底指数型 O(k^n)
阶乘型 O(n!)

记得小时候老爸教过我的话,学好数理化,走遍天下都不怕

时间复杂度

时间复杂度是指执行这个算法所需要的计算工作量,其复杂度反映了程序执行时间随输入规模增长而增长的量级

计算方法

  • 时间复杂度只关注最高数量级,且与之系数也没有关系。
  • 时间复杂度分析的基本策略是:从内向外分析,从最深层开始分析;如果遇到函数调用,就要深入函数进行分析。
  1. 算法执行所需要的临时空间,不随着变量大小而变化,空间复杂度为一个常量,可以表示为 O(1)
function sum() {
  const a = 1;
  const b = 2;
  return a + b;
}
  1. 代码执行次数为 x,n 为目标数,符合 2^x = n,所以 x = log2(n)(logn),即时间复杂度为 O(logn)
function fun(n) {
  let i = 1;
  while (i < n) {
    i = i * 2;
  }
}
  1. 一个循环,时间复杂度通常为 O(n)
function fun(n) {
  let sum = 0;
  for (let i = 0; i < n.length; i++) {
    sum += n[i];
  }
  return sum;
}
  1. O(logn) 的基础上,再执行一个 n 次循环,即是 O(nlogn)
function fun(n) {
  for (let j = 0; j < n; j++) {
    let i = 1;
    while (i < n) {
      i = i * 2;
    }
  }
}
  1. 循环嵌套,比如我们常见的冒泡排序算法,时间复杂度就是 O(n^2)
function sort(arr) {
  for (var i = 0; i < arr.length - 1; i++) {
    for (var j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        var temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}
  1. 斐波那契,使用递归的情况下,因为使用了两次递归,时间复杂度为 O(2^n)
function fib(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

注意:在 leetcode 使用递归解法,会直接超时,因为 n 越大,计算量巨大,所以官方解法用了动态规划,时间复杂度为 O(n)、或者使用矩阵快速幂,时间复杂度为 O(log n)

  1. 下例,时间复杂度为 O(n!),基本不能称作为算法,n 越大,就容易卡死,小心尝试
function fun(n) {
  console.log(n);
  for (let i = 0; i < n; i++) {
    fun(n - 1);
  }
}

空间复杂度

算法的空间复杂度指的是在运行过程中临时占用的存储空间大小的量度

  • 代码所占用的空间:与算法本身的书写长短有关

  • 输入数据所占用的空间:调用函数时传递而来,与算法本身无关

  • 辅助变量所占用的空间

一个算法的空间复杂度只考虑在运行过程中为局部变量所分配的存储空间的大小,它包括为参数表中形参变量分配的存储空间、在函数体中定义的局部变量分配的存储空间两个部分

计算方法

空间复杂度常见的为以下三个例子

  1. 所需要的临时空间不随着某个变量 n 的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
function sum() {
  const a = 1;
  const b = 2;
  return a + b;
}
  1. 下例,定义一个数组空间,数组的长度随着 n 的规模不同,会不一样,这里空间复杂度为 O(n)
function fun(n) {
  let arr = [];
  for (let i = 0; i < n.length; i++) {
    arr.push(n[i]);
  }
  return arr;
}
  1. 下例,最终形成一个二维数组空间,空间复杂度为 O(n^2)
function fun(n) {
  const arr = [];
  for (let i = 0; i < n; i += 1) {
    arr.push([]);
    for (let j = 0; j < n; j += 1) {
      arr[i].push(j);
    }
  }
}

总结

  • 时间复杂度和空间复杂度仅仅代表了一种趋势,是一种预估

  • 好的算法往往更注重时间复杂度,空间复杂度在一个合理的范围即可

补充知识

  • 数学中 log 表示对数

  • 如果 a^n = b(a>0,且 a≠1),那么数 n 叫做以 a 为底 b 的对数,记做 n = log(a)b,【a 是下标】其中,a 叫做底数,b 叫做真数

  • 一般地,函数 y=logax(a>0,且 a≠1)叫做对数函数,也就是说以幂(真数)为自变量,指数为因变量,底数为常量的函数,叫对数函数

  • ln,自然对数以常数 e 为底数的对数。

  • lg,(以 10 为底的对数)对数函数 lg,是以 10 为底的对数(常用对数),如 lg 10 = 1。`lg 即为 log10