> 技术文档 > 动态规划详解及 C/C++ 示例_动态规划问题 c

动态规划详解及 C/C++ 示例_动态规划问题 c

动态规划是一种常用的算法设计技术,用于解决具有重叠子问题和最优子结构特性的问题。它通过将问题分解为较小的子问题,并存储子问题的解以避免重复计算,从而高效地解决问题。

一、基本概念

  1. 多阶段决策问题

这类问题需要经过多个阶段的决策来达到目标,每个阶段的决策会影响后续阶段的状态和决策选择。例如,背包问题中,选择放或不放某个物品就是一个阶段的决策,这个决策会影响背包的剩余容量,进而影响后续物品的放置决策。

  1. 最优子结构

一个问题的最优解包含其子问题的最优解。例如,斐波那契数列的第 n 项等于第 n - 1 项和第 n - 2 项之和,这里的第 n 项的最优解(即正确的值)就包含了第 n - 1 项和第 n - 2 项的最优解。

  1. 重叠子问题

在递归求解过程中,许多子问题会被重复计算。例如,递归计算斐波那契数列时,多个分支都会计算相同的斐波那契数,动态规划通过保存已解决子问题的解来避免重复计算。

二、动态规划算法的步骤

  1. 确定状态

状态定义是动态规划的关键。状态通常表示问题中的某个中间结果或当前所处的情况。例如,在斐波那契数列问题中,状态可以定义为第 n 个斐波那契数的值,在背包问题中,状态可以定义为前 i 个物品放入容量为 j 的背包中所能获得的最大价值。

  1. 确定状态转移方程

状态转移方程描述了不同状态之间的关系,即如何从前一个或多个状态转移到当前状态。例如,斐波那契数列的状态转移方程为 f(n) = f(n - 1) + f(n - 2),背包问题的状态转移方程为 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]),其中 weight[i] 和 value[i] 分别是第 i 个物品的重量和价值,dp[i][j] 表示前 i 个物品放入容量为 j 的背包中的最大价值。

  1. 确定初始条件和边界条件

初始条件是问题的起始状态,边界条件是状态的范围限制。例如,斐波那契数列的初始条件为 f(0) = 0、f(1) = 1,边界条件为 n 为非负整数;背包问题的初始条件可以是 dp[0][j] = 0(没有物品放入背包时价值为 0),边界条件可以是 j 不能超过背包的最大容量。

  1. 计算最优解的值

按照状态转移方程递推地计算各个状态的最优值,通常使用自底向上的方式进行计算,即从初始状态开始逐步计算到最终状态。

  1. 构造最优解

如果需要构造具体的最优解路径,可在计算最优值的同时记录相关决策信息,然后通过回溯这些决策信息来构造最优解。

三、斐波那契数列示例代码(C/C++)

#include using namespace std;int fibonacci(int n) { if (n < 0) { return -1; // 错误处理 } int dp[n + 1]; dp[0] = 0; if (n >= 1) { dp[1] = 1; } for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n];}int main() { int n = 10; cout << \"fibonacci(\" << n << \") = \" << fibonacci(n) << endl; return 0;}

四、背包问题示例代码(C/C++)

#include #include #include using namespace std;int knapsack(vector<int> values, vector<int> weights, int capacity) { int n = values.size(); vector<vector<int>> dp(n + 1, vector<int>(capacity + 1, 0)); for (int i = 1; i <= n; i++) { for (int j = 1; j <= capacity; j++) { if (weights[i - 1] > j) { dp[i][j] = dp[i - 1][j]; } else { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]); } } } return dp[n][capacity];}int main() { vector<int> values = {60, 100, 120}; vector<int> weights = {10, 20, 30}; int capacity = 50; cout << \"knapsack: max value = \" << knapsack(values, weights, capacity) << endl; return 0;}

五、代码解释

  1. 斐波那契数列代码解释

首先创建一个大小为 n + 1 的数组 dp 来存储斐波那契数列的值。将 dp[0] 初始化为 0,当 n 大于等于 1 时,将 dp[1] 初始化为 1。然后从第 2 项开始,利用状态转移方程 dp[i] = dp[i - 1] + dp[i - 2] 依次计算每个斐波那契数的值,最终返回第 n 项的值。

  1. 背包问题代码解释

创建一个二维数组 dp,其中 dp[i][j] 表示前 i 个物品放入容量为 j 的背包中的最大价值。遍历每个物品(从 1 到 n)和每个可能的背包容量(从 1 到 capacity)。对于每个物品,如果其重量大于背包容量 j,则无法放入背包,此时 dp[i][j] 的值与 dp[i - 1][j] 相同。否则,比较放入该物品后的价值(dp[i - 1][j - weights[i - 1]] + values[i - 1])和不放入该物品时的价值(dp[i - 1][j]),将较大的值赋给 dp[i][j]。最终返回 dp[n][capacity],即为背包的最大价值。