常用十大算法_动态规划算法(DP)

动态规划算法(DP)

高能预警:DP算法不容易理解,需要动脑筋+查资料+找例题

动态规划算法(Dynamic Programming),是将复杂问题拆分成子问题,并在子问题的基础上,求解复杂问题,子问题之间不是独立的,而是相互依存的。

动态规划算法有两种实现形式:递归,非递归

有点莫名其妙对吧,下面以例子来了解(递归的容易,非递归的难,要有心理准备:<)

【例一】01背包问题:有个容量为4的背包,有以下3种物品,物品具有价值和重量属性,要求装入的物品价值总和最大且装入物品不能重复

常用十大算法_动态规划算法(DP)_第1张图片

解法1,递归实现动态规划算法

算法分析:

约定 weight[] 装载物品的重量,value[] 装载物品的价值。为了方便人机交互,定义上两个数字第一个元素为0。

装与不装,无非是受背包容量,和价格最大化限制。

我们假设 OPT(n,i) 代表对第n个物品操作且容量为i时的最优解(价值最大)。那么对于第n个物品而言,受背包容量限制:

当容量够的时候,我们有两种对n物品的操作,选n,不选n,选n时其对应最优解为 value[n]+OPT(n-1,i-weight[i]),不选n时其对应最优解为 OPT(n-1,i)。而此时 OPT(n,i)=MAX{value[n]+OPT(n-1,i-weight[i]),OPT(n-1,i)}

当容量不够时,我们只有一种对n物品的操作,即不选n。此时OPT(n,i)=OPT(n-1,i)

以上的运算流程是基于递归完成的。所以要设置递归结束条件:当n==0时,说明没有物品了,return 0;

根据以上推理得到该问题的状态转移方程(别被式子吓到了,这是纸老虎)

常用十大算法_动态规划算法(DP)_第2张图片

代码实现:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.dynamicProgramming;

public class 动态规划算法_DP_01背包问题 {
    static int[] value={0,1500,3000,2000};//分别对应物品a,b,c,为了方便人机交互,定义第0个物品为0
    static int[] weight={0,1,4,3};//分别对应物品a,b,c,为了方便人机交互,定义第0个物品为0
    static int bag=4;//背包容量
    public static void main(String[] args) {
        System.out.println(re_OPT(3,4));
    }

    /**
     * 动态规划:递归来求最优解
     * @param n 第n个物品
     * @param bag 背包容量
     * @return 最优解
     */
    public static int re_OPT(int n, int bag){
        if (n==0){
            return 0;
        }
        if (bag>=weight[n]){
            return Math.max(value[n]+ re_OPT(n-1,bag-weight[n]), re_OPT(n-1,bag));
        }else {
            return re_OPT(n-1,bag);
        }
    }

}
3500

递归式实现了,仍然存在有个小问题。

常用十大算法_动态规划算法(DP)_第3张图片

 递归的本身设计,就包含了大量的重复计算,如上图,相同颜色的方框,表示重复运算。这在海量数据计算过程中,由于递归将创建大量的栈空间,进行重复运算。极易引发栈溢出。为了解决这一问题,需要彻底改变运算模式-------非递归动态规划

 

解法2,非递归实现动态规划算法

算法分析:

将题目中的信息全部整合到一个二维表中,该表对应了容量从4到0的各种情况下的最优解。

表的第一行,由于价值为0,所以全是0。表的第一列,由于容量为0,所以全是0。

其他的情况,需要通过程序进行求解。每一个空都对应一种情况,每种情况都受容量限制,每种情况都有两种操作(选与不选)。

与解法1不同的是,这里是自底而上求解(表中从左至右,从上至下)。将每次计算的结果保存在二维数组中,求解的下一个结果依赖于上一次已经求好的结果。这就解决了递归式重复计算的问题。

最终的答案,就在二维数组的右下角。

常用十大算法_动态规划算法(DP)_第4张图片

代码实现:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.dynamicProgramming;

public class 动态规划算法_DP_01背包问题 {
    static int[] value={0,1500,3000,2000};//分别对应物品a,b,c,为了方便人机交互,定义第0个物品为0
    static int[] weight={0,1,4,3};//分别对应物品a,b,c,为了方便人机交互,定义第0个物品为0
    static int bag=4;//背包容量
    public static void main(String[] args) {
        System.out.println(dp_OPT(3,4));
    }

  
    /**
     * 动态规划:非递归求最优解
     * @param n 第n个物品
     * @param bag 背包容量
     * @return 最优解
     */
    public static int dp_OPT(int n, int bag){
        int[][] result=new int[weight.length][bag+1];//创造一个二维数组,用来存放各种情况对应的最优解,前[]保存第几个物品,后面[]保存多少背包容量
        //将第一行与第一列重置为0
        for (int i=0;i=weight[i]?Math.max(value[i]+result[i-1][j-weight[i]],result[i-1][j]):result[i-1][j];
            }
        }
        return result[n][bag];
    }

}
3500

 

【例二】 求相隔数据和最大值:有数据 {5,7,3,1,4,2} 需要间隔式的取出几个数使数之和最大,求出最大值。

代码实现:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.dynamicProgramming;

