动态规划详解及 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),背包问题的状态转移方程为 dpij = max(dpi - 1j, dpi - 1j - weight\[i] + valuei),其中 weighti 和 valuei 分别是第 i 个物品的重量和价值,dpij 表示前 i 个物品放入容量为 j 的背包中的最大价值。

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

初始条件是问题的起始状态,边界条件是状态的范围限制。例如,斐波那契数列的初始条件为 f(0) = 0、f(1) = 1,边界条件为 n 为非负整数;背包问题的初始条件可以是 dp0j = 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 来存储斐波那契数列的值。将 dp0 初始化为 0,当 n 大于等于 1 时,将 dp1 初始化为 1。然后从第 2 项开始,利用状态转移方程 dpi = dpi - 1 + dpi - 2 依次计算每个斐波那契数的值,最终返回第 n 项的值。

  1. 背包问题代码解释

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

相关推荐
南境十里·墨染春水19 分钟前
C++ 工厂模式:从入门到进阶,彻底掌握对象创建的艺术
开发语言·c++·算法
workflower2 小时前
使用大语言模型处理用户需求
大数据·人工智能·设计模式·重构·动态规划
一拳一个呆瓜3 小时前
【STL】_SCL_SECURE_NO_WARNINGS
c++·stl
2601_951643774 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势
小小编程路4 小时前
C++ 异常 完整讲解
开发语言·c++
AI科技星4 小时前
数术工坊 · 第四卷 橡皮泥江湖(拓扑学)【完整定稿】
c语言·开发语言·汇编·electron·概率论·拓扑学
AI科技星7 小时前
数术工坊第八卷:算力革命
c语言·开发语言·网络·量子计算·agi
Frank学习路上7 小时前
【C++】面试:关键字与语法特性
c++·面试
Irissgwe8 小时前
数据结构-栈和队列
数据结构·c++·c·栈和队列
点云侠9 小时前
PCL 生成三棱锥点云
c++·算法·最小二乘法