动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。
动态规划算法与分治算法类似。其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解。
动态规划可以通过填表的方式来逐步推进,得到最优解。
基本思想与策略编辑:
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
动态规划最经典的就是背包问题:
背包问题可以通过动态规划算法来实现。
背包问题:
背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。
其中又分01背包和完全背包。
- 完全背包:每种物品可以放多次。
- 01背包:每个物品最多放一个。
无限背包可以转化为01背包。
i:行坐标,代表物品。
j:列坐标,代表物品的容量。
v[i][j]:前 i 个物品中能够装入容量为j的背包中的最大价值。
装入顺序是从上到下,从左到右。
利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中。即对于给定的n个物品,设v[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量。
令v[i][j]表示在前 i 个物品中能够装入容量为j的背包中的最大价值
可以推出下面的结论:
1 v[i][0]=v[0][j]=0;
表示填入表第一行和第一列是0。
2 当w[i] > j 时:v[i][j]=v[i-1][j]
该公式描述的是:当准备加入商品的容量大于当前背包的容量时,就直接使用上一个单元格的装入策略。
3 当j >=w[i]时:v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
该公式描述的是:当准备加入的商品的容量小于等于当前背包的容量的装入的策略。
v[i-1][j]:就是上一个单元格的装入的最大值。
v[i]:表示当前要装入商品的价值。
v[i-1][j-w[i]] :表示在前 i-1 个物品中能够装入容量为 j-w[i] 的背包中的最大价值。
代码实现如下:
//01背包问题
public static void beibao01(){
int[] w = {1, 4, 3};//物品的重量
int[] val = {1500, 3000, 2000};//物品的价值
int m = 4;//背包的容量,这里指的是能放多重的东西
int n = val.length;//物品的个数
//v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
int[][] v = new int[n + 1][m + 1];// 4 5
//用于存放符合条件的路径
int [][] path=new int [n+1][m+1];
//由之前的公式来动态规划处理
for (int i=1;i<v.length;i++){//i=2 不处理第一行i从第一行开始
for (int j=1;j<v[0].length;j++){
if(w[i-1]>j){//因为我们程序i 是从1开始的,w[]数组的下标识从0开始的,所以是w[i-1]
v[i][j]=v[i-1][j];
}else {
if(v[i-1][j]<val[i-1]+v[i-1][j-w[i-1]]){//符合情况,val[i-1]+v[i-1][j-w[i-1]]
v[i][j]=val[i-1]+v[i-1][j-w[i-1]];
path[i][j]=1;
}else {
v[i][j]=v[i-1][j];
}
}
}
}
for (int i=0;i<v.length;i++){
for (int j=0;j<v[i].length;j++){
System.out.print(v[i][j]+" ");
}
System.out.println();
}
int i=path.length-1;//行的最大下标
int j=path[0].length-1;//列的最大下标
while (i>0&&j>0){//从path的最后开始找
if(path[i][j]==1){
System.out.println("把第"+i+"个物品放入了背包");
j=j-w[i-1];
}
i--;
}
}
运行结果:
如果是完全背包问题可以由01背包问题改进得到:
把当j >=w[i]时:v[i][j]=max{v[i-1][j], val[i]+v[i-1][j-w[i]]}
改为:当j >=w[i]时:v[i][j]=max{v[i-1][j], val[i]+v[i][j-w[i]]}
代码如下:
//完全背包问题
public static void beibaowanquan(){
int[] w = {1, 4, 3};//物品的重量
int[] val = {1500, 3000, 2000};//物品的价值
int m = 4;//背包的容量,这里指的是能放多重的东西
int n = val.length;//物品的个数
//v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
int[][] v = new int[n + 1][m + 1];// 4 5
//用于存放符合条件的路径
int [][] path=new int [n+1][m+1];
//由之前的公式来动态规划处理
for (int i=1;i<v.length;i++){//i=2 不处理第一行i从第一行开始
for (int j=1;j<v[0].length;j++){
if(w[i-1]>j){//因为我们程序i 是从1开始的,w[]数组的下标识从0开始的,所以是w[i-1]
v[i][j]=v[i-1][j];
}else {
//需要修改的地方在这
if(v[i-1][j]<val[i-1]+v[i][j-w[i-1]]){//由基本的01背包问题转化而来,可以多次重复取无限次,从当前行就能开始取,不必从前面去取val[i-1]+v[i][j-w[i-1]],就能多次取重复值
v[i][j]=val[i-1]+v[i][j-w[i-1]];
path[i][j]=1;
}else {
v[i][j]=v[i-1][j];
}
}
}
}
for (int i=0;i<v.length;i++){
for (int j=0;j<v[i].length;j++){
System.out.print(v[i][j]+" ");
}
System.out.println();
}
int i=path.length-1;//行的最大下标
int j=path[0].length-1;//列的最大下标
while (i>0&&j>0){//从path的最后开始找
if(path[i][j]==1){
System.out.println("把第"+i+"个物品放入了背包");
j=j-w[i-1];
}
i--;
}
}