算法分析与设计:贪心算法实现最少硬币找钱问题(支付+找零共花费硬币数最少)

硬币找钱问题

Problem Description

设有六种不同面值的硬币,各硬币的面值分别为 5分,1角,2角,5角,1元,2元。现要用这些面值的硬币来购物和找钱。购物时可以使用的各面值的硬币个数存于数组 C o i n [ 1 ; 6 ] Coin[1;6] Coin[1;6] 中,商店里各面值的硬币有足够多。在一次购物中希望使用最少的硬币个数。

例如,一次购物需要付款 0.55 元,没有 5 角的硬币,只好用 2 × 20 + 10 + 5 2 \times 20+10+5 2×20+10+5 共 4 枚硬币来付款。如果付出 1 元,找回 4 角 5 分,同样需要 4 枚硬币。但是如果付出 1.05 元(1枚1元和1枚5分),找回 5 角,只需要 3 枚硬币,这个方案用的硬币个数最少。

对于给定的各种面值的硬币个数和付款金额,编程计算使用硬币个数最少的交易方案,如果顾客手中的硬币总量不够付款金额,则输出impossible

Sample Input

文件input.txt给出输入数据。每一行有6个整数和一个有2位小数的实数,分别表示可以使用的各种面值的硬币个数和付款金额。

2 4 2 2 1 0 0.95
2 4 2 0 1 0 0.55
0 0 0 0 0 0 1

Sample Output

输出文件 output.txt

2
3
impossible


算法源代码

#include 
#include 
#include 
using namespace std;

int Coins[6];									   // 各面值硬币数
int CoinNum = 0;								   // 需要的最少硬币数
int Pay;										   // 需要支付的钱数
int CoinValue[6] = {5, 10, 20, 50, 100, 200};	   // 硬币面值,以分为单位
int ChangeValue[7] = {0, 5, 10, 20, 50, 100, 200}; // 用来找零的硬币面值,0为不找零的情况
int RealMoney = CoinValue[0] - ChangeValue[0];	   // 实际支付金额 = 支付硬币 - 找零硬币 【贪心变量】
ifstream input("input.txt");
ofstream output("output.txt");

/*****************************************************************
* 函数描述: 从文件中读取测试数据
*****************************************************************/
void getData()
{
	for (int i = 0; i < 6; ++i)
		input >> Coins[i];
	double costt;
	input >> costt;
	cout << "\n目标金额:" << costt << "元"
		 << "\n各币值数量:" << endl;
	Pay = (int)(costt * 100); //将输入的钱转为分为单位
	for (int i = 0; i < 6; ++i)
		cout << Coins[i] << " ";
}

/*****************************************************************
* 函数描述: 格式化输出结果
*****************************************************************/
void outputResult()
{
	if (CoinNum == 0 || Pay != 0)
	{
		cout << "\n============== impossible" << endl;
		output << "impossible" << endl;
	}
	else
	{
		cout << endl
			 << "各币值剩余数量:" << endl;
		for (int i = 0; i < 6; i++)
			cout << Coins[i] << " ";
		cout << endl
			 << "============== 支付和找零需要的最少总硬币数量:" << CoinNum << endl;
		output << CoinNum << endl;
	}
}

/*****************************************************************
* 函数描述:查看顾客手中是否还有 a 面值的硬币,
*          如果有则返回面值索引,否则返回 -1
* 函数返回:面值索引 or -1
*****************************************************************/
int contains(int a)
{
	for (int i = 0; i < 6; ++i)
		if (CoinValue[i] == a && Coins[i] > 0)
			return i;
	return -1;
}

/*****************************************************************
* 函数描述: 贪心法实现硬币找零问题,以实际支付金额作为贪心变量
*****************************************************************/
void Greed()
{
	// 顾客手中的硬币面值从大到小遍历选取
	for (int i = 5; i >= 0; --i)
	{
		if (Coins[i] > 0) // 如果顾客手中当前面值硬币有剩余
		{
			// 商家手中的硬币从小到大遍历选取,进行找零操作
			for (int j = 0; j <= i; ++j)
			{
				RealMoney = CoinValue[i] - ChangeValue[j]; // 实际支付金额 = 支付硬币 - 找零硬币
				// 只有当实际支付金额小于目标金额的时候才进行选取,否则需要增加找零硬币的金额(即,继续本层循环 j)
				if (Pay >= RealMoney)
				{
					if (Coins[i] >= Pay / RealMoney) //如果当前面值硬币找零后足够支付余额
					{
						int TempCoinNum = Pay / RealMoney; // 此步骤顾客消耗硬币数量
						CoinNum += TempCoinNum * 2;
						// 如果顾客具有该硬币,则无需使用找零,因为找零需要耗费2个硬币,而支付只需要一个硬币
						if (contains(RealMoney) != -1)
						{
							TempCoinNum = min(TempCoinNum, Coins[contains(RealMoney)]);
							CoinNum -= TempCoinNum;
							Coins[contains(RealMoney)] -= TempCoinNum; // 顾客手中相应硬币数减少
						}
						else
							Coins[i] -= Pay / RealMoney; // 顾客手中当前面值硬币数减少
						Pay = Pay % RealMoney;			 // 更新还需支付的金额
						if (contains(CoinValue[i]) == -1)
							break; // 如果顾客手中该硬币用完,直接用下一个小面值
					}
					else // 如果当前币值不够支付,则用完该币值
					{
						CoinNum += Coins[i];
						Pay = Pay - CoinValue[i] * Coins[i];
						Coins[i] = 0;
					}
				}
			}
		}
	}
}

int main()
{
	while (!input.eof())
	{
		CoinNum = 0;
		getData();		// 读取测试数据
		Greed();		// 贪心法实现硬币找零问题
		outputResult(); // 输出结果
	}
	input.close();
	output.close();
}

程序运行截图

算法分析与设计:贪心算法实现最少硬币找钱问题(支付+找零共花费硬币数最少)_第1张图片
算法分析与设计:贪心算法实现最少硬币找钱问题(支付+找零共花费硬币数最少)_第2张图片

算法分析与设计:贪心算法实现最少硬币找钱问题(支付+找零共花费硬币数最少)_第3张图片


贪心算法

所谓贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性。


核心思路

解决本问题在于贪心变量的找取。原始的硬币问题的贪心变量很直观,就是硬币从大到小选取(原始硬币问题的描述与解决见此链接)。而加上了找零的操作后,贪心变量就不那么直观了。本问题中的贪心变量是每个硬币的实际支付金额,加上找零的本质就是通过找零的排列组合,将硬币的面值变多了。

实 际 支 付 金 额 = 支 付 硬 币 − 找 零 硬 币 实际支付金额 = 支付硬币 - 找零硬币 =

注意的“坑”

其中有一点需要注意的就是,如果找零的排列组合(当前硬币的实际支付金额)存在等面值的真实硬币,则不用找零的操作,直接取硬币,因为找零需要耗费 2 枚硬币,而支付只需要一枚硬币。


博主为学生,正在学习,如博文内容有误,欢迎大家在评论区更正,共同进步~

你可能感兴趣的:(笔记,#,算法分析与设计,贪心算法,算法,数据结构,c++)