public class 动态规划算法_DP_相隔数据和最大 {
    public static void main(String[] args) {
        int[] data=new int[]{5,7,3,1,4,2};
        System.out.println(re_OPT(data,5));
        System.out.println(dp_OPT(data,5));
    }

    /**
     * 动态规划:递归式计算出最优解
     * @param data 需要求解的数据
     * @param i 在第i个数据前进行最优解计算
     * @return 返回最优解
     */
    public static int re_OPT(int[] data, int i){
        if (i==0){//i==0时,第一个数据本身就是最优解。递归终止条件
            return data[0];
        }else if (i==1){//i==1是,前两个数中的最大值就是最优解。递归终止条件
            return Math.max(data[0],data[1]);
        }else {
            int A= re_OPT(data,i-2)+data[i];//A方案:选当前数据
            int B= re_OPT(data,i-1);//B方案:不选当前数据
            return Math.max(A,B);//在AB方案中,返回最大值作为最优解
        }
    }

    /**
     * 动态规划:非递归计算出最优解
     * @param data 需要求解的数据
     * @param i 在第i个数据前进行最优解计算
     * @return 返回最优解
     */
    public static int dp_OPT(int[] data, int i){
        int[] opt=new int[data.length];
        int A;
        int B;
        opt[0]=data[0];
        opt[1]=Math.max(data[0],data[1]);
        for (int j=2;j
12
12

 

【例三】数据求和匹配:{3,34,4,12,5,2}的数据中,给出一个待匹配的值n,问是否能从数据中任意取出几个数,使得之和等于n ?

代码实现:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.dynamicProgramming;

public class 动态规划算法_DP_数据求和匹配 {
    static int[] data={3,34,4,12,5,2};
    public static void main(String[] args) {
        System.out.println(re_OPT(data.length-1,9));
        System.out.println(re_OPT(data.length-1,13));
        System.out.println(dp_OPT(data.length-1,9));
    }

    /**
     * 动态规划:递归求出在一组数据中,是否有相加之和为待匹配数的情况
     * @param i 数组索引
     * @param a 待匹配数
     * @return true or false
     */
    public static boolean re_OPT(int i, int a){
        if (a==0){//终止递归
            return true;
        }else if (i==0){//终止递归
            return data[i]==a;
        }else if (data[i]>a){//剪枝
            return re_OPT(i-1,a);
        }
        return re_OPT(i-1,a-data[i])|| re_OPT(i-1,a);//两种方案,选与不选
    }

    /**
     * 动态规划:非递归出在一组数据中,是否有相加之和为待匹配数的情况
     * @param i 数组索引
     * @param a 待匹配数
     * @return true or false
     */
    public static boolean dp_OPT(int i, int a){
        boolean[][] res=new boolean[data.length][a+1];
        boolean A,B;
        for (int x=0;xy){
                    res[x][y]=res[x-1][y];
                }else {
                    A=res[x-1][y-data[x]];
                    B=res[x-1][y];
                    res[x][y]=A||B;
                }
            }
        }
        return res[i][a];
    }
}
true
false
true

 

【例四】最优收益问题:如下图,8个任务分布在0~11的时间段内,每个任务对应的收益不同,(1号任务,收益5),在一个时间段内只能做一件任务。问在8个任务中最优收益为多少?

常用十大算法_动态规划算法(DP)_第5张图片

代码实现: 

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.dynamicProgramming;
public class 动态规划算法_DP_最优收益问题 {
    static int[][] taskTime={{0,0},{1,4},{3,5},{0,6},{4,7},{3,8},{5,9},{6,10},{8,11}};//存储任务时长
    static int[] income={0,5,1,8,4,6,3,2,4};//存储任务收益
    public static void main(String[] args) {
        System.out.println(OPT(8));
    }

    /**
     * 动态规划:递归计算出最优解
     * @param i 开始计算的任务序号
     * @return 最优解
     */
    public static int OPT(int i){
        if (i==0){//当i为1时,说明已经完成规划内的任务,停止递归
            return 0;
        }
        int A=income[i]+OPT(prev(i));//方案1:选择当前任务
        int B=OPT(i-1);//方案2:不选择当前任务
        return Math.max(A,B);//求最优解
    }

    /**
     * 根据任务时长获取当前任务之前的最近可用任务
     * @param i 当前任务
     * @return 最近可用任务
     */
    public static int prev(int i){
        int value=taskTime[i][0];
        while (true){
            if (i==1){
                return 0;
            }
            if (value>=taskTime[i-1][1]){
                return i-1;
            }
            i--;
        }
    }
}
13

推荐关于动态规划算法的博文

教你彻底学会动态规划——入门篇

 


其他常用算法,见下各链接

【常用十大算法_二分查找算法】

【常用十大算法_分治算法】

【常用十大算法_贪心算法】

【常用十大算法_KMP算法】

【常用十大算法_普里姆(prim)算法,克鲁斯卡尔(Kruskal)算法】

【常用十大算法_迪杰斯特拉(Dijkstra)算法,弗洛伊德(Floyd)算法】

【常用十大算法_回溯算法】

 

【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)  

 

你可能感兴趣的:(数据结构与算法)