【计算机算法与设计(5)】贪心法、分治法、动态规划的原理和问题求解

AgenticCoding·十二月创作之星挑战赛 10w+人浏览 317人参与

要点5:掌握贪心法、分治法、动态规划的原理和问题求解

📌 适合对象:算法学习者、计算机科学学生
⏱️ 预计阅读时间:80-90分钟
🎯 学习目标:理解三种重要的算法设计方法(分治法、贪心法、动态规划),掌握它们的原理、区别和应用场景
📚 参考PPT:第 6 章-PPT-N2_v2(算法设计方法)- 分治法、动态规划、贪心法相关内容


📚 学习路线图

算法设计方法
分治法
Divide and Conquer
动态规划
Dynamic Programming
贪心法
Greedy Algorithm
底、分、合
自顶而下
最优子结构
自底而上
贪心选择
局部最优
应用:排序、搜索
应用:优化问题
应用:调度、编码

本文内容一览(快速理解)

  1. 分治法:将大问题分解为小问题,递归求解,最后合并结果(底、分、合)
  2. 动态规划:自底而上求解,子问题有重叠,需要满足最优子结构特性
  3. 贪心法:每一步都做出当前最优选择,希望得到全局最优解
  4. 三种方法的比较:分解方式、子问题关系、求解顺序、适用场景
  5. 实际应用:通过具体问题理解三种方法的应用

一、分治法(Divide and Conquer):理解"分而治之"的算法设计思想

这一章要建立的基础:理解分治法是一种重要的算法设计方法,通过将复杂问题分解为更小的子问题来解决。

核心问题:当输入数据的规模很大时,问题往往变得很难解决。如何设计算法来处理大规模问题?


[!NOTE]
📝 关键点总结:分治法的核心思想是将一个规模为 n n n的问题分解为若干个规模较小的子问题,递归求解这些子问题,然后将子问题的解合并得到原问题的解。分治法要求子问题之间互不相交、相互独立,每一个子问题都是一个全新的问题。

1.1 分治法的基本思想(Basic Idea):将大问题分解为小问题

概念的本质

分治法是一种算法设计的基本方法。它的核心思想是:当输入数据的规模很小时,比如只有一个或两个数字,绝大多数问题都很容易解决。但当输入规模很大时,问题往往变得很难。

算法设计的一个基本方法就是寻找大规模问题的解与小规模问题的解之间的关系。分治法就是这种方法之一。

简单地说,分治法的做法是:

  • 将一个规模为 n n n的问题分解为若干个规模较小的子问题
  • 递归地求解这些子问题
  • 将子问题的解合并,得到原问题的解

图解说明

原问题
规模n
分解
Divide
子问题1
规模n/b
子问题2
规模n/b
子问题a
规模n/b
递归求解
Conquer
合并结果
Combine
原问题的解

💡 说明:分治法要求用完全同样的方法递归解出子问题。即,必须把这些子问题进一步分为规模更小的子问题,直至问题的规模小到可以直接解出。

类比理解

想象你要整理一个巨大的图书馆:

  1. 分解:将图书馆分成几个区域(如按楼层或按类别)
  2. 递归:对每个区域,用同样的方法继续细分和整理
  3. 合并:当每个小区域都整理好后,整个图书馆就整理完成了

实际例子

问题:计算数组[1, 2, 3, 4, 5, 6, 7, 8]的和

分治法思路:
1. 分解:[1,2,3,4] 和 [5,6,7,8]
2. 递归:分别计算两个子数组的和 → 10 和 26
3. 合并:10 + 26 = 36

这就是原问题的解!

 


1.2 分治法的三个关键步骤(Three Key Steps):底、分、合

概念的本质

分治法在解决任何问题时,都需要讲明三件事:

  1. 底(Base Case):对足够小的输入规模,如何直接解出
  2. 分(Divide):如何将输入规模为 n n n的问题分解为规模较小的子问题。子问题在形式上与原问题完全相同,只是规模小一些
  3. 合(Combine):如何通过各个子问题的解获得原问题的解

图解说明

flowchart TD
    A["问题规模n"] --> B{规模足够小?}
    B -->|是| C["直接求解<br/>(底)"]
    B -->|否| D["分解问题<br/>(分)"]
    D --> E["子问题1"]
    D --> F["子问题2"]
    D --> G["子问题a"]
    E --> H["递归求解"]
    F --> H
    G --> H
    H --> I["合并结果<br/>(合)"]
    I --> J["原问题的解"]
    C --> J
    
    style C fill:#e8f5e9
    style D fill:#fff3e0
    style I fill:#e3f2fd

