动态规划-01背包问题

  • 问题描述:给定n个物品和一个容量为C的背包,请给出物品装入背包的方案,使得背包中物品的总价值M最大,并满足:
  • 每个物品I的重量为Wi,价值为Vi。
  • 每个物品不可拆分,要么完整装入背包,要么不在背包里。
  • 背包中物品的总重量不能超过容量C。
    在这里插入图片描述

分析一波,面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。

本问题是满足动态规划的,什么是动态规划?

  • 动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
  • 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

可以应用动态规划求解的问题主要由四个特点:

  1. 问题是求最优解
  2. 整体问题的最优解依赖于各个子问题的最优解
  3. 大问题分解成若干小问题,这些小问题之间还有相互重叠的更小的子问题
  4. 从上往下分析问题,从下往上求解问题
    (即是如果一个问题可以使用分治法来解决的,但是其子问题无法相互独,会出现重复计算,坚持使用分治会导致增加计算量。这时可以采取动态规划算法来解决问题)。

以下是两种不同的动态规划解决方案:

  • 自上而下:你从最顶端开始不断地分解问题,直到你看到问题已经分解到最小并已得到解决,之后只用返回保存的答案即可。这叫做记忆存储(Memoization)。
  • 自下而上:你可以直接开始解决较小的子问题,从而获得最好的解决方案。在此过程中,你需要保证在解决问题之前先解决子问题。这可以称为表格填充算法(Tabulation,table-filling algorithm)。
    例子详解

算法分析中动态规划的四个基本步骤
1、描述优解的结构特征。
2、递归地定义一个最优解的值。
3、自底向上计算一个最优解的值。
4、从已计算的信息中构造一个最优解。

解题思路:针对动态规划普遍采用建表的形势,通过观察建立好一张合适的表格用来记录子问题的最优解。
本题要求是最大的价值,而限制价值的是背包容量。
可以把背包容量划分为从1 到 c,随着容量的增加来不断扩大价值,而且物品的数量也是从少到多的选择,所以可以申明一个数组arr[arr_IDrow][arr_Wcolumn],则m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为 j 时所能获得的最大价值。

那么我们可以很容易分析得出 m[i][j] 的计算方法,
(1). j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿 m[ i ][ j ] = m[ i-1 ][ j ]
(2). j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。

  • 如果拿取,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。
  • 如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)
    究竟是拿还是不拿,自然是比较这两种情况那种价值最大。
    可以得到下表(为了方便表示递推式,多扩充了一行和一列,保证了递推式的通用)
    在这里插入图片描述
    如m[3][4],在面对第3件物品,背包容量为4时我们可以选择不拿,那么获得价值仅为第1件和第2件物品的价值22,如果拿,就要把第1件物品拿出来,放第3件物品,价值30,那我们当然是选择拿。m[3][4]=m[2][4-3]+20=10+20=30(4-3是当前容量减去放入3号物品的重量后包内剩下的容量);依次类推,得到m[4][5]就是考虑所有物品,背包容量为C时的最大价值.

由图可以得到状态转移方程:

if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

主要的算法:

//声明的变量
  private int[][] arr;
	private int arr_IDrow; //行 放id号
	private int arr_Wcolumn; //列 放背包重量
	private int flag[];      //是否装入,1为装入,0为不装。flag[1] = 1 表示id为1的物品装入
	private int M;//总价值
	private Map<Integer,Integer> idToV = new HashMap<>();  //id映射物品的价值
	private Map<Integer, Integer> idToW = new HashMap<>(); //id映射物品的重量
//动态规划解决问题
public void dpFun() {
  arr = new int[arr_IDrow+1][arr_Wcolumn+1];
  flag = new int[arr_IDrow+1];

  //开始判断,并且填表,填入商品的价格
  for(int row=1;row<=arr_IDrow;row++) {
    for(int col=1;col<=arr_Wcolumn;col++) {
      if(col>=idToW.get(new Integer(row))) {
        arr[row][col] = Math.max(arr[row-1][col],arr[row-1][col-idToW.get(row)]+idToV.get(row)) ;
      }else {
        arr[row][col]=arr[row-1][col];
      }
    }
  }
  M = arr[arr_IDrow][arr_Wcolumn];//最大容量
  traceback();//回溯判断是否拿取
}

