动态规划算法(DP)
高能预警:DP算法不容易理解,需要动脑筋+查资料+找例题
动态规划算法(Dynamic Programming),是将复杂问题拆分成子问题,并在子问题的基础上,求解复杂问题,子问题之间不是独立的,而是相互依存的。
动态规划算法有两种实现形式:递归,非递归
有点莫名其妙对吧,下面以例子来了解(递归的容易,非递归的难,要有心理准备:<)
【例一】01背包问题:有个容量为4的背包,有以下3种物品,物品具有价值和重量属性,要求装入的物品价值总和最大且装入物品不能重复
解法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;
根据以上推理得到该问题的状态转移方程(别被式子吓到了,这是纸老虎)
代码实现:
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
递归式实现了,仍然存在有个小问题。
递归的本身设计,就包含了大量的重复计算,如上图,相同颜色的方框,表示重复运算。这在海量数据计算过程中,由于递归将创建大量的栈空间,进行重复运算。极易引发栈溢出。为了解决这一问题,需要彻底改变运算模式-------非递归动态规划
解法2,非递归实现动态规划算法
算法分析:
将题目中的信息全部整合到一个二维表中,该表对应了容量从4到0的各种情况下的最优解。
表的第一行,由于价值为0,所以全是0。表的第一列,由于容量为0,所以全是0。
其他的情况,需要通过程序进行求解。每一个空都对应一种情况,每种情况都受容量限制,每种情况都有两种操作(选与不选)。
与解法1不同的是,这里是自底而上求解(表中从左至右,从上至下)。将每次计算的结果保存在二维数组中,求解的下一个结果依赖于上一次已经求好的结果。这就解决了递归式重复计算的问题。
最终的答案,就在二维数组的右下角。
代码实现:
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个任务中最优收益为多少?
代码实现:
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)