💡 说明:分治法要求子问题之间互不相交、相互独立,每一个子问题都是一个全新的问题。递归调用要求每层递归都必须遵循相同的分解/合并规则。

类比理解

就像搭积木:

  • :最小的积木块(可以直接使用,不需要再分解)
  • :将大积木拆成几个小积木块
  • :把小积木块组合起来,形成完整的结构

实际例子

问题:在有序数组[1, 3, 5, 7, 9, 11, 13, 15]中查找数字7

分治法(二元搜索):
1. 底:如果数组为空或只有一个元素,直接比较
2. 分:取中间元素,将数组分为左右两部分
   - 中间元素是9,7 < 9,所以在左半部分[1,3,5,7]中查找
3. 合:递归查找左半部分,找到7,返回结果

时间复杂度:O(log n)

 


1.3 分治法的特点(Characteristics):自顶而下,子问题独立

概念的本质

分治法的特点:

  • 自顶而下:从原问题开始,逐步分解为子问题
  • 子问题独立:同层的子问题之间,相互独立、互不相交,每个子问题都是全新的(brand-new)
  • 相同规则:每层递归都必须遵循相同的分解规则,极不灵活
  • 递归求解:用完全同样的方法递归解出子问题

图解说明

分治法特点
自顶而下
Top-down
子问题独立
Independent
相同规则
Same Rule
递归求解
Recursive

💡 说明:分治法首先要确定如何将规模为n的问题分解成子问题,而且每层递归都必须遵循相同的分解规则。这种方法的缺点是:同一问题,可能面对不同的规模或输入数据时,最优的划分也许就不一样。这时,分治法就难以给出最优结果或复杂度非常高。

实际例子

归并排序(分治法):

数组:[9, 6, 2, 4, 1, 5, 3, 8]

分解过程(自顶而下):
第1层:[9,6,2,4] 和 [1,5,3,8]
第2层:[9,6]和[2,4],[1,5]和[3,8]
第3层:每个子序列只有1-2个元素

合并过程(自底而上):
第3层:排序并合并
第2层:合并两个有序子序列
第1层:合并两个有序子序列

特点:
- 子问题之间完全独立
- 每层都遵循相同的分解规则(从中间划分)
- 时间复杂度:O(n log n)

 


二、动态规划(Dynamic Programming):自底而上的优化方法

这一章要建立的基础:理解动态规划是求解优化问题的常用方法,通过自底而上的方式,枚举所有可能的分解情况,利用子问题的最优解来构造原问题的最优解。

核心问题:当问题的最优解依赖于子问题的最优解,且子问题之间有重叠时,如何高效求解?


[!NOTE]
📝 关键点总结:动态规划和分治法类似之处在于,两者都是把大问题划分为较小问题来解决,但分解的方法不同。动态规划通常用于解决优化问题,需要枚举所有可能的分解情况,并且在分解时,所有需要用到的子问题的最优解都已在前面步骤中得到了、且已存放好。采用动态规划求解的两个前提条件:1) 问题的最优解必须满足最优子结构特性;2) 问题的子问题之间有交集(subproblems share subsubproblems)。

2.1 动态规划的基本原理(Basic Principles):最优子结构和重叠子问题

概念的本质

动态规划的基本原理:

  1. 最优子结构特性:原问题的最优解是由其子问题的最优解构成的
  2. 重叠子问题:问题的子问题之间有交集,即subproblems share subsubproblems
  3. 自底而上:需要先解决所有子问题,然后逐步构造更大规模问题的解

图解说明

动态规划特点
最优子结构
Optimal Substructure
重叠子问题
Overlapping Subproblems
自底而上
Bottom-up
查表法
Memoization
原问题最优解 =
子问题最优解的组合
子问题之间有交集
需要重复计算
先解决小规模问题
再解决大规模问题
存储子问题解
避免重复计算

💡 说明:不同于动态规划,分治法执行过程中,同层的子问题之间,相互独立、互不相交,每个子问题都是全新的。动态规划需要枚举所有可能的分解情况,这是因为全局最优解到底用到什么组合,在得到最优解之前,我们预先是不知道的。

类比理解

