描述算法运行时间随输入规模 n 增长而变化的趋势。
我们通常使用大O表示法 (Big O notation),例如 O(n), O(n²), O(log n)。
时间复杂度关注的是算法执行的基本操作次数的增长级别,而不是具体的毫秒数,因为它会受硬件、编程语言等因素影响。
描述算法在运行过程中临时占用的额外存储空间随输入规模 n 增长而变化的趋势。
空间复杂度计算的是算法本身需要的辅助空间,不包括存储输入数据所占用的空间。
例如,如果算法只需要固定数量的几个变量,与输入大小无关,则空间复杂度为 O(1)。
方法一 (O(n)):从第一页逐字往后翻,直到找到目标单词。如果字典厚一倍 (n变大),查找时间可能翻倍(线性关系)。
方法二 (O(log n)):利用目录或字母索引,每次查找都能排除掉大约一半的页数。即使字典厚很多,查找次数增加得也很慢(对数关系)。
方法一 (O(1)):原地整理,不需要额外的桌子或空间存放临时取出的书。只需要少量固定空间(比如手里拿几本书)。
方法二 (O(n)):先把所有书都搬到一张大桌子上,再按顺序放回书架。需要的额外桌面空间与书的数量成正比(线性关系)。
从快到慢排序,展示不同复杂度随输入规模 n 增长的趋势:
执行时间不随输入规模 n 的变化而变化。无论 n 多大,操作次数都是固定的。
例子:访问数组中指定索引的元素 arr[i]
,哈希表(理想情况下)的插入和查找。
执行时间随 n 的对数增长。通常出现在每次操作都将问题规模减半的算法中。
例子:二分查找在一个有序数组中查找元素。
执行时间与输入规模 n 成正比。n 增大几倍,时间大致也增加几倍。
例子:遍历一个数组或链表的所有元素,线性查找。
执行时间是 n 乘以 `log n`。常见于高效的排序算法。
例子:归并排序、快速排序(平均情况)、堆排序。
执行时间与 n 的平方成正比。通常涉及嵌套循环,每个循环都依赖于 n。
例子:冒泡排序、选择排序、插入排序,遍历二维数组。
执行时间随 n 呈指数增长。这类算法通常非常慢,只适用于 n 很小的情况。
例子:生成一个集合的所有子集,没有优化的递归计算斐波那契数列。
假设我们在下面的数组中查找数字 7
:
从数组第一个元素开始,逐个比较,直到找到目标或遍历完所有元素。
前提:数组必须有序! 每次查找中间元素,根据比较结果缩小一半查找范围。
对以下无序数组进行排序:
重复遍历数组,比较相邻元素,如果顺序错误就交换,直到没有元素需要交换。
选择一个“基准”元素,将数组分区:小于基准的放左边,大于的放右边。然后递归地对左右子数组进行排序。
递归调用栈 (近似演示空间复杂度 O(log n) ~ O(n)):
递归函数在调用时,系统需要使用调用栈来保存每次调用的上下文(如局部变量、返回地址)。
递归的深度直接影响了调用栈所占用的空间。如果递归深度是 `d`,那么空间复杂度至少是 O(d)。
重要:即使函数本身没有显式申请额外内存,递归调用栈也构成空间开销。
每次调用 `fib(k)` 都会产生新的栈帧,最大深度约为 n。
只需要固定的几个变量 (如 a, b, temp) 来存储中间结果。
你的得分是:0 / 0
实现一个 C++ 函数 sumArray(const std::vector
分别用递归和迭代两种方式实现 C++ 函数计算非负整数 n 的阶乘 (`n!`)。比较并分析两种实现的空间复杂度差异。
恭喜你完成了本次学习!以下是关键要点:
1. 时间复杂度:衡量算法执行时间随输入规模增长的趋势,常用大O表示法。
2. 空间复杂度:衡量算法所需额外存储空间随输入规模增长的趋势。
3. 常见复杂度级别: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ) 等,效率依次降低。
4. 典型示例:理解线性搜索(O(n)) vs 二分查找(O(log n)),冒泡排序(O(n²)) vs 快速排序(O(n log n)) 的效率差异。
5. 递归与空间:递归调用会产生栈空间开销,影响空间复杂度,有时迭代是更优选择(如斐波那契)。
实践建议:在编写代码时,养成分析复杂度的习惯,尤其是在处理大数据量时,选择更优的算法能显著提升程序性能!