Files
final-exam/算法设计与分析/复习文档/算法基础与复杂度分析.md
T

214 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- 算法设计与分析
- 复习
- 复杂度分析
create time: 2026-06-10 11:11
---
# 算法基础与复杂度分析
## 概述
本文系统梳理算法的基本概念、四个特性、算法三要素、时间复杂度的计算与排序,以及递归与迭代的关系。这是算法课程的基石,掌握这些内容才能顺利理解后续的分治法、贪心算法、动态规划等高级策略。
## 正文
### 一、算法的定义与四个特性
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列。每个指令表示一个或多个操作。
一个合法的算法必须满足以下**四个特性**:
| 特性 | 含义 | 反例 |
|------|------|------|
| **有穷性** | 算法必须在执行有限步之后终止 | 死循环 |
| **确定性** | 每条指令的含义明确、无二义性 | 模糊的自然语言描述 |
| **可行性** | 每条指令都是可执行的基本操作 | 无法实现的操作 |
| **输入/输出** | 有零个或多个输入,有一个或多个输出 | 无输出的程序 |
> [!question] 为什么算法可以没有输入,但不能没有输出?
> 因为算法的目的是解决问题并给出结果。没有输入意味着算法不依赖外部数据(如计算 π 的某一位),但无论如何必须有输出才能体现其价值。
### 二、算法三要素
算法的三个核心要素是:**运算**、**控制结构**和**数据结构**。
> [!warning] 常见误区
> 很多同学把"输入/输出"当作算法三要素之一,这是错误的。算法三要素是运算、控制结构和数据结构。
- **运算**:算法中执行的每一种操作
- **控制结构**:三种基本结构——**顺序**、**选择**(if-else)、**循环**(for/while)
- **数据结构**:算法处理的数据的组织方式
### 三、算法描述方法
描述算法的常见方式有三种:
1. **自然语言**:简单直观,但容易产生歧义
2. **流程图**:图形化表示,直观清晰。注意:**菱形表示判断/选择**
3. **伪代码**:介于自然语言和编程语言之间,兼顾可读性和精确性
```cpp
#include <vector>
using namespace std;
// 查找数组中的最小值
int findMin(vector<int>& A, int n) {
int minVal = A[0];
for (int i = 1; i < n; i++) {
if (A[i] < minVal) {
minVal = A[i];
}
}
return minVal;
}
```
> [!tip] 流程图关键符号
> - **矩形**:处理/运算
> - **菱形**:判断/选择(这是一个高频考点)
> - **平行四边形**:输入/输出
> - **椭圆/圆角矩形**:开始/结束
### 四、时间复杂度分析
#### 4.1 基本概念
时间复杂度描述的是算法运行时间随输入规模增长的**增长趋势**,我们使用大 O 表示法。
#### 4.2 常见复杂度排序
从低到高排列,这是必须记住的核心序列:
$$O(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(2^n) < 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ⁿ) | 指数 | Fibonacci递归 | 10³⁰¹(天文数字) |
| O(n!) | 阶乘 | 全排列 | 不可计算 |
#### 4.3 复杂度增长趋势
```mermaid
xychart-beta
title "Time Complexity Growth Curves"
x-axis "Input Size n" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "Operations" 0 --> 30
line "O(1)" [1, 1, 1, 1, 1, 1, 1, 1]
line "O(logn)" [0, 1, 1.6, 2, 2.3, 2.6, 2.8, 3]
line "O(n)" [1, 2, 3, 4, 5, 6, 7, 8]
line "O(nlogn)" [0, 2, 4.8, 8, 11.6, 15.5, 19.6, 24]
line "O(n^2)" [1, 4, 9, 16, 25, 36, 49, 64]
```
#### 4.4 常见算法复杂度分析
| 算法 | 复杂度 | 分析思路 |
|------|--------|----------|
| 查找n个数最小值 | O(n) | 需要遍历所有元素一次 |
| 冒泡排序 | O(n²) | 两层嵌套循环 |
| 二分查找 | O(logn) | 每次将搜索范围缩小一半 |
| 归并排序 | O(n logn) | 递归深度 logn,每层合并 O(n) |
| Fibonacci递归 | O(2ⁿ) | 每次调用产生2个子调用 |
### 五、递归与Fibonacci复杂度详解
#### 5.1 Fibonacci递归的调用树
Fibonacci递归算法的时间复杂度是 O(2ⁿ)。让我们具体分析 f(5) 的递归调用次数:
```plaintext
f(5)
/ \
f(4) f(3)
/ \ / \
f(3) f(2) f(2) f(1)
/ \
f(2) f(1)
/ \
f(1) f(0)
```
> [!question] Fibonacci递归算法的时间复杂度为什么是O(2^n)?
> 因为每次递归调用都会产生**两个子调用**(f(n-1) 和 f(n-2)),形成一棵近似满二叉树。树的节点总数约为 2^n 量级,因此总时间复杂度为 O(2^n)。
> [!question] f(5) 需要多少次递归调用(不含基本运算)?
> 从上面的调用树可以看出,f(5) 展开后共有 **15个节点**(含重复计算)。其中 f(3) 被计算了2次,f(2) 被计算了3次。大量重复计算正是Fibonacci递归效率低下的原因。
#### 5.2 递归方程
对于递归算法,我们常用**递归方程**来描述其时间复杂度:
```
T(n) = T(n-1) + T(n-2) + 1
T(0) = 0, T(1) = 0
```
这个递归方程的解约为 O(2ⁿ),与Fibonacci数本身的增长速度一致。
### 六、递归与迭代
#### 6.1 核心定理
> [!tip] 递归与迭代的等价性
> **每个递归算法都可以转换为迭代算法。** 递归使用函数调用栈,迭代使用显式循环。递归更直观,但迭代通常更节省空间。
#### 6.2 栈数据结构
递归调用的底层依赖**栈(Stack)**数据结构:
- **LIFO**(Last In First Out,后进先出)
- 每次递归调用,将当前函数的局部变量和返回地址压入栈
- 每次返回,从栈顶弹出恢复现场
- **DFS(深度优先搜索)** 也使用栈实现
```cpp
// Fibonacci的迭代版本(时间O(n),空间O(1))
int fibIterative(int n) {
if (n <= 1) return n;
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
```
迭代版本将时间复杂度从 O(2ⁿ) 降至 O(n),空间复杂度从 O(n)(递归栈)降至 O(1)。
#### 6.3 迭代加深
**迭代加深(Iterative Deepening)** 是一种限制递归深度的策略,常用于搜索算法:
- 设定最大深度 limit
- 从深度 0 开始,逐步增加 limit
- 每次进行深度受限的DFS
- 结合了DFS的空间效率和BFS的完备性
> [!warning] 迭代加深与递归的关系
> 迭代加深本质上还是递归,只是人为限制了递归的深度。它不是把递归转成迭代,而是在递归框架内控制搜索深度。
### 七、递归调用次数的计算方法
计算递归调用次数是考试高频考点。通用方法:
1. **画出递归树**,从根节点开始展开
2. **统计节点总数**(每个节点代表一次调用)
3. **注意重复计算**:相同子问题被多次计算的节点要重复计数
| 问题 | 递归方程 | 调用次数 |
|------|----------|----------|
| Fibonacci f(n) | T(n) = T(n-1) + T(n-2) + 1 | ~2ⁿ |
| 汉诺塔 n个盘 | T(n) = 2T(n-1) + 1 | 2ⁿ - 1 |
| 二分查找 | T(n) = T(n/2) + 1 | ~log₂n |
## 关联笔记
- [[算法设计与分析/试题册/index|试题册索引]]