如何求解金矿问题


金矿问题题目

很久很久以前,有一位国王拥有5座金矿,每座金矿的黄金储量不同, 需要参与挖掘的工人人数也不同。例如有的金矿储量是500kg黄金,需要5个工人来挖掘;有的金矿储量是200kg黄金,需要3个工人来挖掘…… 如果参与挖矿的工人的总数是10。每座金矿要么全挖,要么不挖,不能 派出一半人挖取一半的金矿。要求用程序求出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

解题思路

首先分析题意得出这是一个动态规划问题,所谓动态规划就是把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到复杂问题的最优解。

动态规划的要点:

确定全局最优解和最优子结构之间的关系,以及问题的边界。这个关系用数学公式来表达的话,就叫作状态转移方程式 。

先找问题得边界:

一直把问题简化成在0个金矿或0个 工人时的最优选择,这个收益结果显然是0,也就是问题的边界 。

自底向上求解:

  • 状态转移方程: n是当前金矿储量,w是当前金矿所需人数 ,p是挖掘金矿所需人数数组,g是金矿储量数组
  • F(n,w) = F(n-1,w) (n>1,w<p[n-1])
  • F(n,w) = max( F(n-1,w),F(n-1,w-p[n-1])+g[n-1]) (n>1,w>=p[n-1])
  • 二维数组表示这张对应得表

递归求解代码

/*

* 获得金矿最优收益
* @param w 工人数量
* @param n 可选金矿数量
* @param p 金矿开采所需的工人数量
* @param g 金矿储量
  */
  public static int getBestGoldMining(int w, int n,int[] p, int[] g){
  if(w==0 || n==0){
   		return 0;
   }
   	if(w<p[n-1]){
   		return getBestGoldMining(w, n-1, p, g);
   	}
   	return Math.max(getBestGoldMining(w, n-1, p, g),getBestGoldMining(w-p[n-1], n*1, p, g)+g[n-1]);
   }

 public static void main(String[] args) {
 	int w = 10;
 	int[] p = {5, 5, 3, 4 ,3};
 	int[] g = {400, 500, 200, 300 ,350};
 	System.out.println(" 最优收益:" + getBestGoldMining(w,g.length, p, g));
}
//单纯用递归去计算得话时间复杂度就是O(2^n),在这个算法中有很多重复得调用计算

动态规划代码

/**
 * 获得金矿最优收益
 * @param w 工人数量
 * @param p 金矿开采所需的工人数量
 * @param g 金矿储量
*/
 public static int getBestGoldMiningV2(int w, int[] p, int[] g)
{
	//创建表格
	int[][] resultTable = new int[g.length+1][w+1];
	 //填充表格
 	for(int i=1; i<=g.length; i++){
 		for(int j=1; j<=w; j++){
 			if(j<p[i-1]){
 				resultTable[i][j] = resultTable[i-1][j];
 			}else{
 				resultTable[i]
				[j] = Math.max(resultTable[i-1]
				[j], resultTable[i-1][j-p[i1]]+ g[i-1]);
			 }
		 }
	 }
 	//返回最后1个格子的值
 	return resultTable[g.length][w];
 }
//动态规划求解时间和空间都是是O(nw)

代码优化

/**
* 获得金矿最优收益
* @param w 工人数量
* @param p 金矿开采所需的工人数量
* @param g 金矿储量
*/
public static int getBestGoldMiningV3(int w, int[] p, int[] g){
//创建当前结果
int[] results = new int[w+1];
 //填充一维数组
	for(int i=1; i<=g.length; i++){
		for(int j=w; j>=1; j--){
		if(j>=p[i-1]){
			results[j] = Math.max(results[j],results[j-p[i-1]]+ g[i-1]);
		}
	}
}
	//返回最后1个格子的值
	return results[w];
}

在表格中除第1行之外,每 一行的结果都是由上一行数据 推导出来的。我们以4个金矿9个工 人为例。 4个金矿、9个工人的最优结果,是由它的两个最优子结构,也就是3个 金矿、5个工人和3个金矿、9个工人的结果推导而来的。这两个最优子 结构都位于它的上一行。 所以,在程序中并不需要保存整个表格,无论金矿有多少座,我们只保 存1行的数据即可。在计算下一行时,要从右向左统计(读者可以想想 为什么从右向左),把旧的数据一个一个替换掉。空间复杂度降低到了O(n)


文章作者: 冰冰的小屋
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 冰冰的小屋 !
  目录