1.时间复杂度和空间复杂度
空间复杂度和时间复杂度详解
时间复杂度
定义
时间复杂度 :描述算法执行时间随输入数据规模增长的变化趋势。
常用表示法(大O表示法)
常见时间复杂度(从小到大):
| 复杂度 | 名称 | 示例 |
|---|---|---|
| O(1) | 常数时间 | 数组索引访问 |
| O(log n) | 对数时间 | 二分查找 |
| O(n) | 线性时间 | 遍历数组 |
| O(n log n) | 线性对数时间 | 快速排序、归并排序 |
| O(n²) | 平方时间 | 冒泡排序、嵌套循环 |
| O(2ⁿ) | 指数时间 | 斐波那契数列(递归) |
| O(n!) | 阶乘时间 | 旅行商问题 |
记住:O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!)
示例代码分析
javascript
// O(1) 常数时间复杂度
//执行时间与输入规模无关,始终固定。
function getFirstElement(arr) {
return arr[0]; // 无论数组多大,只执行一次操作
}
//分析:无论数组有多大,只需要访问第一个元素,操作次数恒定。
// O(n) 线性时间复杂度
function sumArray(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i]; // 执行次数 = arr.length
}
return total;
}
console.log(sumArray([1, 2, 3, 4, 5])); // 输出 15
//分析:循环执行次数与数组长度成正比,输入规模n倍增,执行时间也约n倍增。
// O(n²) 平方时间复杂度
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) { // 外层循环 n 次
for (let j = 0; j < arr.length - 1; j++) { // 内层循环 n-1 次
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr; // 总操作次数 ≈ n * (n-1) ≈ O(n²)
}
//分析:两层嵌套循环,最坏情况下需要比较n*(n-1)次,时间复杂度≈为O(n²)。
// O(log n) 对数时间复杂度
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
const 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; // 总操作次数 ≈ log₂n
}
//分析:每次循环将搜索范围减半,执行次数约为log₂n,时间复杂度为O(log n)。
时间复杂度计算规则
- 忽略常数项:O(2n) = O(n)
- 保留最高阶项:O(n² + n) = O(n²)
- 循环嵌套相乘 ,顺序执行相加
javascript
// O(n + m) 时间复杂度
function twoLoops(n, m) {
for (let i = 0; i < n; i++) {} // O(n)
for (let j = 0; j < m; j++) {} // O(m)
// 总复杂度: O(n + m)
}
// O(n × m) 时间复杂度
function nestedLoops(n, m) {
for (let i = 0; i < n; i++) { // O(n)
for (let j = 0; j < m; j++) { // O(m)
// 操作
}
}
// 总复杂度: O(n × m)
}
空间复杂度
定义
空间复杂度 :描述算法占用内存空间随输入数据规模增长的变化趋势。
常见空间复杂度
| 复杂度 | 描述 | 示例 |
|---|---|---|
| O(1) | 常数空间 | 使用固定数量的变量 |
| O(n) | 线性空间 | 创建与输入等长的数组 |
| O(n²) | 平方空间 | 创建 n×n 的二维数组 |
| O(log n) | 对数空间 | 递归调用栈深度 |
示例代码分析
javascript
// O(1) 常数空间复杂度
function sum(arr) {
let total = 0; // 1个变量
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total; // 只用了2个变量,与输入规模无关
}
//分析:只使用了total和i两个变量,空间占用不随输入数组大小变化。
// O(n) 线性空间复杂度
function copyArray(arr) {
const copy = []; // 创建新数组
for (let i = 0; i < arr.length; i++) {
copy.push(arr[i]); // 占用空间与输入大小成正比
}
return copy; // 空间复杂度 O(n)
}
//分析:创建了一个与输入数组等长的新数组,空间复杂度为O(n)。
// O(n²) 平方空间复杂度
function createMatrix(n) {
const matrix = [];
for (let i = 0; i < n; i++) {
matrix[i] = [];
for (let j = 0; j < n; j++) {
matrix[i][j] = i + j; // 创建 n×n 的二维数组
}
}
return matrix; // 空间复杂度 O(n²)
}
//分析:创建了一个n×n的二维数组,空间复杂度为O(n²)。
// O(log n) 空间复杂度(递归深度)
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
if (left > right) return -1;
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
return binarySearchRecursive(arr, target, mid + 1, right);
} else {
return binarySearchRecursive(arr, target, left, mid - 1);
}
// 递归深度 = 调用栈深度 = log₂n
}
时间与空间复杂度权衡
javascript
// 示例:查找数组中的重复元素
// 方法1:时间复杂度 O(n²),空间复杂度 O(1)
function findDuplicateBruteForce(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) return arr[i];
}
}
return null;
// 时间: O(n²) - 嵌套循环
// 空间: O(1) - 只用常数个变量
}
// 方法2:时间复杂度 O(n log n),空间复杂度 O(1)
function findDuplicateSort(arr) {
arr.sort((a, b) => a - b); // O(n log n)
for (let i = 1; i < arr.length; i++) { // O(n)
if (arr[i] === arr[i - 1]) return arr[i];
}
return null;
// 时间: O(n log n + n) ≈ O(n log n)
// 空间: O(1) - 原地排序
}
// 方法3:时间复杂度 O(n),空间复杂度 O(n)
function findDuplicateHash(arr) {
const seen = new Set(); // O(n) 空间
for (let i = 0; i < arr.length; i++) { // O(n) 时间
if (seen.has(arr[i])) return arr[i];
seen.add(arr[i]);
}
return null;
// 时间: O(n)
// 空间: O(n)
}
// 方法4:时间复杂度 O(n),空间复杂度 O(1) - 但有限制
function findDuplicateFloyd(arr) {
// 使用弗洛伊德循环检测算法
// 要求:数组元素在 1..n 范围内,且只有一个重复
let slow = arr[0];
let fast = arr[0];
do {
slow = arr[slow];
fast = arr[arr[fast]];
} while (slow !== fast);
slow = arr[0];
while (slow !== fast) {
slow = arr[slow];
fast = arr[fast];
}
return slow;
// 时间: O(n)
// 空间: O(1) - 只用几个变量
}
实际应用中的复杂度分析
React/Vue 组件
javascript
// O(n) 时间,O(1) 空间
function ListComponent({ items }) {
return (
<ul>
{items.map(item => ( // O(n) 时间
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// O(n²) 时间(潜在性能问题)
function NestedListComponent({ data }) {
return (
<div>
{data.map(item => ( // O(n)
<div key={item.id}>
{item.children.map(child => ( // O(m) - 嵌套
<span key={child.id}>{child.name}</span>
))}
</div>
))}
</div>
);
// 总复杂度: O(n × m)
}
API 请求处理
javascript
// 低效:O(n²) 时间
async function getUsersWithPosts(users) {
const result = [];
for (const user of users) { // O(n)
const posts = await fetchPosts(user.id); // O(m) API调用
result.push({ ...user, posts });
}
return result; // 总: O(n × m) 时间
}
// 高效:O(n + m) 时间
async function getUsersWithPostsOptimized(users) {
// 批量获取所有用户的帖子
const postPromises = users.map(user =>
fetchPosts(user.id) // 并行请求
);
const allPosts = await Promise.all(postPromises); // O(n) 并行
return users.map((user, index) => ({ // O(n)
...user,
posts: allPosts[index]
}));
}
性能优化建议
时间优化技巧
- 缓存计算结果(记忆化)
- 使用哈希表代替线性查找
- 排序后使用二分查找
- 避免嵌套循环
空间优化技巧
- 原地操作(修改原数组而非创建新数组)
- 流式处理大数据集
- 及时释放引用(设置为 null)
- 使用位运算存储状态
复杂度分析工具
javascript
// 性能测试示例
function measurePerformance(fn, input) {
const startTime = performance.now();
const result = fn(input);
const endTime = performance.now();
console.log(`执行时间: ${endTime - startTime}ms`);
console.log(`输入规模: ${input.length}`);
console.log(`结果: ${result}`);
return result;
}
总结
- 时间复杂度 关注速度 ,空间复杂度 关注内存
- 一般优先优化时间复杂度,除非内存受限
- 根据实际场景选择最优方案(时间 vs 空间权衡)
- 理解复杂度有助于写出更高效、可扩展的代码