国王与金矿
有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?(其实就是经典的背包问题,当然这个问题是来微信公众号-程序员小灰的,当然内容都是有自己思考的,他的文章中代码部分有部分错误)
500金/5人 400金/5人 200金/3人 300金/4人 350金/3人
最简单的思路还是暴力,先找出可以挖的矿的最值,显然是2 ~ 3,也就是说从可以五个矿里面选择2 ~ 3个矿开始挖,排列组合一下,再剔除人力要求超过10的,取挖金数最多的就好
int maxGold(const std::vector<int> &gold, const std::vector<int> &manNumNeed, int manNum){//增加空间开销std::vector<int> m = manNumNeed;//需要排序算法,有一定时间复杂度O(nlogn)sort(manNumNeed);int i = 0;int min = 0;int max = 0;int sum = 0;while (sum <= manNum && i < m.size()){sum += m[i];++i;++max;}i = m.size();while (sum <= manNum && i >= 0){sum += m[i];--i;++min;}for (int j = min; j <= max; ++j){// 这里进行排列组合并求人数需求,显然又是一个指数级算法}}
显然暴力枚举是不行的,那么我们用动态规划思想想一想:四座金矿与五座金矿的问题有什么关联呢。显然第五座金矿存在挖和不挖的情况,如果不挖,那么就是四座金矿的问题,如果挖,那就是四座金矿7个人的问题。
我们用gold[N-1],manN-1分别表示开采第N座矿所得到的金与所需要的人,用F(N,M)表示N座金矿M个人的问题,那么
F(N, M) = MAX(F(N - 1, M), F(N - 1, M - man[N - 1]) + gold[N - 1])F(5, 10) = MAX(F(4, 10), F(4, 10 - 3) + 350)F(4, 10) = MAX(F(3, 10), F(3, 7 - 4) + 300)if (M > man[1]){F(1, M) = gold[0];}else{F(1, M) = 0;}
不妨先把递归算法先写出来
#define D_N 100int man[D_N];int gold[D_N];int F(int N, int M){if (N == 1){if (M > man[0]){return gold[0];}else{return 0;}}else{int a = F(N - 1, M);int b = F(N - 1, M - man[N - 1])+gold[N-1];return (a > b) ? a : b;}}
其结构类似于二叉树,时间复杂为O(2^n),空间复杂度为O(n)
如何将其转换成非递归呢,因为我们无法确定底层每个F(1,i )的i具体有哪些,因为确定过程涉及到排列组合,没必要将问题复杂化,所以我们将F(F1,i)的所有数据都存下来,那么必定能得出F(2,i)的所有值,依此类推,求出我们需要的F(N, M);
当然如果对preResult数组理解不了,那么创建一个二维数组F[N][M+1],从F[1][i]逐步填充,,便不需要移动,也便于理解。算法实现如下:
int maxgoldold(const std::vector<int> &gold, const std::vector<int> &man, int N, int M){int Fn_m = 0;std::vector<int> preResult(M+1, 0);std::vector<int> result(M+1);// 先填充栈底数据// preResult[j]相当于F(1,j)// 也就是说这里的j相当于工人数量,这里已经考虑工人数量为0的可能for (int j = 1; j < M; ++j){if (j >= man[0]){preResult[j] = gold[0];}}// i是金矿数量,j是工人数量// man与gold显然都是与第i+1个金矿相关的// 因为第一个金矿已经分析存值了,所以从第二个开始for (int i = 1; i < N; ++i){//工人不需要从0个开始判断for (int j = 1; j <= M; ++j){// 如果工人比当前矿所需人还少,那当前矿显然只能放弃,也就是max中选左边的if (j < man[i]){result[j] = preResult[j];}else{// 考虑 j-man[i]==0 所以前面result数组长度为M+1result[j] = max(preResult[j], preResult[j - man[i]] + gold[i]);}}preResult = result;}return result[M];}
点击关注,持续推送质量好文;如有不当,欢迎指出;转载请注明出处。
