紧接着上一篇动态规划问题,现在我们开始探讨一个新的问题,问:有一个发现了5个金矿,每一个金矿的储量不同,需要参与挖掘的工人数也不通,参与挖矿工人的总数量是10人,每一座金矿要么全挖,要么不挖,不能派一般人挖一半的金矿,想要得到最多的黄金,应该挖取哪几座金矿呢?金矿1储存500金 需5人,金矿2储存400金需要5人,金矿3储存350金矿需要3人,金矿4储存300人需要4人,金矿5储存200金需要3人
经过上篇爬楼梯的问题,我们已经知道了动态规划的三个核心元素,最优子结构、边界、状态转移方程式,好现在我们就开始分析一下看在五个金矿全部在的情况下 可以分为两个状态1、第五金矿不挖、前四个金矿中挖两个,2第五个金矿选择挖,和前四个金矿选择挖一个 同时人数剩余7人。这个两个结构都是这个大问题的最优子结构 但是这个最优子结构和这个金矿选择有什么关于呢?没错 就是两者选其中最大的一个。
我们将金矿数设置为N,工人数为W,金矿的数量设置为数组G[],金矿的用工量设置为数组p[]。则得到结论:
F(5,10)=MAX(F(4,10),F(4,10-P[4])+G[4])
ok 现在我们 找到了最优子结构和和状态转移方程式,但是程序的边界是什么呢?这个很简单边界就是只剩下一个金矿可以选择我们必须挖这个唯一的金矿,但是这个时候我们需要满足一个 条件就是剩余的总人数大于挖改矿需要的人数。
同样我们和上篇文章一样我们可以首先开始递归算法。
1、递归算法
/** * * @param G 金矿储存量 * @param N 金矿数 * @param W 工人数 * @param P 需要的挖矿人数 * @return */ public int getKingGold(int G[],int N,int W,int P[])throws Exception{ if (N>G.length)throw new Exception("金矿数不匹配!"); if(N<=1 && P[0] > W) return 0; //当数量人数少于 需要挖矿的人数时 if (N==1 && P[0]<=W) return G[0]; //最后一个矿时 人数多于需要挖矿的人数 if (N>1 && W < P[N-1])return getKingGold(G,N-1,W,P); int num1 = getKingGold(G,N-1,W,P); int num2 = getKingGold(G,N-1,W-P[N-1],P) + G[N-1]; return Math.max(num1,num2); }
2、备忘录算法
这里要做一个小改动,应为这个是要记录,将剩余人数和剩余金矿数作为的最大值记录下来
public int getKingGoldwithMap(int G[],int N,int W,int P[],Map<Integer,Integer> map)throws Exception{ if (N>G.length)throw new Exception("金矿数不匹配!"); if(N<=1 && P[0] > W) return 0; //当数量人数少于 需要挖矿的人数时 if (N==1 && P[0]<=W) return G[0]; //最后一个矿时 人数多于需要挖矿的人数 if (N>1 && W < P[N-1])return getKingGold(G,N-1,W,P); DicText obj = new DicText(N,W); if (map.containsKey(obj.hashCode())){ return map.get(obj.hashCode()); }else{ int num1 = getKingGold(G,N-1,W,P); int num2 = getKingGold(G,N-1,W-P[N-1],P) + G[N-1]; int result = Math.max(num1,num2); map.put(obj.hashCode(),result); return result; } } private static class DicText{ private int n; private int w; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DicText dicText = (DicText) o; return n == dicText.n && w == dicText.w; } @Override public int hashCode() { final int seed = 31; int num = seed * n +2 ; num = num * w + 1; return num; } public DicText(int n, int w) { this.n = n; this.w = w; } public int getN() { return n; } public void setN(int n) { this.n = n; } public int getW() { return w; } public void setW(int w) { this.w = w; } };
这里将金矿剩余数和人数的剩余数加在一起做成一个类,并将hashcode值进行重写。
三、动态规划的解法
代码为
public int getMostGold(int n,int w,int []g,int[] p){ int perReuslts []=new int[p.length]; int result [] = new int [p.length]; //填充边界格子的值 for (int i = 0;i<=n ;i++){ if (i<p[0]){ perReuslts[i] = 0; }else { perReuslts[i] = g[0]; } } //填充其余格子的值,外层循环是金矿的数量,内层循环是人工数 for (int i = 0;i<n ;i++){ for (int j = 0;j<=w;j++){ if (j<p[i]){ result[j] = perReuslts[j]; }else { result[j] = Math.max(perReuslts[j],perReuslts[j-p[i]] + g[i]); } } } return result[n]; }
根据表中的数据可以找到以上规律 可以求出数据。