1. 确定输入规模(n)
- 明确问题规模的定义(如数组长度、矩阵维度、树节点数等)。
- 例如:排序算法中,
n
通常指待排序元素的数量。
2. 识别基本操作
- 找到算法中执行次数最多的操作(如比较、赋值、循环迭代等)。
- 例如:排序算法中的比较操作,搜索算法中的循环迭代。
3. 建立执行次数的数学表达式
- 统计基本操作的执行次数,将其表示为输入规模
n
的函数T(n)
。 - 常见情况:
- 顺序结构:执行次数相加。
- 分支结构:取最坏情况下的分支。
- 循环结构:分析循环次数与
n
的关系(重点关注循环变量如何变化)。 - 递归算法:通过递归方程(递推关系式)描述时间。
4. 用大O表示法简化
- 保留最高阶项:忽略低阶项和常数系数。
- 例如:
T(n) = 3n² + 5n + 2 → O(n²)
。
- 例如:
- 常见复杂度等级(从优到劣):
O(1)
(常数)→O(log n)
(对数)→O(n)
(线性)→O(n log n)
→O(n²)
(平方)→O(2ⁿ)
(指数)。
第一步:理解基本概念
时间复杂度:描述算法运行时间与输入规模 n 的增长关系,用 大O符号(O) 表示。
核心思想:忽略常数和低阶项,只保留最高阶项,例如 3n² + 5n + 10 → O(n²)
。
第二步:找出代码中的“基本操作”
基本操作是执行次数最多的核心操作,通常是循环或递归内的操作。
示例1:循环中的加法操作
c复制代码
int sum = 0;
for(int i=0; i<n; i++) { // 循环n次
sum += i; // ← 基本操作(执行n次)
}
时间复杂度:O(n)
第三步:分析循环结构
1. 单层循环
c复制代码
for(int i=0; i<n; i++) {
printf("%d", i); // 执行n次
}
数学表达式:n次 → O(n)
2. 双重循环(独立变量)
c复制代码
for(int i=0; i<n; i++) { // 外层n次
for(int j=0; j<m; j++) { // 内层m次
printf("%d", i*j); // 执行n×m次
}
}
数学表达式:n × m次
- 若m与n无关 → O(nm)
- 若m = n → O(n²)
3. 双重循环(变量相关)
c复制代码
for(int i=0; i<n; i++) { // 外层n次
for(int j=0; j<i; j++) { // 内层i次(i从0到n-1)
printf("%d", j); // 总次数:0+1+2+...+(n-1) = n(n-1)/2
}
}
数学推导:
总次数 = Σi=0n-1 i = n(n-1)/2
简化:保留最高阶项并去掉系数 → O(n²)
4. 对数循环(变量倍增/倍减)
c复制代码
for(int i=1; i<=n; i*=2) { // 循环次数:log₂n
printf("%d", i); // 执行log₂n次
}
数学推导:
i的变化:1 → 2 → 4 → 8 → ... → 2k ≤ n
解得 k = log₂n → O(log n)
第四步:递归算法分析
1. 单次递归调用(线性递归)
c复制代码
void func(int n) {
if(n <= 0) return;
printf("%d", n); // O(1)操作
func(n-1); // 递归调用n次
}
递推公式:
T(n) = T(n-1) + 1
T(0) = 0
解得:T(n) = n → O(n)
2. 多次递归调用(指数级复杂度)
c复制代码
int fib(int n) {
if(n <= 1) return n;
return fib(n-1) + fib(n-2); // 每次调用产生2次递归
}
递推公式:
T(n) = T(n-1) + T(n-2) + 1
近似解:T(n) ≈ 2n → O(2ⁿ)