lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

动态规划入门指南

什么是动态规划?

动态规划(Dynamic Programming,简称DP)是一种解决复杂问题的方法,它通过把原问题分解为相对简单的子问题,然后利用子问题的最优解来推导出原问题的最优解。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

重叠子问题是指在求解原问题时,需要反复求解相同或者相似的子问题。如果不使用动态规划,那么这些子问题就会被多次重复计算,导致效率低下。动态规划的一个核心思想就是记住已经求解过的子问题的答案,避免重复计算,从而提高效率。

最优子结构是指原问题的最优解可以由子问题的最优解推导出来。如果一个问题具有最优子结构性质,那么就可以使用动态规划来求解。动态规划的另一个核心思想就是根据状态转移方程来递推出原问题的最优解,从而降低复杂度。

动态规划的基本步骤

一般来说,使用动态规划来求解一个问题,需要遵循以下四个步骤:

  1. 定义状态:状态是指描述原问题或者子问题的特征变量,通常用一个或者多个变量来表示。比如在背包问题中,状态可以用两个变量来表示:当前物品的编号和当前背包的容量。
  2. 找出状态转移方程:状态转移方程是指描述状态之间如何转换的数学表达式,通常用递归或者迭代的方式来表示。比如在背包问题中,状态转移方程可以表示为:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]),其中dp[i][j]表示前i个物品放入容量为j的背包中能得到的最大价值,w[i]v[i]分别表示第i个物品的重量和价值。
  3. 确定边界条件:边界条件是指状态转移方程中不能递推或者迭代的特殊情况,通常用已知或者显然的值来表示。比如在背包问题中,边界条件可以表示为:当i=0或者j=0时,dp[i][j]=0,即没有物品或者没有容量时,背包中的价值为0。
  4. 计算并返回结果:根据状态转移方程和边界条件,从小到大或者从大到小地计算出所有状态的值,并返回原问题所需的结果。比如在背包问题中,可以从左上角到右下角地填充二维数组,并返回右下角的值作为最终答案。

动态规划的示例代码

下面给出一个使用动态规划求解背包问题的示例代码

 

// 定义物品类
class Item {
    String name; // 物品名称
    int weight; // 物品重量
    int value; // 物品价值

    public Item(String name, int weight, int value) {
        this.name = name;
        this.weight = weight;
        this.value = value;
    }
}

// 定义背包问题类
public class KnapsackProblem {

    // 定义物品列表
    static Item[] items = {
        new Item("水", 3, 10),
        new Item("书", 1, 3),
        new Item("食物", 2, 9),
        new Item("小刀", 3, 4),
        new Item("衣物", 2, 5),
        new Item("手机", 1, 10)
    };

    // 定义背包最大容量
    static int max_capacity = 6;

    // 定义状态数组
    static int[][] dp = new int[items.length + 1][max_capacity + 1];

    // 找出状态转移方程并计算所有状态
    public static void solve() {
        for (int i = 1; i <= items.length; i++) {
            for (int j = 1; j <= max_capacity; j++) {
                int weight = items[i-1].weight; // 获取当前物品重量
                int value = items[i-1].value; // 获取当前物品价值
                if (weight > j) {
                    dp[i][j] = dp[i-1][j]; // 大于直接取上一次最优结果 此时i-1代表上一行
                } else {
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight] + value); // 使用内置函数Math.max(),将上一次最优结果与当前物品价值+剩余空间可利用价值做对比取最大值
                }
            }
        }
    }

    // 返回结果
    public static void printResult() {
        System.out.println("最大价值为:" + dp[items.length][max_capacity]);

        // 输出状态数组
        for (int[] row : dp) {
            for (int cell : row) {
                System.out.print(cell + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        solve();
        printResult();
    }
}

  

 

 

 

动态规划的优缺点

动态规划的优点有:

- 可以有效地减少重复计算,提高效率。
- 可以找出问题的最优解,而不是近似解或者贪心解。
- 可以处理多维度或者多约束条件的问题。

动态规划的缺点有:

- 需要定义合适的状态和状态转移方程,这可能需要一定的技巧和经验。
- 需要额外的空间来存储所有状态的值,这可能会占用较多的内存。
- 需要考虑边界条件和初始化问题,这可能会增加编程的难度。


动态规划和分治法的区别
动态规划和分治法都是通过把原问题分解为子问题来求解复杂问题的方法,但是它们有以下几点区别: - 分治法是把原问题分解为相互独立且结构相同的子问题,然后递归地求解子问题,最后合并子问题的解得到原问题的解。动态规划是把原问题分解为相互重叠且结构相似的子问题,然后自底向上或者自顶向下地求解子问题,利用子问题的最优解得到原问题的最优解。 - 分治法适用于子问题之间没有关联性的情况,比如快速排序、归并排序、二分查找等。动态规划适用于子问题之间有关联性且具有最优子结构性质的情况,比如背包问题、最长公共子序列、最短路径等。 - 分治法不需要记忆化或者备忘录来存储子问题的解,因为每个子问题只需要求解一次。动态规划需要记忆化或者备忘录来存储子问题的解,以避免重复计算
 
posted on 2023-05-01 16:45  白露~  阅读(15)  评论(0编辑  收藏  举报