挖金矿

一、问题描述

有 5 座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是 10 人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

二、算法分析

w 表示总共人数,n 表示金矿数,

  1. 排列组合

    每一座金矿都有挖与不挖两种选择,排列组合起来就有 2n 种选择。对所有可能性做遍历,剔除那些使用工人数超过 10 的选择,在剩下的选择里找出获得金币数最多的选择。

    时间复杂度:O(2n)。

  2. 递归

    每个金矿存在"挖"、"不挖"两种情况,可以同时内部调用两次递归方法,表示"挖"和"不挖"。

    void A()
    {
    if (边界条件) return x;
    // 挖
    A();
    // 不挖
    A();
    }

    时间复杂度:O(2n) 开辟空间:递归深度 O(n)

  3. 动态规划

    这个问题与 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)

  4. 比较

    当工人数 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("

%d", monerFinderAlgorithm(mines, 5, 10, 0, 0, 0));
    
    //GoldMine mines[2] = { {4, 300}, {5, 500} };
    //printf("

%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("
");
    }
    printf("
");
    
    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("
");
    }
    printf("
");
    
    // 挖哪些矿
    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("

%d", monerFinderAlgorithm(mines, 5, 10));
    
    //GoldMine mines[2] = { {4, 300}, {5, 500} };
    //printf("

%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("
");
        
        // 打印 preGoldMatrix、goldMatrix 数组的内容
//        for (int k = 0; k < worker; k++) {
//            printf("%d    ", preGoldMatrix[k]);
//        }
//        printf("
");
//
//        for (int k = 0; k < worker; k++) {
//            printf("%d    ", goldMatrix[k]);
//        }
//        printf("

");
        
        for (int k = 0; k < worker; k++) {
            // 不能使用 preGoldMatrix = goldMatrix;  这是指针赋值,preGoldMatrix 与 goldMatrix 内存地址一样
            preGoldMatrix[k] = goldMatrix[k];
        }
        
        //printf("%p   %p", preGoldMatrix, goldMatrix);  // 打印数组的内存地址
    }
    return goldMatrix[worker - 1];
}

四、内容来源

漫画:什么是动态规划?

原文地址:https://www.cnblogs.com/dins/p/dig-gold.html