动态规划详解及 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++)

cpp 复制代码
#include <iostream>
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++)

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
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],即为背包的最大价值。

相关推荐
jianqiang.xue43 分钟前
ESP32-S3 入门教程:从环境搭建到物联网应用实战
c语言·单片机·嵌入式硬件·物联网·青少年编程·51单片机·嵌入式
Keying,,,,1 小时前
力扣hot100 | 多维动态规划 | 62. 不同路径、64. 最小路径和、5. 最长回文子串、1143. 最长公共子序列、72. 编辑距离
算法·leetcode·动态规划
钱彬 (Qian Bin)1 小时前
企业级实战:构建基于Qt、C++与YOLOv8的模块化工业视觉检测系统(基于QML)
c++·qt·yolo·qml·工业质检·qt 5.15.2
会开花的二叉树1 小时前
上手 cpp-httplib:轻量级 C++ HTTP 库的安装与实战指南
开发语言·c++·http
Su^!-苏释州1 小时前
Windows配置C/C++环境:MinGW+Vscode
c语言·c++·windows·vscode·大一新生学c语言
im_AMBER1 小时前
c回顾 03
c语言·学习
代码村新手1 小时前
C语言-指针
c语言·开发语言·jvm
Nuyoah11klay2 小时前
华清远见25072班C++学习day3
c++
一点都不方女士2 小时前
.NET Framework 4.0.30319:官方下载与常见问题解决指南
c++·windows·.net·动态链接库·运行库
静水流深-刘申3 小时前
算法继续刷起-2025年09月26日
开发语言·c++·算法