动态规划
- 动态规划问题的一般形式就是求最值问题。
- 求解动态规划的核心问题是穷举
- 动态规划的穷举有点特别,因为这类问题存在“重叠子问题”,所以可以减少穷举的过程,可以使用备忘录或者dpTable的方法。
- 通常解决动态规划问题要写出状态方程。
public class Fib {
public static void main(String[] args) {
System.out.println(fib(5));
System.out.println(fibMemo(5));
System.out.println(fibTable(4));
}
//暴力递归
public static int fib(int n){
if (n == 1 || n == 2){
return n;
}else{
return fib(n-1) + fib(n-2);
}
}
//带备忘录的递归
private static int fibMemo(int n){
int[] memo = new int[n+1];
return helper(memo,n);
}
private static int helper(int[] memo, int n) {
if (n == 1 || n == 2){
return n;
}
if (memo[n] != 0) return memo[n];
memo[n] = helper(memo,n-1) + helper(memo,n-2);
return memo[n];
}
//dp table
private static int fibTable(int n){
int[] dp = new int[n+1];
dp[1] = 1;dp[2] = 1;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
}
背包问题
背包容量5,各商品的重量分别为2,1,3,2,各商品价值12,10,20,15.
如何拿可以让背包装下价值最大。
思路
可以使用动态规划,暴力递归。可以写出状态方程
- 设F(i,j)是最优解的物品总价值,在不包括第i个物品的子集中,最优子集的价值为F(i-1,j);
- 在包括第i个物品的子集中,如果j-wi > 0 的话,即能够装得下此物品的话。最优子集是 要么放进去wi的价值大,要么不放wi的价值大,所以最优解为 max{ vi+F(i-1,j-wi) , F(i-1),j};
- 所以递推式为
F(i,j) = { max(F(i-1,j),vi+F(i-1,j-wi)) ,j-wi>0 装的进去
装不进去 { F(i-1,j) j-wi < 0;
public class DynamicPro {
static int w = 5;//背包容量
static int[] weight = {2,1,3,2};//各物品重量
static int[] value = {12,10,20,15};//个物品的价值
static int[][] dp = new int[weight.length+1][w+1];//记录之前的状态
static List<Integer> result = new ArrayList<>();
public static void main(String[] args) {
traceback(4,5);
System.out.println("最大价值为"+knapsack(4,5));
System.out.println("选择的数组为"+result);
}
public static void traceback(int i,int w){
if(i==0||w==0) return;
if (w >= weight[i-1]){//如果装得下
if (knapsack(i, w) == value[i - 1] + knapsack(i - 1, w - weight[i - 1])){
result.add(i);
traceback(i-1,w-weight[i-1]);
}else{
traceback(i-1,w);
}
}else{
traceback(i-1,w);
}
}
private static int knapsack(int n, int w) {
for (int i = 1; i <= n ; i++) {
for (int j = 1; j <= w; j++) {
if (j < weight[i-1]){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1]);
}
}
}
return dp[n][w];
}
}
备忘录
即然耗时的原因是重复计算,那么我们可以造一个「备忘录」,每次算出某个子问题的答案后别急着返回,先记到「备忘录」里再返回;每次遇到一个子问题先去「备忘录」里查一查,如果发现之前已经解决过这个问题了,直接把答案拿出来用,不要再耗时去计算了。
代码
public class DynamicPro2 {
static int w = 5;//背包容量
static int[] weight = {2,1,3,2};//各物品重量
static int[] value = {12,10,20,15};//个物品的价值
static int[][] dp = new int[weight.length+1][w+1];//记录之前的状态
static int[][] memo = new int[weight.length + 1][w + 1];
static List<Integer> result = new ArrayList<>();
public static void main(String[] args) {
System.out.println("最大价值为"+knapsack(4,5));
getArray();
System.out.println("选择的数组为"+result);
}
private static int knapsack(int n, int j) {
if (n == 0) return 0;
if (memo[n][j] == 0){
if (j < weight[n-1]){
memo[n][j] = knapsack(n-1,j);
}else{
memo[n][j] = Math.max(knapsack(n-1,j),value[n-1]+knapsack(n-1,j-weight[n-1]));
}
}
return memo[n][j];
}
private static void getArray(){
for (int i = weight.length; i > 0 ; i--) {
if (memo[i][w] > memo[i-1][w]){
result.add(i);
w = w-weight[i-1];
}
}
}
}