系列文章
数据结构与算法基础知识(一):时间复杂度和空间复杂度
数据结构与算法基础知识(二):常见数据结构
数据结构与算法基础知识(三):常见排序、搜索算法
前言
算法(Agorithm) 是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。
那么我们应该如何去衡量不同算法之间的优劣呢?主要还是从算法所占用的「时间」和「空间」两个维度去考量。
- 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
- 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。
具体来讲,算法复杂度旨在计算在输入数据量 N
的情况下,算法的「时间使用」和「空间使用」情况;体现算法运行使用的时间和空间随「数据大小 N 」而增大的速度
。
- 时间复杂度: 假设各操作的运行时间为固定常数,统计算法运行的「计算操作的数量」 ,以代表算法运行所需时间;
- 空间复杂度: 统计在最差情况下,算法运行所需使用的「最大空间」;
「输入数据大小 N 」指算法处理的输入数据量
;根据不同算法,具有不同定义,例如:
- 排序算法:N 代表需要排序的元素数量;
- 搜索算法:N 代表搜索范围的元素总数,例如数组大小、矩阵大小、二叉树节点数等;
因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。下面分别介绍一下「时间复杂度」和「空间复杂度」的计算方式。
时间复杂度
时间复杂度指输入数据大小为 N 时,算法运行所需花费的时间。需要注意:
- 统计的是算法的「计算操作数量」,而不是「运行的绝对时间」。计算操作数量和运行绝对时间呈正相关关系,并不相等。算法运行时间受到「编程语言 、计算机处理器速度、运行环境」等多种因素影响。例如,同样的算法使用 JavaScript 或 C++ 实现、使用 CPU 或 GPU 、使用本地 IDE 或力扣平台提交,运行时间都不同。
- 体现的是计算操作随数据大小 N 变化时的变化情况。假设算法运行总共需要「 1 次操作」、「 100 次操作」,此两情况的时间复杂度都为常数级 O(1) ;需要「 N 次操作」、「 100N 次操作」的时间复杂度都为 O(N) 。
常见种类
根据从小到大排列,常见的算法时间复杂度主要有:
O(1) < O(log N) < O(N) < O(Nlog N) < O(N^2) < O(2^N) < O(N!)
代码示例
O(1)
let i = 0
i += 1
O(n)
for (let i = 0; i < n; i++) {
console.log(i)
}
O(1)+O(n)=O(n)
let i = 0
i += 1
for (let j = 0; j < n; j++) {
console.log(j)
}
O(n)*O(n)=O(n^2)
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
console.log(i, j)
}
}
O(logN)
for (let i = 1; i < n; i *= 2) {
console.log(i)
}
空间复杂度
既然时间复杂度不是用来计算程序具体耗时的,那么我们也应该明白,空间复杂度也不是用来计算程序实际占用的空间的。空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势。
常见种类
根据从小到大排列,常见的算法空间复杂度有:
O(1) < O(log N) < O(N) < O(N^2) < O(2^N)
代码示例
O(1)
let i = 0
i += 1
O(n)
const arr = []
for (let i = 0; i < n; i++) {
arr.push(i)
}
O(n)*O(n)=O(n^2)
const matrix = []
for (let i = 0; i < n; i++) {
matrix.push([])
for (let j = 0; j < n; j++) {
matrix[i].push(j)
}
}