30. 贪心算法

一、什么是贪心算法

  贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,贪心算法并不从整体最优上加以考虑,它所做出的选择只是在某种意义上的局部最优解。贪心算法的基本思路是从问题的某一个初始解出发,然后一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,它的选取应该满足局部优化的条件。

  贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。贪心算法的可行性需要满足两个要素:所求问题的整体最优解可以通过一系列局部最优的选择(即贪心选择)来达到;问题的最优子结构性质是该问题可用贪心算法求解的关键特征。

  总的来说,贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即贪心)的选择,从而希望导致结果是全局最好或最优的算法。

贪心算法所得到的结果不一定是最优的结果,但是都是相对接近最优解的结果。

二、零钱兑换问题

  零钱兑换问题主要涉及到如何将一定金额的钱兑换成零钱,并且通常希望以最少的硬币或纸币数量来完成兑换。这个问题在日常生活中非常常见,比如消费者在购物后收到的找零,或者需要将大面额钞票换成零钱的情况。

  解决零钱兑换问题,可以采用以下几种方法:

  • 贪心算法:这是一种直观且简单的策略,其基本思想是从最大面额的硬币或纸币开始,尽可能多地使用这些大面额零钱进行兑换,然后逐渐使用较小面额的零钱,直到兑换完所需的全部金额。然而,贪心算法并不总是能够得到最优解,即使用最少的硬币或纸币数量。在某些特定的面额组合下,贪心算法可能会陷入局部最优而非全局最优。
  • 动态规划:当贪心算法无法得出最优解时,可以考虑使用动态规划。动态规划通过把原问题分解为相对简单的子问题,并保存子问题的解来避免重复计算,最终推导出原问题的最优解。在零钱兑换问题中,可以设定一个数组来保存每个金额对应的最少硬币数量,并从小到大逐步计算出每个金额的最优解。
  • 回溯法:对于更复杂的情况,特别是当硬币或纸币的面额组合不规则时,可以采用回溯法来尝试所有可能的兑换组合。回溯法通过递归地尝试不同的兑换方式,并在发现当前方式不可行时回退到上一步,直到找到可行的最优解。
#include <stdio.h>

void GreedyCoinChange(int coins[], int coin_kind, int amount);

int main() 
{
    int amount = 0;
    int coins[] = {5, 2 , 1};                                                   // 硬币面值数组,从大到小排列
    int coin_kind = sizeof(coins) / sizeof(coins[0]);                           // 硬币种类数

    printf("请输入要兑换的金额数: ");
    scanf("%d", &amount);

    if (amount >= 0) 
    {
        GreedyCoinChange(coins, coin_kind, amount);                             // 调用贪心找零函数
    } else 
    {
        printf("输入的金额数不能为负数\n");
    }

    return 0;
}
/**
 * @brief 贪心算法零钱兑换问题
 * 
 * @param coins 硬币面值数组
 * @param coin_kind 硬币种类数
 * @param amount 兑换的金额数
 */
void GreedyCoinChange(int coins[], int coin_kind, int amount) 
{
    int remainingAmount = amount;                                               // 剩余需要找零的金额
    int coinCount[coin_kind];                                                   // 用于记录每种硬币的数量

    for (int i = 0; i < coin_kind; i++)
    {
        coinCount[i] = 0;                                                       // 初始化硬币数量为0
    }
  
    for (int i = 0; i < coin_kind; i++)                                         // 遍历硬币面值,从大到小尝试找零
    {
        while (remainingAmount >= coins[i]) 
        {
            coinCount[i]++;                                                     // 增加该面值硬币的数量
            remainingAmount -= coins[i];                                        // 减去已找零的金额
        }

        if (remainingAmount == 0) 
        {
            break;                                                              // 找到足够的硬币后退出循环
        }
    }

    // 打印找零结果
    printf("兑换的金额数: %d\n", amount);
    for (int i = 0; i < coin_kind; i++) 
    {
        if (coinCount[i] > 0) 
        {
            printf("面值为 %d 的硬币的个数为 %d\n", coins[i], coinCount[i]);
        }
    }
}

posted @ 2023-08-12 19:57  星光映梦  阅读(30)  评论(0编辑  收藏  举报