【算法】动态规划——背包算法

原文地址:http://blog.csdn.net/a784586/article/details/63262080


1.常用的算法设计技术:贪心算法,分治和动态规划。

贪心:寻找局部最优,代替全局最优。比如说不带权的区间调度问题,每次选取最早完成时间的作业。找到贪心的标准是最重要的,这种算法设计技术,需要对算法的有效性进行验证,贪心常常不一定有效。

分治:简而言之,分而治之。将一个复杂的大问题分解为若干个子问题求解;

动态规划:将一个复杂问题分解问若干子问题,这些子问题之间有关联和交集,避免重复计算。可以先来看一个简单的更容易理解的例子:走楼梯问题。


欢迎查看相关问题:动态规划之:防止重复计算【经典问题:走楼梯问题,斐波那数列】 【完成】


背包问题是各个领域的经典问题之一,今天就总结下,通俗易懂的帮助大家快速理解该算法。


背包问题:有N件物品和一个容量为W的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。




定义:OPT(i,w)为在1,2,3,.......,i 这么多件物品中重量和小余w的最优解;

i=0,1,2,3,4,5

wi:  w1,w2,w3,w4,w5-------------> 1,2,5,6,7

w: 11  书包容量(重量)不能超过w.


则:

(1). OPT(i,w)=0           if  i<=0

(2). OPT(i,w)=OPT(i-1,w)             if wi>w 【第i个重量超过了总重量W的限制】

(3).OPT(i,w)=max{OPT(i-1,w) ,   Vi+OPT(i-1,w-wi) }    otherwise


上述递归式很重要,如下让我们手工操作一遍,感受下这个过程,请自己画表格,填充试一试,加深理解:


上面第一行为0,1,2,.........,10,11。意思为总重量限制;

第一列指的是包含物品序号的集合;

在对应的物品序列集合和总重量限制下,白色部分红色的0为填充的值,即当总重量为0限制【第二列列头0】或者物品序号集合为空集合{  }【第二行的行头】时候,最大的价值OPT为红色部分所示的0。

下面根据如下的公式计算下表:

(1). OPT(i,w)=0           if  i<=0

(2). OPT(i,w)=OPT(i-1,w)             if wi>w 【第i个重量超过了总重量W的限制】

(3).OPT(i,w)=max{OPT(i-1,w) ,  Vi+OPT(i-1,w-wi) }      otherwise


继续按照递推公式填满下表:


         

          例如计算图标中的红色值是18,过程如下:

OPT(i,w)=OPT(3,5),此时Wi=W3=5>5不成立【Wi>W不成立】,

OPT(i,w)==max{OPT(2,5),18+OPT(2 ,5-5)}==max{OPT(2,5), 18+OPT(2,0)}==max{7,18}=18;

算法描述:

[plain]  view plain  copy
  1.          Input: n,     W1,W2,    ............ ,Wn,  V1,.......Vn  
  2.          for w=0  to  W  
  3.           M[0,w]=0;  
  4.   
  5.    for i=1 to n  
  6.          for w=1  to  W  
  7.          if(wi>w)  
  8. M[i,w]=M[i-1, w];  
  9.  else  
  10. M[i, w]=max{M[i-1, w],vi+M[i-1, w-wi]}  
  11.  return M[n,W];  


此算法的时间复杂度:O(nW)   



java代码实现:请理解上面的表格或者递推公式。下面代码复制到java环境改下对应包名即可运行。

[java]  view plain  copy
  1. package com.mytest.test001;  
  2.   
  3. public class knapspack {  
  4.     /** 
  5.      * 有N件物品和一个容量为W的背包。第i件物品的重量是w[i],价值是v[i]。 
  6.      * 求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。 
  7.      */  
  8.     public static void main(String[] args) {  
  9.         int w=11;//背包装入的总重量不能超过该值,使得总价值最大  
  10.         int n=5;//五个物品  
  11.         int[] value={1,6,18,22,28};//对应物品的价值  
  12.         int[] weight={1,2,5,6,7};//对应每个品的重量  
  13.         System.out.println("所得结果:"+findMaxValue(w,n,weight,value));  
  14.     }  
  15.   
  16.     private static int findMaxValue(int w,int n, int[] weight, int[] value) {  
  17.         int[][]max=new int[n+1][w+1];  
  18.           
  19.         for(int i=0;i<=w;i++)//M[n,W]  
  20.             max[0][i]=0;  
  21.           
  22.         for(int j=1;j<=n;j++)  
  23.             for(int k=1;k<=w;k++)  
  24.                 if(weight[j-1]>k){//第j个物品对应重量的下标减1,从0开始。  
  25.                     max[j][k]=max[j-1][k];//当加入的一个物品重量大于k,这个物品一定不能选  
  26.                 }else{  
  27.                     int a=max[j-1][k];//不选第j个物品  
  28.                     int b=value[j-1]+max[j-1][k-weight[j-1]];//可以选第j个物品,选择这个物品  
  29.                     max[j][k]=a>b ? a:b;//选择第j个和不选第j个物品,那个大,返回哪个;  
  30.                 }  
  31.           
  32.         //遍历数组结果,打印出来看看  
  33.         for (int[] is : max) {  
  34.             for (int i : is) {  
  35.                 System.out.print(i+"  ");  
  36.             }  
  37.             System.out.println();  
  38.         }  
  39.         return max[n][w];  
  40.           
  41.     }  
  42.   
  43.   
  44. }  



运行效果图:


【备注】:显然以上算法的时间复杂度为O(nw)。不是多项式时间内的解法。

【备注】:每个物品有多件,等其他非简单背包问题都可以转化为类似的简单背包问题。

【备注】:存在O(n)的近似算法。



---

我自己的代码:

public class Solution {
	// N见物品,每件重量w[i],价值v[i],背包总容量W
	public int dynamicP(int N, int[] w, int[] v, int W){
		int[][] dp = new int[N+1][W+1];
		int m = dp.length, n = dp[0].length;
		
		for(int i=1; i<dp.length; i++){
			for(int j=1; j<dp[0].length; j++){
				// dp[i][j] <-> w[i-1]+v[i-1] && 最大容量为j
				/*1、先取值为不加物品i的最大价值
				此处不需要计算dp[i][j-1]与dp[i-1][j]的关系,因为是按照取不取物品i进行分类讨论的
				不管dp[i][j-1]有没有取物品i,都会在第二步中计算出最优解
				*/
				dp[i][j] = dp[i-1][j];
				// 2、当物品i的重量允许加入背包,计算物品i加入后,剩余背包装i-1件物品,总重j-w[i]最优解为多少
				if(j-w[i-1]>=0){
					int temp = v[i-1]+dp[i-1][j-w[i-1]];
					dp[i][j] = Math.max(temp, dp[i][j]);
				}
			}
			this.show(dp);
		}
		return  dp[N-1][W];
	}
	
	public void show(int[][] a){
		System.out.println();
		for(int j=0; j<a[0].length; j++)
			System.out.print(j+"\t ");
		System.out.println();
		for(int i=0; i<a.length; i++){
			for(int j=0; j<a[0].length; j++)
				System.out.print(a[i][j]+"\t ");
			System.out.println();
		}
		System.out.println();
	}
	public static void main(String[] args) {
		Solution sol = new Solution();

		int[] w = {1,2,5,6,7};
		int[] v = {1,6,18,22,28};
		System.out.println(sol.dynamicP(w.length, w, v, 11));
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值