找零问题
硬币找零问题描述:
现存在一堆面值为 V1、V2、V3 … 个单位的硬币,问最少需要多少个硬币才能找出总值为 T 个单位的零钱?假设这一堆面值分别为 1、2、5、21、25 元,需要找出总值 T 为 63 元的零钱。
很明显,只要拿出 3 个 21 元的硬币就凑够了 63 元了。
基于上述动态规划的思想,我们可以从 1 元开始计算出最少需要几个硬币,然后再求 2 元、3元…每一次求得的结果都保存在一个数组中,以后需要用到时则直接取出即可。那么我们什么时候需要这些子问题的解呢?如何体现出由子问题的解得到较大问题的解呢?
其实,在我们从 1 元开始依次找零时,可以尝试一下当前要找零的面值(这里指 1 元)是否能够被分解成另一个已求解的面值的找零需要的硬币个数再加上这一堆硬币中的某个面值之和,如果这样分解之后最终的硬币数是最少的,那么问题就得到答案了。
参考链接:http://haolloyin.blog.51cto.com/1177454/352115
上述硬币面值不能用贪心算法来求得问题的最优解,而当硬币的面值分别为1,5,10,25时,此时用贪心算法是有效的。
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。在人民币找零问题中,其最优子结构性质表现为:
设c[i]是各面额人民币使用的数量,S[i]是商品价格为n时的最优解,数量为K。现在设某面值的人民币数量减一:S[j]=S[j]-1,则新的S[i]为n-c[j]的最优解,纸币数K-1. 否则,设T[i]是n-c[j]的最优解,纸币数为m,即m<k-1.那么对于n来说,T[i]+1应该为原问题最少纸币数,即m+1<k-1+1=k,此与k为最少纸币数矛盾,故问题满足最优子结构性质。 贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。在人民币找零问题中,满足贪心选择性质的证明如下:
设纸币面额25,10,5,1元的数量依次为A,B,C,D,则根据贪心算法思想得到的解应依次保证max(A),max(B),max(C),max(D)。假设存在更优的算法,使得所用的纸币数更少,即数量至少小于或等于A+B+C+D-1。那么在纸币总数减少的情况下保证总额不变只能增大相对大面额纸币的数量并减少小面额纸币数量。而由贪心算法知max(A)已经是最大的了,以此类推,max(B),max(C),max(D)均应为最大数量了,所以贪心算法得到的解是最优解,即满足贪心选择性质。
参考链接:http://www.cnblogs.com/youxin/p/3262424.html
假设可换的硬币单位是c的幂,也就是c0,c1, ..., ck, 其中整数c>1, k>=1, 这种情况下贪心算法可以产生最优解。
问题的证明用到了引理:对于i=0,1,..., k, 设ai是找n分钱的最优解中面值ci 的数量。那么对i=0,1,...,k-1,有ai<c.
证明:假设ai>=c,对于0<=i<k. 则可以改进最优解通过增加一个面值ci+1的硬币和减少c个面值ci的硬币,这样找零钱的总数不变,但减少了c-1个硬币。于是,这个比最优解还要优化,矛盾。
然后证明问题具有贪心选择性质,即贪心选择具有最优解。这里证明不用贪心选择不能产生最优解。设j=max{0<=i<=k: ci<=n}.所以贪心解法会使用至少一个面值cj的硬币,考虑非贪心选择的解,不使用面值cj或更高的硬币。设非贪心的解使用ai数量的面值ci的硬币,对i=0,1, ... , j-1. 所以有a0c0+a1c1+ ... +aj-1cj-1=n.
因为n>=cj, 所以上式左边大于等于 cj. 现在假设这种非贪心选择的解是最优的,由引理ai<c,上式左边
a0c0+a1c1+ ... +aj-1cj-1 <= (c-1)c0+(c-1)c1+ ... +(c-1)cj-1 =(c-1)(1+c+...+cj-1)=(c-1)(cj-1)/(c-1)=(cj-1)<cj
与上面推出的左边大于等于cj矛盾。所以可以得到非贪心选择不能产生最优解。
参考链接:http://blog.csdn.net/cattycat/article/details/5813307
// money.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<iostream> using namespace std; int coinValue[] = {0,1,5,10,25 }; //硬币面值 #define kindsOfcoin 4 //硬币种类数 //贪心算法 void findMinByGreed(int money) { int coinNum = 0; //存放硬币总数 int i = kindsOfcoin; int *remeCoin = new int[money + 1](); //存放面值为coinValue[i]的硬币所用的个数 while (money > 0) { remeCoin[i] = money / coinValue[i]; money= money % coinValue[i]; coinNum += remeCoin[i]; i--; } cout << "面值:\n1\t5\t10\t25" << endl; for (int i = 1; i <= kindsOfcoin; i++) cout << remeCoin[i]<<'\t'; cout << "\n所用的硬币总数为:" << coinNum; delete[]remeCoin; } //动态规划版本 void findMIn(int money) { int *coinNum = new int[money + 1](); //存储1...money找零最少需要的硬币的个数 int *remeCoin = new int[money + 1](); //最后加入的硬币,方便后面输出是哪几个硬币 coinNum[0] =remeCoin[0] = 0; for (int i = 1; i <= money; i++) //i面值钱,需要最少硬币个数 { int minNum = i; int useCoin = 0; //这次找零,在原来的基础上需要的硬币 for(int j=1;j<=kindsOfcoin;j++) if(i>=coinValue[j]) //需要判断i - coinValue[j]是否能找的开,如果找不开,就不需要更新。 if ((minNum > coinNum[i - coinValue[j]] + 1) && (i == coinValue[j] || remeCoin[i - coinValue[j]] != 0)) { minNum = coinNum[i - coinValue[j]] + 1; useCoin = coinValue[j]; } coinNum[i] = minNum; remeCoin[i] = useCoin; } if (remeCoin[money] == 0) cout << "找不开零钱" << endl; else { cout << "需要最少的硬币个数为:" << coinNum[money] << endl; cout << "所用到的硬币为:"<<endl; while (money > 0) { cout << remeCoin[money] << '\t'; money -= remeCoin[money]; } cout << endl; } delete[]coinNum; delete[]remeCoin; } int main() { cout << "请输入需要找零的金额" << endl;; int money; cin >> money; findMIn(money); findMinByGreed(money); while (1); return 0; }