当前位置: 首页 > article >正文

动态规划模式

        动态规划(Dynamic Programming, DP)是一种解决复杂问题的算法设计技术,它通过将大问题分解为小问题,并利用小问题的解决方案来构造大问题的解决方案,从而避免了重复计算。动态规划通常用于具有“最优子结构”和“重叠子问题”特征的问题。

动态规划的基本步骤

  1. 定义状态:明确问题的状态表示。即如何用状态表示当前的子问题。
  2. 状态转移方程:根据问题的结构,找到从一个状态转移到另一个状态的方式。
  3. 初始化:为基础情况设置初始值。
  4. 计算顺序:确定计算的顺序,从最小的子问题开始逐步计算到原问题。
  5. 答案提取:从动态规划表中提取最终答案。

动态规划的典型应用场景

  • 最短路径问题(如 Dijkstra、Floyd 算法)。
  • 背包问题(如 0/1 背包、完全背包)。
  • 字符串匹配问题(如最长公共子序列、编辑距离)。
  • 最优子结构问题(如矩阵链乘法问题、切割钢条问题)。

动态规划的分类

动态规划的算法通常可以分为以下两种类型:

  1. 自顶向下的递归式动态规划(Top-Down with Memoization):

        使用递归进行问题的求解,并通过记忆化(Memoization)保存已计算的结果,避免重复计算。

  1. 自底向上的迭代式动态规划(Bottom-Up with Tabulation):

        通过迭代从最小子问题开始,逐步解决更大的子问题,直到得到最终结果。

动态规划的三大特点

  1. 最优子结构:问题的最优解可以通过子问题的最优解来构造。例如,最长公共子序列问题的最优解可以通过子问题的解来构造。
  2. 重叠子问题:不同的子问题可能会多次出现,因此可以通过记忆化来存储已解决的子问题,避免重复计算。
  3. 状态转移:通过当前状态和已解决的子问题来推导出下一个状态。

动态规划的经典问题

1. 0/1 背包问题

问题描述:

  • 有一个背包,最多可以容纳重量为 W 的物品。
  • 有 n 个物品,每个物品的重量是 w[i],价值是 v[i]。
  • 问:如何选择物品,使得背包的总价值最大,且总重量不超过 W?

动态规划解法:

  • 定义状态 dp[i][j]:表示前 i 个物品,背包容量为 j 时的最大价值。
  • 状态转移方程:
    • 如果不选择第 i 个物品:dp[i][j] = dp[i-1][j]
    • 如果选择第 i 个物品:dp[i][j] = dp[i-1][j-w[i]] + v[i]
    • 最终的状态转移方程为:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int knapsack(int W, vector<int>& w, vector<int>& v, int n) {
    // dp[i][j]表示前i个物品,容量为j时的最大价值
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= W; j++) {
            if (w[i - 1] <= j) {
                // 当前物品不放入或放入两种选择的最大值
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1]);
            } else {
                dp[i][j] = dp[i - 1][j]; // 当前物品不能放入
            }
        }
    }
    return dp[n][W]; // 返回最大价值
}

int main() {
    int W = 10; // 背包容量
    vector<int> w = {2, 3, 4, 5}; // 物品重量
    vector<int> v = {3, 4, 5, 6}; // 物品价值
    int n = w.size();

    cout << "Max value in knapsack: " << knapsack(W, w, v, n) << endl;
    return 0;
}

输出:

Max value in knapsack: 7

2. 最长公共子序列问题

问题描述:

  • 给定两个字符串 s1 和 s2,求它们的最长公共子序列(LCS)。

动态规划解法:

  • 定义状态 dp[i][j]:表示 s1[0...i-1] 和 s2[0...j-1] 的最长公共子序列长度。
  • 状态转移方程:
    • 如果 s1[i-1] == s2[j-1],那么 dp[i][j] = dp[i-1][j-1] + 1
    • 否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int longestCommonSubsequence(string s1, string s2) {
    int m = s1.size(), n = s2.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (s1[i - 1] == s2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n]; // 返回最长公共子序列长度
}

int main() {
    string s1 = "abcde";
    string s2 = "ace";

    cout << "Length of Longest Common Subsequence: " << longestCommonSubsequence(s1, s2) << endl;
    return 0;
}

输出:

Length of Longest Common Subsequence: 3

3. 斐波那契数列

斐波那契数列是经典的动态规划问题。其定义是:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2)

动态规划解法通过迭代避免了递归中的重复计算。

#include <iostream>
using namespace std;

int fib(int n) {
    if (n <= 1) return n;
    
    int prev2 = 0, prev1 = 1;
    for (int i = 2; i <= n; i++) {
        int curr = prev1 + prev2;
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

int main() {
    int n = 10;
    cout << "Fibonacci of " << n << " is " << fib(n) << endl;
    return 0;
}

输出:

Fibonacci of 10 is 55

总结

        动态规划是一种通过将问题分解成小问题并利用已解决的小问题来避免重复计算的技术。其核心思想是使用状态表示问题的不同阶段,并通过状态转移方程来递推求解。在实际应用中,动态规划非常适用于具有“最优子结构”和“重叠子问题”特征的问题,常见的问题有背包问题、最长公共子序列、矩阵链乘法等。


http://www.kler.cn/a/464110.html

相关文章:

  • 如何轻松关闭 iPhone 上的 HEIC [HEIC 图像技巧]
  • 三甲医院等级评审八维数据分析应用(五)--数据集成与共享篇
  • ‘元素.style.样式名‘获取不到样式,应该使用Window.getComputedStyle()获取正真的样式
  • 【重庆】《政务数字化应用费用测算规范》(T/CDCIDA 001—2023)-省市费用标准解读系列36
  • MySQL 08 章——聚合函数
  • 如何利用 ClickHouse 实现高级分析:MySQL 到 ClickHouse 实时数据同步指南
  • 精密制造动力箱行业需要哪种多功能电表
  • 地理数据库Telepg面试内容整理-相关技术与工具
  • 【C语言】如何插入并播放音频文件
  • 高级架构五 设计模式
  • python中序列化之json文件的使用
  • Redis 发布订阅(Pub/Sub)机制详解
  • Switch组件的用法
  • 《我在技术交流群算命》(二):QGraphicsItem怎么写自定义信号啊(QObject多继承顺序问题)
  • 深入解析 JVM vs JDK vs JRE:三者区别与联系详解
  • python opencv的orb特征检测(Oriented FAST and Rotated BRIEF)
  • LevelDB 源码阅读:利用 Clang 的静态线程安全分析
  • 彻底解决 Selenium ChromeDriver 不匹配问题:Selenium ChromeDriver 最新版本下载安装教程
  • 概率论与数理统计
  • 需求上线,为什么要刷缓存?
  • LeetCode算法题——长度最小的子数组
  • 大模型的prompt的应用一
  • 数据挖掘——集成学习
  • Java-写一个计数器
  • mac下载Homebrew安装nvm
  • 微服务间通信的端口开放性探究:从单机到多机的转变