大家好,我是杨成功。
算法复杂度是区分算法性能的一个总体指标。当我们实现一个算法的时候,还要评估本次实现的性能是否过关。如何评估算法实现的性能呢?主要是从时间和空间两个方面入手。
时间方面,算法大概执行了多少次,一般执行的次数越多耗时越长,这种评价依据叫时间复杂度
。
空间方面,算法执行过程中占用了多少内存,一般动态创建或修改变量会增加内存,这种评价依据叫空间复杂度
。
我们从一个 demo 开始,请看下面代码一共执行了多少次。
function traverse(arr) {var len = arr.length;for (var i = 0; i < len; i++) {console.log(arr[i]);}}
首先,这一句只执行一次:
var len = arr.length;
接着是一个 for 循环,关于 for 循环的执行次数,可以记住这个规则:
那么,将循环拆解,其执行次数如下:
var i = 0; // 1 次i < len; // n+1 次i++; // n 次,与循环体执行次数一样
统计该函数的执行次数,大致如下:
T(n) = 1 + 1 + (n+1) + n + n = 3n + 3
虽然详细的代码执行次数能精确表示代码执行时间,但这样计算显然过于繁琐。时间复杂度只是反映一个总体趋势,对于逐次递增/递减的循环来说,我们只需要关注指数,其他数据可以全部去掉。
将表达式 3n + 3
的系数(n)和常数(3)去掉,得出时间复杂度为:
O(n) = n// 简写为O(n)
这么来看,时间复杂度是可以通过肉眼来判断的。再写一个双层循环:
function traverse(arr) {var len = arr.length;for (let i = 0; i < len; i++) {for (let j = len; j > 0; j--) {console.log(arr[i], arr[j - 1]);}}}
代码中循环了两层,因此可直接推断出时间复杂度为 n * n
,表示如下:
O(n) = n * n// 简写为 n 的 2 次方O(n^2)
因此,代码中循环了 m 层,时间复杂度就是 O(n^m)
。如果代码中没有循环,那么时间复杂度就是 O(1)
。
理解了时间复杂度的表达规则,空间复杂度也一致,常见的空间复杂度也包括:
O(1)、O(n)、O(n^2)
时间复杂度,通过循环的层级来确定 n;空间复杂度,则通过在循环中是否申请/修改变量来确定 n。
我们再看这个函数:
function traverse(arr) {var len = arr.length;for (var i = 0; i < len; i++) {console.log(arr[i]);}}
虽然函数中有循环,但是循环体内并没有申请/修改变量,变量申请只有 2 次:
var len = arr.length;var i = 0;
那么,这个函数的空间复杂度即为 O(1)
。
如果将函数修改成这样:
function traverse(arr) {var len = arr.length;var new_arr = [];for (var i = 0; i < len; i++) {new_arr[i] = arr[i];}}
在循环中数组 new_arr
的长度不断被加大,内存占用不断变高,因此它的空间复杂度为 O(n)
。