在计算机科学中,贪心算法和动态规划是两种常用的算法设计策略。本文将通过一个经典的找零钱问题,详细讲解这两种算法的实现和应用。我们将会提供完整的C++代码,并对代码进行详细解释,帮助读者更好地理解和掌握这两种算法。
问题描述
找零钱问题是这样一个问题:给定不同面值的零钱和一个总金额,如何使用最少数量的零钱来凑出这个总金额。例如,假设我们有面值为1、5、14、18的零钱,需要凑出28元,那么可能的解包括:28=18+5+5 或 28=14+14。
输入输出格式
输入:
-
第一行:两个整数,分别表示零钱的种类数和需要凑的总金额。
-
第二行:若干个整数,表示每种零钱的面值。
输出:
-
分别输出贪心算法和动态规划算法得到的找零方案。
算法分析
贪心算法
贪心算法的核心思想是在每一步选择中都采取当前状态下最优的选择,从而希望导致最终结果最优。在找零钱问题中,贪心算法的策略是每次选择面值最大的零钱,尽可能多地使用这种零钱,直到无法再使用为止,然后继续选择次大的零钱,依此类推。
动态规划算法
动态规划算法通过把原问题分解为多个子问题来求解。对于找零钱问题,我们可以定义一个数组dp,其中dp[i]表示凑出金额i所需的最少零钱数量。通过填充电动态规划表,我们可以得到凑出总金额的最少零钱数,并通过回溯得到具体的找零方案。
代码实现
以下是使用C++实现的找零钱问题解决方案:
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
vector<int> greedyCoinChange(int Y, vector<int> coins) {
sort(coins.rbegin(), coins.rend()); // 降序排列
vector<int> result;
int remaining = Y;
for (int coin : coins) {
while (remaining >= coin) {
result.push_back(coin);
remaining -= coin;
if (remaining == 0) break;
}
if (remaining == 0) break;
}
return result;
}
vector<int> dpCoinChange(int Y, vector<int>& coins) {
vector<int> dp(Y + 1, INT_MAX);
vector<int> coinUsed(Y + 1, 0);
dp[0] = 0;
for (int i = 1; i <= Y; ++i) {
for (int coin : coins) {
if (coin <= i && dp[i - coin] != INT_MAX && dp[i - coin] + 1 < dp[i]) {
dp[i] = dp[i - coin] + 1;
coinUsed[i] = coin;
}
}
}
vector<int> result;
int current = Y;
while (current > 0) {
int coin = coinUsed[current];
result.push_back(coin);
current -= coin;
}
// 动态规划结果按降序排列
sort(result.rbegin(), result.rend());
return result;
}
int main() {
int n, Y;
cin >> n >> Y;
vector<int> coins(n);
for (int i = 0; i < n; ++i) {
cin >> coins[i];
}
// 贪心算法直接处理降序
vector<int> greedyResult = greedyCoinChange(Y, coins);
// 动态规划处理后排序
vector<int> dpResult = dpCoinChange(Y, coins);
// 格式化输出
cout << Y << "=";
for (size_t i = 0; i < greedyResult.size(); ++i) {
if (i != 0) cout << "+";
cout << greedyResult[i];
}
cout << endl;
cout << Y << "=";
for (size_t i = 0; i < dpResult.size(); ++i) {
if (i != 0) cout << "+";
cout << dpResult[i];
}
cout << endl;
return 0;
}
代码解析
贪心算法函数 greedyCoinChange
-
将零钱面值按降序排列,这样可以优先使用面值较大的零钱。
-
初始化一个空向量
result
来存储找零方案。 -
使用一个循环遍历每种面值的零钱,尽可能多地使用当前面值的零钱。
-
当剩余金额为零时,结束循环并返回找零方案。
动态规划函数 dpCoinChange
-
初始化一个动态规划数组
dp
,其中dp[i]
表示凑出金额i
所需的最少零钱数量。 -
初始化一个数组
coinUsed
来记录凑出每个金额时使用的最后一个零钱面值。 -
使用嵌套循环填充电动态规划表,外层循环遍历金额,内层循环遍历零钱面值。
-
通过回溯
coinUsed
数组构造找零方案。
示例输入与输出
输入
4 28
1 5 14 18
输出
28=18+5+5
28=14+14
总结
本文通过一个具体的找零钱问题,详细介绍了贪心算法和动态规划算法的实现过程。贪心算法简单直观,但在某些情况下可能无法得到最优解。而动态规划算法虽然时间复杂度较高,但可以保证得到最优解。在实际应用中,我们可以根据问题的具体特点选择合适的算法。希望本文能够帮助读者更好地理解和掌握这两种重要的算法设计策略。