题目
很久很久以前,有一位国王拥有5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人人数也不同。例如有的金矿储量是500kg黄金,需要5个工人来挖掘:有的金矿储量是200kg黄金,需要3个工人来挖掘…
如果参与挖矿的工人的总数是10。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半的金矿。要求用程序求出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
总共10个工人,A:400kg黄金/5人 B:500kg黄金/5人 C:200kg黄金/3人 D:300kg黄金/4人 E:350kg黄金/3人
n/w | 1个工人 | 2个工人 | 3个工人 | 4个工人 | 5个工人 | 6个工人 | 7个工人 | 8个工人 | 9个工人 | 10个工人 |
---|---|---|---|---|---|---|---|---|---|---|
400kg黄金/5人 | ||||||||||
500kg黄金/5人 | ||||||||||
200kg黄金/3人 | ||||||||||
300kg黄金/4人 | ||||||||||
350kg黄金/3人 |
解决方法:
贪心算法:依次求得局部最优解,最终得到全局最优解
动态规划:把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推
动态规划的要点:确定全局最优解和最优子结构之间的关系,以及问题的边界
原问题分解成子问题进行求解(类似于背包问题)
动态规划的状态转移方程式:
我们把金矿数量设为n,工人数量设为w,金矿的含金量设为数组g[ ],金矿所需开采人数设为数组p[ ]. 设F(n, w)为n个金矿、w个工人时的最优收益函数,那么状态转移方程式如下。
F(n,w) = 0 (n=0或w=0)
问题边界,金矿数为0或工人数为0的情况。
F(n,w)= F(n-1,W) (n≥1, w
当所剩工人不够挖掘当前金矿时,只有一种最优子结构。
F(n,w) = max(F(n-1,w), F(n-1,w-p[n-1])+g[n-1]) (n≥1, w≥p[n-1])
在常规情况下,具有两种最优子结构(挖当前金矿或不挖当前金矿)。
自底向上求解
w < p[n-1] : F(n,w)= F(n-1,W) (n≥1, w
w ≥ p[n-1] : F(n,w) = max(F(n-1,w), F(n-1,w-p[n-1])+g[n-1])
例如:F(2,10) = max(F(2-1,10),F(2-1,10-5)+500) = max(F(1,10),F(1,5)+500) = max(400,400+500) = 900
n/w | 1个工人 | 2个工人 | 3个工人 | 4个工人 | 5个工人 | 6个工人 | 7个工人 | 8个工人 | 9个工人 | 10个工人 |
---|---|---|---|---|---|---|---|---|---|---|
400kg黄金/5人 | 0 | 0 | 0 | 0 | 400 | 400 | 400 | 400 | 400 | 400 |
500kg黄金/5人 | 0 | 0 | 0 | 0 | 500 | 500 | 500 | 500 | 500 | 900 |
200kg黄金/3人 | 0 | 0 | 200 | 200 | 500 | 500 | 500 | 700 | 700 | 900 |
300kg黄金/4人 | 0 | 0 | 200 | 300 | 500 | 500 | 500 | 700 | 800 | 900 |
350kg黄金/3人 | 0 | 0 | 350 | 350 | 500 | 550 | 650 | 850 | 850 | 900 |
package some_problem;
/**
* Copyright (C), 2019-2020
* author candy_chen
* date 2020/7/30 16:00
* version 1.0
* Description: 测试
*/
/**
*
*/
public class GetBestGoldMining {
/**
* 获得金矿最优收益
* @param w 工人数量
* @param n 可选金矿数量
* @param p 金矿开采所需的工人数量
* @param g 金矿储量
* @return
*/
//递归进行求解
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]);
}
/**
* 获得金矿最优收益
* @param w 工人数量
* @param p 金矿开采所需的工人数量
* @param g 金矿数量
* @return
*/
//递归做了很多重复的计算,当金矿越来越多,递归层次越来越深,重复调用也就越来越多,无谓的调用必然会降低程序的性能
//利用双循环来填充一个二维数组,时间复杂度和空间复杂度都是O(nw)
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[i-1]] + g[i-1]);
}
}
}
//返回最后1个格子的值
return resultTable[g.length][w];
}
// 并不需要保存整个表格,无论金矿多少座,我们只保存1行的数据即可,在计算下一行时,要从右向左统计,把旧的数据一个一个替换掉
//时间复杂度是O(nw) 空间复杂度是O(n)
/**
* 获得金矿最优收益
*
* @param w 工人数量
* @param p 金矿开采所需的工人数量
* @param g 金矿数量
* @return
*/
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]);
}
}
}
//返回最后一个格子的值
return results[w];
}
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));
}
}
说明:作者根据网络资料进行搜索学习,理解整理 若有侵权联系作者
参考:程序员小灰