算法
1. 算法复杂度
前言
算法复杂度旨在计算在输入数据量 NN 的情况下,算法的「时间使用」和「空间使用」情况;体现算法运行使用的时间和空间随「数据大小 NN 」而增大的速度。
算法复杂度主要可从 时间 、空间 两个角度评价:
- 时间: 假设各操作的运行时间为固定常数,统计算法运行的「计算操作的数量」 ,以代表算法运行所需时间;
- 空间: 统计在最差情况下,算法运行所需使用的「最大空间」;
「输入数据大小 N 」指算法处理的输入数据量;根据不同算法,具有不同定义,例如:
- 排序算法: N 代表需要排序的元素数量;
- 搜索算法: N 代表搜索范围的元素总数,例如数组大小、矩阵大小、二叉树节点数、图节点和边数等;
接下来,本章将分别从概念定义、符号表示、常见种类、时空权衡、示例解析、示例题目等角度入手,介绍「时间复杂度」和「空间复杂度」。
1.1 时间复杂度
概念定义
根据定义,时间复杂度指输入数据大小为 NN 时,算法运行所需花费的时间。需要注意:
- (即执行的次数)统计的是算法的「计算操作数量」,而不是「运行的绝对时间」。计算操作数量和运行绝对时间呈正相关关系,并不相等。算法运行时间受到「编程语言 、计算机处理器速度、运行环境」等多种因素影响。例如,同样的算法使用 Python 或 C++ 实现、使用 CPU 或 GPU 、使用本地 IDE 或力扣平台提交,运行时间都不同。
- (跟常数无关)体现的是计算操作随数据大小 N 变化时的变化情况。假设算法运行总共需要「 1次操作」、「 100 次操作」,此两情况的时间复杂度都为常数级 O(1)O(1) ;需要「 N 次操作」、「 100N 次操作」的时间复杂度都为 O(N)O(N) 。
符号表示
根据输入数据的特点,时间复杂度具有「最差」、「平均」、「最佳」三种情况,分别使用 O ,Θ , Ω 三种符号表示。以下借助一个查找算法的示例题目帮助理解。
题目: 输入长度为 N 的整数数组 nums ,判断此数组中是否有数字 7 ,若有则返回 true ,否则返回 false。
解题算法: 线性查找,即遍历整个数组,遇到 7则返回 true 。
代码:
boolean findSeven(int[] nums) { for (int num : nums) { if (num == 7) return true; } return false; }
- 最佳情况Ω(1) : nums = [7, a, b, c, …] ,即当数组首个数字为 7 时,无论 nums 有多少元素,线性查找的循环次数都为 1 次;
- 最差情况 O(N) : nums = [a, b, c, …] 且 nums 中所有数字都不为 7 ,此时线性查找会遍历整个数组,循环 N次;
- 平均情况Θ : 需要考虑输入数据的分布情况,计算所有数据情况下的平均时间复杂度;例如本题目,需要考虑数组长度、数组元素的取值范围等;
1.2 空间复杂度
概念定义
空间复杂度涉及的空间类型有:
- 输入空间: 存储输入数据所需的空间大小;
- 暂存空间: 算法运行过程中,存储所有中间变量和对象等数据所需的空间大小;
- 输出空间: 算法运行返回时,存储输出数据所需的空间大小;
通常情况下,空间复杂度指在输入数据大小为 N 时,算法运行所使用的「暂存空间」+「输出空间」的总体大小。