算法学习仓库。
复杂度是衡量算法效率的指标,描述算法消耗的资源(时间或空间)如何随输入规模增长。
定义:算法执行所需的基本操作次数,用大 O 表示法描述其增长趋势。
如何计算:
- 找出基本操作(比较、赋值、算术运算等)
- 统计操作次数与输入规模 n 的关系
- 保留最高阶项,忽略常数系数
常见复杂度(从快到慢):
| 复杂度 | 名称 | 示例 | n=1000 时操作数 |
|---|---|---|---|
| O(1) | 常数 | 数组取值、哈希查找 | 1 |
| O(log n) | 对数 | 二分查找 | ~10 |
| O(n) | 线性 | 遍历数组 | 1,000 |
| O(n log n) | 线性对数 | 快排、归并排序 | ~10,000 |
| O(n²) | 平方 | 冒泡排序、双重循环 | 1,000,000 |
| O(2ⁿ) | 指数 | 暴力递归斐波那契 | 天文数字 |
计算示例:
// O(1) - 常数时间
function getFirst(arr) {
return arr[0]; // 无论数组多大,只执行一次
}
// O(n) - 线性时间
function sum(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) { // 循环 n 次
total += arr[i];
}
return total;
}
// O(n²) - 平方时间
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) { // 外层 n 次
for (let j = 0; j < arr.length - 1; j++) { // 内层 n 次
// ... // 总共 n × n = n²
}
}
}
// O(log n) - 对数时间
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
// 每次循环砍掉一半,所以是 log₂(n) 次
let mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}定义:算法执行过程中额外占用的内存空间。
如何计算:
- 统计额外创建的变量、数组、对象
- 递归算法需计算调用栈深度
常见情况:
| 复杂度 | 场景 |
|---|---|
| O(1) | 只用几个变量,原地操作 |
| O(n) | 创建与输入等长的数组 |
| O(log n) | 递归深度为 log n(如二分) |
| O(n) | 递归深度为 n(如链表递归) |
示例:
// O(1) 空间 - 原地交换
function swap(arr, i, j) {
let temp = arr[i]; // 只用了一个临时变量
arr[i] = arr[j];
arr[j] = temp;
}
// O(n) 空间 - 创建新数组
function double(arr) {
const result = []; // 新数组长度与输入相同
for (let x of arr) {
result.push(x * 2);
}
return result;
}
// O(n) 空间 - 递归调用栈
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 递归 n 层,栈深度 O(n)
}同一算法在不同输入下表现可能不同:
| 情况 | 含义 | 快排示例 |
|---|---|---|
| 最好 | 理想输入下的复杂度 | O(n log n),每次均匀分割 |
| 最坏 | 最差输入下的复杂度 | O(n²),每次选到最大/最小值 |
| 平均 | 随机输入的期望复杂度 | O(n log n) |
实践建议:面试和工程中通常关注最坏情况,因为它决定了算法的可靠性。
定义:相等元素排序后是否保持原有相对顺序。
原数组:[{name: "A", age: 20}, {name: "B", age: 20}]
按 age 排序后:
稳定:A 仍在 B 前面
不稳定:A、B 顺序可能交换
| 稳定排序 | 不稳定排序 |
|---|---|
| 冒泡、插入、归并 | 快排、堆排、选择 |
- 看循环:单层循环 O(n),嵌套循环相乘
- 看递归:画递归树,统计节点数
- 看分治:用主定理 T(n) = aT(n/b) + f(n)
- 看数据结构:哈希 O(1),树 O(log n),数组遍历 O(n)
| 算法 | 时间(平均) | 时间(最坏) | 空间 | 稳定 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | ✓ |
| 选择排序 | O(n²) | O(n²) | O(1) | ✗ |
| 插入排序 | O(n²) | O(n²) | O(1) | ✓ |
| 快速排序 | O(n log n) | O(n²) | O(log n) | ✗ |
| 归并排序 | O(n log n) | O(n log n) | O(n) | ✓ |
| 堆排序 | O(n log n) | O(n log n) | O(1) | ✗ |
| 二分查找 | O(log n) | O(log n) | O(1) | - |
| 哈希查找 | O(1) | O(n) | O(n) | - |