就像建房子:

  • 最优子结构:整栋房子的最优设计 = 每个房间的最优设计 + 房间之间的最优连接
  • 重叠子问题:多个房间可能使用相同的设计模式(如相同的窗户设计)
  • 自底而上:先设计好每个房间,再考虑如何连接它们
  • 查表法:把设计好的房间方案记录下来,需要时直接查表

实际例子

动态规划 vs 分治法:

问题:计算斐波那契数列F(n)

分治法(递归):
F(n) = F(n-1) + F(n-2)
- 子问题独立:F(n-1)和F(n-2)的计算互不相关
- 重复计算:F(n-2)在计算F(n-1)时又被计算一次
- 时间复杂度:O(2^n)

动态规划:
- 自底而上:先计算F(1), F(2), ..., F(n)
- 查表法:存储已计算的F(i),避免重复计算
- 时间复杂度:O(n)

 


2.2 动态规划的具体做法(Implementation):初始化和自底而上归纳

概念的本质

动态规划的具体做法:

  1. 初始化:先把输入数据分解到最小单位,并对其求解做出解答。这是个"解"的集合,称初始解集合,用 S 0 S_0 S0表示这个初始集合,也称初始条件。

  2. 自底而上归纳:用集合 S 0 , S 1 , … , S i S_0, S_1, \ldots, S_i S0,S1,,Si中的解来求解集合 S i + 1 S_{i+1} Si+1 i ≥ 0 i \geq 0 i0)。 S i + 1 S_{i+1} Si+1 S i S_i Si规模更大。归纳进行到产生规模为 n n n的解为止。

  3. 查表法:小规模的解都会被记在一个表里,供需要时查表,以求解大规模问题的解。

  4. 归纳公式:找出 S i + 1 S_{i+1} Si+1的解和 S 0 , S 1 , … , S i S_0, S_1, \ldots, S_i S0,S1,,Si的解之间的关系是关键。表示这个关系的公式称为归纳公式。

图解说明

动态规划步骤
初始化
S₀:最小规模问题的解
归纳步骤1
S₁:用S₀求解
归纳步骤2
S₂:用S₀,S₁求解
...
归纳步骤n
Sₙ:用S₀...Sₙ₋₁求解
原问题的解
查表法
存储所有Sᵢ的解
需要时直接查表

💡 说明 S i S_i Si的下标代表子问题的规模,而子问题的规模会随着下标的增长而增长。求解 S i + 1 S_{i+1} Si+1时,到底会用到哪些小规模问题的解,和具体问题的性质有关。要求: S 0 , S 1 , … , S i , … S_0, S_1, \ldots, S_i, \ldots S0,S1,,Si,中任何一个解,都必须是所对应子问题的最优解。

类比理解