另起一个 flag[ ] 数组,flag[i]=0表示不拿,flag[i]=1表示拿。
m[n][c]为最优值,如果m[n][c]=m[n-1][c] ,说明有没有第n件物品都一样,则flag[n]=0 ; 否则 flag[n]=1。当flag[n]=0时,由flag[n-1][c]继续构造最优解;当flag[n]=1时,则由flag[n-1][c-w[i]]继续构造最优解。以此类推,可构造出所有的最优解。

//回溯判断是否拿取
public void traceback()
{
    for (int i = arr_IDrow; i > 1; i--)
    {
        if (arr[i][arr_Wcolumn] == arr[i - 1][arr_Wcolumn])
            flag[i] = 0;
        else
        {
            flag[i] = 1;
            arr_Wcolumn -= idToW.get(i);
        }
    }
    //判断第一行id:1
    flag[1] = (arr[1][arr_Wcolumn] > 0) ? 1 : 0;
}

完整的代码:

public class pack0_1 {

	private int[][] arr;
	private int arr_IDrow; //行 放id号
	private int arr_Wcolumn; //列 放背包重量
	private int flag[];      //是否装入,1为装入,0为不装。flag[1] = 1 表示id为1的物品装入
	private int M;//总价值
	private Map<Integer,Integer> idToV = new HashMap<>();  //id映射物品的价值
	private Map<Integer, Integer> idToW = new HashMap<>(); //id映射物品的重量

	public pack0_1() {
		// TODO 自动生成的构造函数存根
		initArr();
		dpFun();
		outPlan();
	}

	//动态规划解决问题
	public void dpFun() {
		arr = new int[arr_IDrow+1][arr_Wcolumn+1];
		flag = new int[arr_IDrow+1];

		//开始判断,并且填表,填入商品的价格
		for(int row=1;row<=arr_IDrow;row++) {
			for(int col=1;col<=arr_Wcolumn;col++) {
				if(col>=idToW.get(new Integer(row))) {
					arr[row][col] = Math.max(arr[row-1][col],arr[row-1][col-idToW.get(row)]+idToV.get(row)) ;
				}else {
					arr[row][col]=arr[row-1][col];
				}
			}
		}
		M = arr[arr_IDrow][arr_Wcolumn];
		traceback();
	}

	//读取文件初始化数组
	private void initArr() {
		File inFile = new File("C:\\Users\\HP\\Desktop\\input.txt");
		InputStreamReader input = null;
		BufferedReader bf = null;
		try {
			input = new FileReader(inFile);
			bf = new BufferedReader(input);

			//按行读取文件
			String str = bf.readLine();
			arr_Wcolumn = Integer.valueOf(str);

			while((str=bf.readLine())!=null) {
				String[] strArr = new String[] {};
				strArr = str.split("\t");//按tab来切割字符串
				int i = 1;
				idToW.put(Integer.valueOf(strArr[0]), Integer.valueOf(strArr[i]));
				idToV.put(Integer.valueOf(strArr[0]), Integer.valueOf(strArr[i+1]));
			}
			arr_IDrow = idToV.size();

		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}

		try {
			if(input!=null||bf!=null) {
			input.close();
			bf.close();
			}
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}

	}

	//输出结果文件
	private void outPlan() {

		File outFile = new File("C:\\Users\\HP\\Desktop\\output.txt");
		OutputStreamWriter output = null;
		 try {
			output = new FileWriter(outFile);
			output.write(M+"\r\n");

			for (int i = 1; i < arr.length; i++) {
				output.write(""+i+"\t"+flag[i]+"\t"+idToV.get(i)+"\r\n");
			}

		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}

		 try {
			 if(output!=null) {
			output.close();
			}
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}

	//回溯判断是否拿取
	public void traceback()
	{
	    for (int i = arr_IDrow; i > 1; i--)
	    {

	        if (arr[i][arr_Wcolumn] == arr[i - 1][arr_Wcolumn])
	            flag[i] = 0;
	        else
	        {
	            flag[i] = 1;
	            arr_Wcolumn -= idToW.get(i);
	        }
	    }
	    //判断第一行id:1
	    flag[1] = (arr[1][arr_Wcolumn] > 0) ? 1 : 0;
	}

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
			new pack0_1();
	}
}

结果:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值