递归与分治
一、概念与本质
1. 递归(Recursion)
- 定义:一个函数直接 or 间接地调用自身。
- 本质:一种控制流程(control flow),与迭代并列,是“实现手段”。
- 必须满足:①基准情形(base case) ②递推关系(recursive case)。
2. 分治(Divide & Conquer)
- 定义:算法设计范式,把规模为 n 的问题分成 k 个“互相独立、可独立求解”的子问题(通常 k≥2,且子问题规模 ≈ n/b),递归求解后再合并结果。
- 本质:面向“问题分解”的顶层思想,与动态规划、贪心并列。
二、核心特征对比
| 维度 | 递归 | 分治 |
|---|---|---|
| 关键词 | 自我调用、调用栈、基准条件 | 拆分、独立、合并 |
| 是否要求“独立子问题” | × | √(子问题无重叠) |
| 是否要求“合并结果” | × | √(Merge 步骤) |
| 是否保证更小规模 | 需要(否则无限递归) | 需要(否则无法收敛) |
| 典型复杂度模型 | T(n)=T(n-1)+O(1) 等 | T(n)=aT(n/b)+f(n) 主定理 |
| 额外空间来源 | 调用栈(最深=递归深度) | 同左,但可尾递归/迭代优化 |
三、执行流程拆解
1. 纯递归(以阶乘为例)
fact(n):
if n==0: return 1 // base
return n * fact(n-1) // self-call,无“合并”
流程:一路压栈 → 到达 base → 一路弹栈返回乘积。
2. 分治(以归并排序为例)
mergeSort(A, l, r):
if l==r: return // 1. 基础规模
mid = (l+r)//2
mergeSort(A, l, mid) // 2. 分
mergeSort(A, mid+1, r) // 2. 分
merge(A, l, mid, r) // 3. 合
流程:二分拆解 → 子序列有序 → 线性合并 → 向上返回。
四、复杂度示例
- 阶乘递归:T(n)=T(n-1)+O(1) ⇒ Θ(n) 时间,Θ(n) 栈空间。
- 归并分治:T(n)=2T(n/2)+Θ(n) ⇒ Θ(n log n) 时间,Θ(n) 额外空间(merge 数组 + log n 栈)。
- 快速幂/二分搜索:T(n)=T(n/2)+Θ(1) ⇒ Θ(log n) 时间,Θ(log n) 栈(可尾递归消掉)。
五、代码骨架模板
1. 通用递归
def recursion(参数):
if 基准情形: return 基准值
缩小参数
子结果 = recursion(缩小后参数)
return 利用子结果构造当前结果
2. 通用分治
def divide_and_conquer(参数):
if 规模足够小: return 蛮力解
拆分成 k 个子问题
for 子问题 in 子问题列表:
子结果.append(divide_and_conquer(子问题))
return merge(子结果列表)
其中 merge() 复杂度决定整个复杂度的 f(n) 项。
六、典型例子对号入座
- 只用递归,不属于分治
– 阶乘、斐波那契(朴素)、链表逆序打印、DFS 求连通块。 - 分治 + 递归
– 归并排序、快速排序、二分搜索、最大子段和(线段树/DC 法)、Strassen 矩阵乘、最近点对、FFT。
七、常见误区澄清
- “递归一定比分治慢” → 错误。递归只是实现手段,复杂度由算法本身决定。
- “分治必须二分” → 错误。可三分、四分(如 k-way 归并),只要子问题独立。
- “有了递归就不用迭代” → 错误。深递归可能栈溢出,可用显式栈或 bottom-up 消递归。
- “分治和动态规划都需要子问题” → 区别在“子问题是否重叠”。分治子问题独立,DP 子问题重叠且通常用记忆化/填表。
八、总结
- 递归:手段 → 自我调用 → 关注“终止+递推式”。
- 分治:思想 → 拆-治-合 → 关注“独立+合并”。
- 关系:分治常常用递归落地,但递归也能干别的;分治可改写成迭代(bottom-up),只要你能手动维护栈/队列。
4649

被折叠的 条评论
为什么被折叠?