就像搭积木塔:

  • 初始化:准备好最小的积木块( S 0 S_0 S0
  • 归纳步骤1:用最小积木块搭第一层( S 1 S_1 S1
  • 归纳步骤2:用第一层和最小积木块搭第二层( S 2 S_2 S2
  • 继续:逐层往上搭,直到完成整个塔( S n S_n Sn
  • 查表:记录每一层的搭建方案,需要时直接使用

实际例子

矩阵连乘问题(动态规划):

问题:n个矩阵A₁, A₂, ..., Aₙ要连乘,找到最优的相乘顺序

初始化(S₀):
- 单个矩阵:A₁, A₂, ..., Aₙ,不需任何乘法

归纳步骤1(S₁):
- 每2个相邻矩阵相乘:A₁A₂, A₂A₃, ..., Aₙ₋₁Aₙ
- 计算每种组合的乘法次数

归纳步骤2(S₂):
- 每3个相邻矩阵相乘:A₁A₂A₃, A₂A₃A₄, ...
- 枚举所有可能的划分方式,利用S₁的结果

继续归纳...
直到Sₙ:A₁A₂...Aₙ的最优顺序

时间复杂度:O(n³)
空间复杂度:O(n²)

 


2.3 动态规划的应用:矩阵连乘问题(Matrix Chain Multiplication)

概念的本质

矩阵连乘问题是动态规划的经典应用。

问题描述

  • n n n个矩阵要连乘,当 n > 2 n > 2 n>2时,可以有很多种顺序来进行
  • 总共需要的乘法次数会因为顺序不同而相差甚多
  • 希望找到一个最优顺序使我们用最少的乘法次数完成连乘

动态规划解法

n n n个矩阵 A 1 , A 2 , A 3 , … , A n A_1, A_2, A_3, \ldots, A_n A1,A2,A3,,An的维数分别是: p 0 × p 1 p_0 \times p_1 p0×p1 p 1 × p 2 p_1 \times p_2 p1×p2 … \ldots p n − 1 × p n p_{n-1} \times p_n pn1×pn

归纳公式

考虑子问题:求 A [ i , j ] = A i A i + 1 … A j A[i, j] = A_i A_{i+1} \ldots A_j A[i,j]=AiAi+1Aj连乘的最优顺序, 1 ≤ i < j ≤ n 1 \leq i < j \leq n 1i<jn

任何算法都是把它分为两段, A [ i , k ] = A i × … × A k A[i, k] = A_i \times \ldots \times A_k A[i,k]=Ai××Ak A [ k + 1 , j ] = A k + 1 × … × A j A[k+1, j] = A_{k+1} \times \ldots \times A_j A[k+1,j]=Ak+1××Aj,分别连乘后再相乘。

O P T ( i , j ) = min ⁡ i ≤ k < j { O P T ( i , k ) + O P T ( k + 1 , j ) + p i − 1 × p k × p j } OPT(i, j) = \min_{i \leq k < j} \{OPT(i, k) + OPT(k+1, j) + p_{i-1} \times p_k \times p_j\} OPT(i,j)=ik<jmin{OPT(i,k)+OPT(k+1,j)+pi1×pk×pj}

其中 O P T ( i , j ) OPT(i, j) OPT(i,j)表示序列 A [ i , j ] A[i, j] A[i,j]在最优顺序时需要的乘法次数。

图解说明

A₁A₂...Aₙ
枚举所有划分点k
A₁...Aₖ
Aₖ₊₁...Aₙ
OPT(1,k)
OPT(k+1,n)
计算总乘法次数
选择最小的k
最优顺序

💡 说明:上述归纳公式成立的前提条件是,问题的解必须满足最优子结构特性。不知道第一层如何划分?——解决办法:枚举 k k k的所有选取可能, i ≤ k ≤ j − 1 i \leq k \leq j-1 ikj1

实际例子

矩阵连乘示例:

4个矩阵:A(25×2), B(2×40), C(40×15), D(15×30)

不同顺序的乘法次数:
1. ((AB)C)D:28,250次
2. (A(B(CD))):21,900次
3. ((AB)(CD)):50,000次
4. ((A(BC))D):13,200次
5. (A((BC)D)):3,600次 ← 最优

动态规划方法:
- 枚举所有可能的划分方式
- 利用子问题的最优解
- 时间复杂度:O(n³)

 


三、贪心法(Greedy Algorithm):局部最优的选择策略

这一章要建立的基础:理解贪心法是一种在每一步都做出当前最优选择的算法设计方法,希望通过局部最优选择得到全局最优解。

核心问题:如何在每一步都做出最优选择,从而得到全局最优解?


[!NOTE]
📝 关键点总结:贪心法可以用于解决优化问题,如果满足以下条件:1) 原问题可以分成更小的子问题;2) 最优子结构特性:原问题的最优解可以通过合并子问题的最优解而得到;3) 我们可以设计一种贪心法则,用于在每一个阶段,来确定搜索那个子集或哪些子集。贪心法通常不保证最优解,但在某些问题中可以得到最优解。

3.1 贪心法的基本思想(Basic Idea):每一步都做最优选择

概念的本质

贪心法的基本思想:

  • 在每一步都做出当前看起来最优的选择
  • 希望通过局部最优选择得到全局最优解
  • 贪心法常常用于解决那些求解过程可以描述为多级决策过程的优化问题

图解说明

问题
第1步:选择当前最优
第2步:选择当前最优
第3步:选择当前最优
...
第n步:选择当前最优
得到解
贪心选择性质
局部最优
→ 全局最优

💡 说明:针对这类问题,我们可以构造一棵决策树来枚举出所有可能的选项,贪心法则可以让我们寻找一条从根节点(空解,null solution)到叶节点(全解,complete solution)的路径。在其中决策过程中的每一步,贪心法都可以用来决定要搜索/选择哪一个儿子节点。

类比理解

就像在迷宫中找出口:

  • 贪心策略:每一步都选择离出口最近的方向
  • 局部最优:当前这一步看起来最好的选择
  • 全局最优:如果贪心策略正确,最终能找到最优路径

实际例子

找零钱问题(贪心法):

问题:用最少的硬币找零$0.63

贪心策略:每次选择面额最大的硬币

步骤:
1. 选择$0.25(最大面额)→ 剩余$0.38
2. 选择$0.25 → 剩余$0.13
3. 选择$0.10 → 剩余$0.03
4. 选择$0.01 × 3 → 完成

结果:2个$0.25 + 1个$0.10 + 3个$0.01 = 6个硬币

注意:贪心法在某些情况下可能不是最优的
(例如:如果只有$0.25, $0.10, $0.01,且要找$0.30)

 


3.2 贪心法的应用:最优邮局设置问题(Post Office Placement)

概念的本质

最优邮局设置问题是贪心法的典型应用。

问题描述

  • n n n户人家与街西头的距离是 H [ 1 ] < H [ 2 ] < H [ 3 ] < … < H [ n ] H[1] < H[2] < H[3] < \ldots < H[n] H[1]<H[2]<H[3]<<H[n]
  • 街上无邮局
  • 要建一些邮局使得任意一户人家到其最近的一个邮局的距离不超过100米
  • 目标:确定最少需要建多少邮局,并给出每个邮局到街西头的距离

贪心法的思路

  • 先确定第一个邮局应该建在哪里,即确定 P [ 1 ] P[1] P[1]的位置
  • 为了使邮局数量最小,应尽量使 P [ 1 ] P[1] P[1]距街西头远一些(以发挥其最大覆盖作用)
  • 但是,因为要使得第一户人家到它的距离不超过100米,因此 P [ 1 ] ≤ H [ 1 ] + 100 P[1] \leq H[1] + 100 P[1]H[1]+100
  • P [ 1 ] P[1] P[1]确定后,可被 P [ 1 ] P[1] P[1]覆盖的住户将不需要再考虑
  • 如果在 P [ 1 ] + 100 P[1]+100 P[1]+100米外还有人家,则可重复上述过程确定 P [ 2 ] P[2] P[2] P [ 3 ] P[3] P[3] … \ldots

算法伪码

Post-office (P, H, m, n)
1.   P[1] = H[1] + 100.
2.   m ← 1     //m: 邮局个数
3.   for i ← 2 to n
4.      if H[i] > P[m] + 100     //这里,m是最后一个部署的邮局的编号.
5.      then   m ← m + 1
6.            P[m] ← H[i] + 100
7.      endif
8.   endfor
9.   if P[m] > H[n] 
10.     then P[m] ← H[n]     //最后一个邮局不要超过H[n]
11. endif
12. return P, m 
End

图解说明

H[1]
P[1] = H[1] + 100
(贪心选择:最远位置)
覆盖范围
[H[1], P[1]+100]
还有未覆盖的?
P[2] = H[i] + 100
(下一个未覆盖的第一户)
完成

💡 说明:贪心法的证明往往用反证法。对本题而言,如果 P [ 1 ] ≠ H [ 1 ] + 100 P[1] \neq H[1] + 100 P[1]=H[1]+100,那么必有 P [ 1 ] = x < H [ 1 ] + 100 P[1] = x < H[1] + 100 P[1]=x<H[1]+100。由图可见,所有能在 x x x处得到服务的住户都可以在 P [ 1 ] = H [ 1 ] + 100 P[1] = H[1]+100 P[1]=H[1]+100处的邮局得到服务。因此把第一个邮局建在 P [ 1 ] P[1] P[1]处总是正确的。

实际例子

最优邮局设置示例:

输入:H = [50, 150, 250, 350, 450, 550](米)

贪心算法:
1. P[1] = H[1] + 100 = 50 + 100 = 150
   - 覆盖范围:[50, 250]
   - 覆盖住户:H[1], H[2]

2. H[3] = 250 > P[1] + 100 = 250,需要新邮局
   P[2] = H[3] + 100 = 250 + 100 = 350
   - 覆盖范围:[250, 450]
   - 覆盖住户:H[3], H[4]

3. H[5] = 450 > P[2] + 100 = 450,需要新邮局
   P[3] = H[5] + 100 = 450 + 100 = 550
   - 但P[3] = 550 > H[6] = 550,所以P[3] = H[6] = 550
   - 覆盖范围:[450, 550]
   - 覆盖住户:H[5], H[6]

结果:需要3个邮局,位置在150, 350, 550
时间复杂度:O(n)

 


3.3 贪心法的应用:区间调度问题(Interval Scheduling)

概念的本质

区间调度问题(也称活动安排问题)是贪心法的经典应用。

问题描述

  • n n n门课申请使用同一间教室,每门课 x x x的选课人数为 W x W_x Wx
  • 每门课 a i a_i ai都有其开始时间 S i S_i Si和结束时间 F i F_i Fi 0 ≤ S i < F i < ∞ 0 \leq S_i < F_i < \infty 0Si<Fi<
  • 目标:满足尽量多的学生的需求

贪心策略

  • 按结束时间从早到晚排序
  • 每次选择结束时间最早且不与已选活动冲突的活动

图解说明

所有活动
按结束时间排序
选择结束时间最早的活动
还有可选活动?
选择下一个不冲突的
结束时间最早的活动
得到最优解

💡 说明:贪心策略的正确性证明:假设存在一个最优解,如果它的第一个活动不是结束时间最早的,我们可以用结束时间最早的活动替换它,得到另一个最优解。因此,贪心策略总是可以得到最优解。

实际例子

区间调度示例:

活动:A₁(0-3, W=1), A₂(1-4, W=5), A₃(2-5, W=4), 
      A₄(3-6, W=2), A₅(4-7, W=3), A₆(5-8, W=2),
      A₇(6-9, W=1), A₈(7-10, W=3), A₉(8-11, W=5)

按结束时间排序后:
A₁(0-3), A₂(1-4), A₃(2-5), A₄(3-6), A₅(4-7),
A₆(5-8), A₇(6-9), A₈(7-10), A₉(8-11)

贪心选择:
1. 选择A₁(结束时间最早:3)
2. 选择A₄(下一个不冲突且结束时间最早:6)
3. 选择A₆(下一个不冲突且结束时间最早:8)
4. 选择A₉(下一个不冲突且结束时间最早:11)

结果:{A₁, A₄, A₆, A₉}
总收益:1 + 2 + 2 + 5 = 10

 


四、三种方法的比较(Comparison):理解它们的区别和联系

这一章要建立的基础:理解分治法、动态规划、贪心法三种方法的区别和联系,知道在什么情况下使用哪种方法。

核心问题:三种方法有什么相同点和不同点?如何选择合适的方法?


[!NOTE]
📝 关键点总结:三种方法都用于解决优化问题,都利用了最优子结构特性,但分解方式、子问题关系、求解顺序不同。分治法是自顶而下,子问题独立;动态规划是自底而上,子问题有重叠;贪心法是每一步做最优选择,不需要解决所有子问题。

4.1 相同点(Similarities):都利用最优子结构

概念的本质

三种方法的相同点:

  1. 都用于解决优化问题:寻找问题的最优解
  2. 都利用最优子结构特性:原问题的最优解可以通过子问题的最优解得到
  3. 都分解问题:将大问题分解为小问题

图解说明

三种方法
都用于优化问题
都利用最优子结构
都分解问题

 


4.2 不同点(Differences):分解方式、子问题关系、求解顺序

概念的本质

三种方法的不同点:

特性分治法动态规划贪心法
分解方式固定规则分解枚举所有可能分解贪心选择分解
子问题关系相互独立、互不相交有重叠、有交集不需要解决所有子问题
求解顺序自顶而下自底而上按贪心策略顺序
查表法不需要需要(存储子问题解)不需要
灵活性不灵活(固定规则)灵活(枚举所有可能)灵活(贪心选择)
保证最优保证保证不一定保证

图解说明

三种方法比较
分治法
自顶而下
子问题独立
动态规划
自底而上
子问题重叠
贪心法
贪心选择
局部最优
固定分解规则
枚举所有分解
贪心选择分解

💡 说明:分治法首先要确定如何将规模为n的问题分解成子问题,而且每层递归都必须遵循相同的分解规则,称为自顶而下的方法,极不灵活。动态规划需要枚举所有可能的分解情况,并且在分解时,所有需要用到的子问题的最优解都已在前面步骤中得到了、且已存放好。贪心法在每一步都做出当前最优选择,不需要解决所有子问题。

类比理解

就像三种不同的策略:

分治法

  • 就像按照固定的规则切蛋糕(每次都从中间切)
  • 切好的每一块都是独立的
  • 最后把所有块拼起来

动态规划

  • 就像建房子,先打好所有地基(解决所有子问题)
  • 然后一层层往上建(利用已解决的子问题)
  • 需要记录每一层的设计方案(查表)

贪心法

  • 就像在迷宫中,每一步都选择看起来最好的方向
  • 不需要考虑所有可能的路径
  • 但可能走不到最优路径

实际例子

三种方法解决同一问题的不同思路:

问题:找零钱$0.63,用最少的硬币

分治法:
- 分解:分成两部分,分别找零
- 子问题独立:两部分互不影响
- 但可能不是最优(因为固定分解规则)

动态规划:
- 枚举所有可能的组合
- 利用子问题的最优解(如$0.62的最优解)
- 保证最优,但复杂度高

贪心法:
- 每次选择面额最大的硬币
- 不需要考虑所有组合
- 简单快速,但可能不是最优

 


4.3 如何选择合适的方法(How to Choose):根据问题特性选择

概念的本质

如何选择合适的方法:

  1. 分治法适用

    • 问题可以自然地分解为独立的子问题
    • 子问题之间没有重叠
    • 合并子问题的解比较简单
  2. 动态规划适用

    • 问题有最优子结构特性
    • 子问题之间有重叠
    • 需要枚举所有可能的分解方式
  3. 贪心法适用

    • 问题有最优子结构特性
    • 贪心选择性质成立(局部最优 → 全局最优)
    • 不需要解决所有子问题

图解说明

选择方法
子问题有重叠?
动态规划
贪心选择性质成立?
贪心法
(更简单)
子问题独立?
分治法
可能需要其他方法

💡 说明:实际应用中,需要根据问题的特性来选择合适的方法。有些问题可以用多种方法解决,需要权衡时间复杂度和实现复杂度。

实际例子

方法选择示例:

问题1:归并排序
- 子问题独立 → 分治法
- 时间复杂度:O(n log n)

问题2:矩阵连乘
- 子问题重叠 → 动态规划
- 时间复杂度:O(n³)

问题3:区间调度
- 贪心选择性质成立 → 贪心法
- 时间复杂度:O(n log n)(排序)

问题4:0/1背包
- 子问题重叠 → 动态规划
- 贪心法不适用(不满足贪心选择性质)

 


📝 本章总结

核心要点回顾

  1. 分治法

    • 核心思想:将大问题分解为小问题,递归求解,最后合并
    • 三个步骤:底、分、合
    • 特点:自顶而下,子问题独立,固定分解规则
    • 应用:归并排序、快速排序、二元搜索
  2. 动态规划

    • 核心思想:自底而上,枚举所有可能的分解,利用子问题最优解
    • 前提条件:最优子结构特性、重叠子问题
    • 特点:自底而上,子问题有重叠,需要查表
    • 应用:矩阵连乘、最长公共子序列、0/1背包
  3. 贪心法

    • 核心思想:每一步都做出当前最优选择
    • 前提条件:最优子结构特性、贪心选择性质
    • 特点:贪心选择,不需要解决所有子问题
    • 应用:区间调度、最优邮局设置、霍夫曼编码
  4. 三种方法的比较

    • 相同点:都用于优化问题,都利用最优子结构
    • 不同点:分解方式、子问题关系、求解顺序不同
    • 选择方法:根据问题特性(子问题是否重叠、是否有贪心选择性质)选择

知识地图

算法设计方法
分治法
Divide and Conquer
动态规划
Dynamic Programming
贪心法
Greedy Algorithm
自顶而下
子问题独立
自底而上
子问题重叠
贪心选择
局部最优
应用:排序、搜索
应用:优化问题
应用:调度、编码

关键决策点

  • 何时使用分治法:问题可以自然地分解为独立的子问题,子问题之间没有重叠
  • 何时使用动态规划:问题有最优子结构特性,子问题之间有重叠,需要枚举所有可能的分解
  • 何时使用贪心法:问题有最优子结构特性和贪心选择性质,贪心策略可以得到最优解
  • 如何证明贪心法正确性:通常使用反证法或交换论证

💡 延伸学习:三种方法都是算法设计的重要工具。掌握它们的原理和应用有助于我们:

  1. 根据问题特性选择合适的方法
  2. 设计高效的算法解决实际问题
  3. 理解算法设计的核心思想
  4. 为学习更复杂的算法打下基础
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值