算法-动态规划

动态规划

动态规划法的求解过程:

  1. 划分子问题:将原问题分解为若干个子问题,每个子问题对应一个决策阶段,并且子问题之间具有重叠关系。
  2. 确定动态规划函数:根据子问题之间的重叠关系找到子问题满足的递推关系式(即动态规划函数),这是动态规划法的关键。
  3. 填写表格:设计表格,以自底向上的方式计算各个子问题的解并填表,实现动态规划过程。

动态规划算法的基本要素为最优子结构性质子问题重叠性质

一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质,也称此问题满足最优性原理。

子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。

背包问题

背包问题是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,如何选择,才能使得物品的总价格最高。

三种背包:

  1. 01背包:物品只可以取一次
  2. 完全背包:物品可以取无限次
  3. 多重背包:物品可以取的次数有一个上限

01背包:设 V ( n , C ) V(n,C) V(n,C)表示将n个物品装入容量为C的背包获得的最大价值,显然,初始子问题是把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0,即: V ( i , 0 ) = V ( 0 , j ) = 0 V(i,0)=V(0,j)=0 V(i,0)=V(0,j)=0 V ( i , j ) V(i,j) V(i,j)在在决策 x i x_i xi时,已确定了 ( x 1 , . . . , x i − 1 ) (x_1,...,x_{i-1}) (x1,...,xi1),则问题处于下列两种状态之一:

  1. 背包容量不足以装入物品i,则装入前i个物品得到的最大价值和装入前i-1个物品得到的最大价值是相同的,即 x i = 0 x_i=0 xi=0,背包不增加价值。

  2. 背包容量可以装入物品i,如果把第i个物品装入背包,则背包中物品的价值等于把前i-1个物品装入容量为 j − w i j-w_i jwi的背包中的价值加上第i个物品的价值v;如果第i个物品没有装入背包,则背包中物品的价值等于把前i-1个物品装入容量为j的背包中所取得的价值。显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。则得到如下递推式:
    V ( i , j ) = { V ( i − 1 , j ) j < w i m a x { V ( i − 1 , j ) , V ( i − 1 , j − w i ) + V i } j ≥ w i V(i,j)= \left\{ \begin{array}{ll} V(i-1,j) &j<w_i\\ max\{V(i-1,j),V(i-1,j-w_i)+Vi\} &j\ge w_i \end{array} \right. V(i,j)={V(i1,j)max{V(i1,j),V(i1,jwi)+Vi}j<wijwi
    V ( n , C ) V(n,C) V(n,C)的值向前推,如果 V ( n , C ) > V ( n − 1 , C ) V(n,C)>V(n-1,C) V(n,C)>V(n1,C),表明第n个物品被装入背包,前n-1个物品被装入容量为 C − w n C-w_n Cwn的背包中。

import java.util.Arrays;

public class DynamicProgramming {
    // 动态规划解决01背包
    public static int knapsack01(int[] weight, int[] value, int n, int capacity) {
        // 表格的行代表物品,列代表容量
        int[][] table = new int[n + 1][capacity + 1];
        // 初始化表格的第0列
        for (int c = 0; c <= n; c++)
            table[c][0] = 0;
        // 初始化表格的第0行
        for (int r = 0; r <= capacity; r++)
            table[0][r] = 0;
        // 填写表格
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= capacity; j++)
                if (j < weight[i]) // 此时的容量j,装不下物品i
                    // 此时价值为上一行这个容量时的价值
                    table[i][j] = table[i - 1][j];
                else // 此时的容量j,能装下物品i
                    table[i][j] = Math.max(table[i - 1][j], table[i - 1][j - weight[i]] + value[i]);
        }
        // 装入背包的物品:0未装入,1装入
        int[] loaded = new int[n + 1];
        for (int i = n, j = capacity; i > 0; i--)
            if (table[i][j] > table[i - 1][j]) {
                loaded[i] = 1;
                j = j - weight[i];
            } else {
                loaded[i] = 0;
            }
        System.out.println(Arrays.toString(loaded));
        return table[n][capacity];
    }

    public static void main(String[] args) {
        // 商品编号从1开始
        int[] weight = {0, 2, 12, 1, 1, 4};
        int[] value = {0, 2, 4, 2, 1, 10};
        // 物品数量
        int n = weight.length - 1;
        int capacity = 15;
        // [0, 1, 0, 1, 1, 1]
        // 15
        System.out.println(knapsack01(weight, value, n, capacity));
    }
}
五大常用算法动态规划、分治、递归、贪心和回溯。 动态规划是一种将问题分解成子问题并保存子问题解的方法。通过求解子问题,可以逐步推导出原始问题的解。动态规划通常用于求解最优化问题,例如最长公共子序列、最短路径等。 分治是将原问题划分成多个相互独立的子问题,然后通过递归的方式求解子问题,并将子问题的解合并成原问题的解。分治算法常用于排序、快速幂等问题。 递归是通过函数调用自身来解决问题的方法。递归算法在问题定义可以被分解为较小规模或更简单情况的时候很有用。例如,计算一个数的阶乘,就可以使用递归实现。 贪心算法是一种选择当前最优策略的方法,即在每一步选取最优解,最终得到全局最优解的算法。贪心算法通常用于解决无后效性的问题,例如最小生成树、哈夫曼编码等。 回溯是一种通过穷举搜索所有可能的解空间,找到满足条件的解的方法。回溯算法在解决组合问题、排序问题、子集和问题等方面很有效。回溯算法通过递归的方式逐步构建解,当发现当前解不满足条件时,会回退到上一步继续搜索其他可能的解。 这五种常用算法在不同的问题领域中都有广泛应用,每种算法都有自己的特点和适用范围。在解决具体问题时,可以根据问题的性质和要求选择最适合的算法进行求解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值