挖金矿
一、问题描述
有 5 座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是 10 人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
二、算法分析
w 表示总共人数,n 表示金矿数,
排列组合
每一座金矿都有挖与不挖两种选择,排列组合起来就有 2n 种选择。对所有可能性做遍历,剔除那些使用工人数超过 10 的选择,在剩下的选择里找出获得金币数最多的选择。
时间复杂度:O(2n)。
递归
每个金矿存在"挖"、"不挖"两种情况,可以同时内部调用两次递归方法,表示"挖"和"不挖"。
void A() {
if (边界条件) return x;
// 挖
A();
// 不挖
A();
}时间复杂度:O(2n) 开辟空间:递归深度 O(n)
动态规划
这个问题与 0-1 背包问题相同,动态规划时的策略也是:当前金矿是否应该挖,挖与不挖的价值比较。整理出以下表格。
【状态】是 f(w),【边界】是 f(w < 3) = 0;状态方程 f(w) = max{ f(w - 1), f(w - i) + vi }, w > i
i 表示当前金矿需要的人数,vi 表示当前金矿的价值。
时间复杂度:O(n*w)
空间复杂度:O(n*w)
如果不保留挖金矿信息,只输出最高金币数,可以由上发现,每一层的数据只与上一层有关,那么就可以由上至下,只用一位数组保存状态,空间复杂度 O(w)。
比较
当工人数 w -> ∞ 时,动态规划的时间复杂度和空间复杂度与 w 成正比,所以增长较快;而递归算法与 w 无关,所以不增长,此时动态规划的效率就没有递归的好。
三、代码实现
①、递归(自底向上)
#include <stdio.h>
#include <stdlib.h>
#define MAX(a, b) (a) > (b) ? (a) : (b)
typedef struct GoldMine {
int worker;
int gold;
} GoldMine;
/**
* mineNum 金矿数
* workerNum 挖矿总人数
* curMine 当前金矿(从 0 开始)
* curWorker 当前人数(从 0 开始)
* max 当前最大金币数
*/
int monerFinderAlgorithm(GoldMine* mines, int mineNum, int workerNum, int curMine, int curWorker, int max)
{
GoldMine curGoldMine = mines[curMine];
// 金矿挖完了 || 人数不够了
if (curMine >= mineNum || curWorker + curGoldMine.worker > workerNum) return max;
// 挖
int dig = monerFinderAlgorithm(mines, mineNum, workerNum, curMine + 1, curWorker + curGoldMine.worker, max + curGoldMine.gold);
// 不挖
int noDig = monerFinderAlgorithm(mines, mineNum, workerNum, curMine + 1, curWorker, max);
return MAX(dig, noDig);
}
int main()
{
GoldMine mines[5] = { {3, 200}, {4, 300}, {3, 350}, {5, 400}, {5, 500} };
printf("\n\n%d", monerFinderAlgorithm(mines, 5, 10, 0, 0, 0));
//GoldMine mines[2] = { {4, 300}, {5, 500} };
//printf("\n\n%d", monerFinderAlgorithm(mines, 2, 10, 0, 0, 0));
return 0;
}
②、动态规划
#include <stdio.h>
#include <stdlib.h>
#define MAX(a, b) (a) > (b) ? (a) : (b)
typedef struct GoldMine {
int worker;
int gold;
} GoldMine;
/// mineNum 金矿数 worker 工人数
int monerFinderAlgorithm(GoldMine* mines, int mineNum, int worker)
{
if (worker == 0 || mineNum == 0) return 0;
// i 列 j 行
int i = 0, j = 0;
// 二维数组,从左向右,第一个 [ ] 是列,第二个 [ ] 是行
int goldMatrix[worker + 1][mineNum];
// 初始化并打印二维数组
for (; j < mineNum; j++) {
for (i = 0; i <= worker; i++) {
goldMatrix[i][j] = 0;
printf("0 "); // printf("%d ", goldMatrix[i][j]);
}
printf("\n");
}
printf("\n");
GoldMine mine;
for (i = 1; i <= worker; i++) {
for (j = 0; j < mineNum; j++) {
mine = mines[j];
// 挖矿人数不够
if (mine.worker > i) {
// 第一个存储 0,非第一个存储前一个 j 的值
goldMatrix[i][j] = (j == 0) ? 0 : goldMatrix[i][j-1];
}
// 挖矿人数足够
else {
// 第一个直接存储,非第一个存储 MAX{ 不加入 j 的值, 加入 j 的值 },j -1 是因为剔除了当前
goldMatrix[i][j] = (j == 0) ? mine.gold : MAX(goldMatrix[i][j-1], goldMatrix[i-mine.worker][j-1] + mine.gold);
}
}
}
// 打印二维数组内容
for (j = 0; j < mineNum; j++) {
for (i = 1; i <= worker; i++)
printf("%d ", goldMatrix[i][j]);
printf("\n");
}
printf("\n");
// 挖哪些矿
int curWorker = worker;
for (j = mineNum - 1; j >= 0; j--) {
mine = mines[j];
if (curWorker == 0) {
break;
}
// 根据变换公式从上至下获得物品
if (goldMatrix[curWorker][j] - goldMatrix[curWorker - mine.worker][j-1] == mine.gold) {
printf("%d ", mine.worker);
curWorker -= mine.worker;
}
}
return goldMatrix[worker][mineNum - 1];
}
int main()
{
GoldMine mines[5] = { {3, 200}, {4, 300}, {3, 350}, {5, 400}, {5, 500} };
printf("\n\n%d", monerFinderAlgorithm(mines, 5, 10));
//GoldMine mines[2] = { {4, 300}, {5, 500} };
//printf("\n\n%d", monerFinderAlgorithm(mines, 2, 10));
return 0;
}
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 200 200 200 200 200 200 200 200
0 0 200 300 300 300 500 500 500 500
0 0 350 350 350 550 650 650 650 850
0 0 350 350 400 550 650 750 750 850
0 0 350 350 500 550 650 850 850 900
5 5
900
空间复杂度 O(n)
/// mineNum 金矿数 worker 工人数
int monerFinderAlgorithm(GoldMine* mines, int mineNum, int worker)
{
if (worker == 0 || mineNum == 0) return 0;
int* preGoldMatrix = (int *)calloc(sizeof(int), worker);
int* goldMatrix = (int *)calloc(sizeof(int), worker);
GoldMine mine;
for (int i = 0; i < mineNum; i++) {
mine = mines[i];
// 从 1 开始计算
for (int j = 1; j <= worker; j++) {
// 挖矿人数不够
if (j < mine.worker) {
goldMatrix[j-1] = preGoldMatrix[j-1];
}
// 挖矿人数足够
else {
// 从上一层中 [j - mine.worker - 1] 和 [j - 1] 中获取最大值
goldMatrix[j-1] = MAX(preGoldMatrix[j-1], preGoldMatrix[j - mine.worker - 1] + mine.gold);
}
printf("%d ", goldMatrix[j - 1]);
}
printf("\n");
// 打印 preGoldMatrix、goldMatrix 数组的内容
// for (int k = 0; k < worker; k++) {
// printf("%d ", preGoldMatrix[k]);
// }
// printf("\n");
//
// for (int k = 0; k < worker; k++) {
// printf("%d ", goldMatrix[k]);
// }
// printf("\n\n");
for (int k = 0; k < worker; k++) {
// 不能使用 preGoldMatrix = goldMatrix; 这是指针赋值,preGoldMatrix 与 goldMatrix 内存地址一样
preGoldMatrix[k] = goldMatrix[k];
}
//printf("%p %p", preGoldMatrix, goldMatrix); // 打印数组的内存地址
}
return goldMatrix[worker - 1];
}