多少个硬币加起来等于1美元?
环境:VC++ 6.0 , Win9x, WinNT
在这篇文章里,我介绍了一个解决著名的“多少个硬币组成一美元”问题的可选择方案。这种方法采用的动态规划是一个有名用来解决状态不定问题的方法。典型的动态规划问题包括费波纳契数列和阶乘,这些都把递归作为求解的第一选择。
问题陈述
找出所有的用1美分,5美分,25美分和50美分组成一美元的组合。这个问题的一个流行的版本也包括10美分。这篇文章设法强调有关的算法:动态规划的作用。在文章的结束,很容易明白这种方法能扩展到许多硬币组成一个希望和。
用数学的术语描述:你有N种面值V1, V2, ..., VN的硬币,你要用其中的一种或多种硬币组成和S,每种硬币可以一个或多个。
手工方法
就这个一元问题来说,并非不能用手工的方法求出解。用纸和笔,你可以像这样开始:
1、 有多少种方式只用1美分或5美分或25美分或50美分组成一美元:你需要100个1美分,20个5美分,4个25美分,2个50美分。
2、 有多少种方式用1美分和5美分组成一美元:你需要计算1个5美分时要多少个1美分,2个5美分需要多少个1美分···直到计算19个5美分。
3、 类似的,你还要计算5美分和25美分,25美分和50美分,1美分和25美分,1美分和 50美分。
4、 求用3种硬币组成1美元的方案的数量。从上面的步骤2和步骤3,你知道所有的用两种硬币组成一美元的方案。举个例子,把所有的用1美分和5美分组成1美元的方案放在一边。现在,用他们组成75美分来代替1美元。就像你猜的一样,只需要加上25美分,你就能得到一种方案。让他们组成50美分,在加上2个25美分,你就得到了第二种方案,以此类推。
为了便于理解,看下面的计算:
1 | 5 | 95 | 1 |
|
|
| = 100 |
2 | 5 | 90 | 1 |
|
|
| = 100 |
... ... | ... ... | ... ... | ... ... | ... ... | ... ... | ... ... | ... ... |
19 | 5 | 5 | 1 |
|
|
| = 100 |
... ... | ... ... | ... ... | ... ... | ... ... | ... ... | ... ... | ... ... |
1 | 5 | 14 | 5 | = 75 | + 1 | 25 | = 100 |
1 | 5 | 9 | 5 | = 50 | + 2 | 25 | = 100 |
一旦你得到了三种硬币组成一个和的所有方案,你不难得到四种硬币的。只需要见到的重复上面的步骤4。
写完这些,你开始感到疲倦了,但是在仔细和重复的努力后,你能得到正确的解,但是我推荐这种方法。因为,一旦增加一种硬币VN+1 ,就会使问题变成一个庞大的任务。在这点上动态规划能正确的解决这类问题。
动态规划方法:一个公式?
动态规划试着识别状态和改变他们的条件。你需要的一切是一个包含n和n-1的函数(或者根据问题性质,最接近的关系)。
一个有名的动态规划的例子是阶乘,通过递归逐一调用你得到的解。
如果是阶乘,他的函数是:
F (n) = n*f (n - 1) | if n>1 |
= 1 | If n = 1 |
试着找出与你的问题的相同点。硬币问题要求你从V1, V2, ..., VN 面值的硬币中凑出S。你可以像这样定义它:
S = aV1 + bV2 + cV3 + ... + kVN
你需要求出a,b,c,…,k值,把他们与V1, V2, ..., VN 组合在一起,就是所求的组合方案。
状态的识别和转变
因为你打算用动态规划按部就班的执行,先前手工的方法给了你一点暗示,试着像前面那样计算。你总能得到用两种硬币组成一个值的方案。算法类似这样:
For i = 1 to Sum / V1
For j = 1 to Sum / V2
total = i* V1 + j* V2;
If (total == Sum)
return i V1 j V2;
else
continue;
上面是蛮力计算法的本质,你最后的解法不是这个,你马上就会知道。在你的代码里,你会优化算法,为了减少循环,分别把上面的循环改为(Sum - V2) / V1 和 (Sum - V1) / V2 。这是因为当你计划和S有V1, ,V2两部分组成,它们必须有一个。
正确,优化后的算法是:
For i = 1 to (Sum - V2 ) / V1
For j = 1 to (Sum - V1 ) / V2
total = i* V1 + j* V2;
If (total == Sum)
return i V1 j V2;
else
continue;
你有了有效的第一步:获得所有的用两种硬币组成一个值S的组合。在代码里,函数Comb2()就相当于这个功能。这个函数有两个int型参数a和b,并且求出所有的使a,b加起来等于S的组合。我选择vector<map<int>>作为返回值,和因为我预计有大量的组合,每个组合都有两个(或更多)的面值(keys of map)V1, V2, 和它们需求的数量(values of the map)
Comb2()函数在a=5,b=15,S=100条件吓的一部分输出为:
Output Vector Vect: contains Vect1, Vect2....VectN
Vect1: 5 : 5 25 : 3 --- 5 coins of 5 cents, 3 coins of 25 cents.
Vect2: 5 : 10 2 25 --- 10 coins of 5 cents, 2 coins of 25 cents.
注意:在实际输出中顺序可能不一样。这里只是为了说明问题,这只是显示了矢量的2个元素
现在,在看三种硬币组成100的组合;每种组合都必须包含这三种硬币。用1美分,5美分和25美分举个例子。你已经有了一种方法得到用1美分和5美分组合的所有和。另一种硬币依旧是25美分。你的目标和是100.
所以,把你的目标和改为100-25=75.试着得到所有用1美分和5美分组成75美分的组合。Comb2()会以向量的方式给你解。一旦你得到这个,你只需为每个组合加上25美分就能的到100美分的组合。
接着,把目标和改为100-25*2=50.用Comb2()得到解后,为每个组合加上2个25美分。
这里只有一个25 的空间,所以,将目标和改为100-25*3=25,用Comb2()得到解后,为每个组合加上3个25美分。
正像你上面那样排除了25美分,你值需要重复上述步骤,排除5美分,1美分,就能得到所有的组合。
你得到了所有组合后,你可以用5,25,50;1,25,50;1,5,50重复上述步骤。
这就是函数Comb3()的功能。它有三个int类型的参数a,b,c,求出所a,b,c和为值S的组合。就像Comb2()那样,返回映射的矢量,映射的关键词是硬币的值V1, V2, V3... 映射的值为各个硬币所需数量a.b.c…
这样就能衍生出更高的层次。Comb4()就是用Comb3()生成的组合用上面的方法,求出用四种硬币组成定值的组合。这个可以泛型化的使用递归,但是对这伤脑筋的问题,我限制自己求到4种硬币的组合。
作为一种动态规划方法的对比,我在代码种加入了蛮力计算法(BruteForceTest())。
在我的测试中,BruteForceTest()在xp和windows 2003 Server上大概需要15+ms。而动态规划几乎是0 ms。重复运行BruteForceTest()使运行时间减少到0ms,但是这要归功与二进制代码写入高速缓存。在硬币种类很多的情况下,动态规划方法肯定可以增加实际履行的好处。
- // onedollar.cpp : Defines the entry point for the console application.
- #define DO_LOG //comment this out to turn off result log
- #include <ctime>
- #include <iostream>
- #include<fstream>
- #include <iomanip>
- #include <sstream>
- #include <string>
- #include <vector>
- #include <map>
- #include <deque>
- #include <set>
- #include <map>
- #include <bitset>
- #include <valarray>
- #include <algorithm>
- #include <functional>
- #include <numeric>
- #include <complex>
- #include <utility>
- #include <cstdio>
- #include <cstdlib>
- #include <cstring>
- #include <cctype>
- #include <cmath>
- #include <queue>
- #include <iosfwd>
- #include <conio.h>
- using namespace std;
- int a[] = {1,5,25,50};
- void printvect(vector <map<int, int> > &vect)
- {
- for (int i=0; i<vect.size(); i++)
- {
- map<int, int>::const_iterator iter;
- for(iter = vect[i].begin(); iter != vect[i].end(); iter++)
- cout << (*iter).first << " : " << (*iter).second <<" ";
- cout<<endl;
- }
- cout<<"/r/nTotal No of combinations:"<<vect.size()<<endl;
- }
- void logvect(vector <map<int, int> > &vect, const char * filename)
- {
- ofstream outfile(filename, ios_base::app);
- for (int i=0; i<vect.size(); i++)
- {
- map<int, int>::const_iterator iter;
- for(iter = vect[i].begin(); iter != vect[i].end(); iter++)
- outfile<<(*iter).first << " : " << (*iter).second <<" ";
- outfile<<"/r/n"<<endl;
- }
- outfile<<"/r/nTotal No of combinations:"<<vect.size()<<endl;
- outfile.close ();
- }
- void logtext(const char * message, const char * filename)
- {
- ofstream outfile(filename);
- outfile<<message<<endl;
- outfile.close ();
- }
- void appendvect(vector <map<int, int> > &dest, vector <map<int, int> > &src)
- {
- for (int i=0; i<src.size(); i++)
- dest.push_back (src[i]);
- }
- vector<map<int, int> > BruteForceTest(int a, int b, int c, int d, int S)
- {
- vector<map<int, int> > retVect;
- int w, x, y, z;
- int count = 0;
- for (w=0; w<=S/c; w++)
- {
- for (x=0; x<=S/c; x++)
- {
- for (y=0; y<=S/b; y++)
- {
- for (z=0; z<=S/a; z++)
- {
- int val= w*d + x*c + y*b + z*a;
- if (val == 100)
- {
- map<int, int> temp;
- temp[a] = z;
- temp[b] = y;
- temp[c] = x;
- temp[d] = w;
- retVect.push_back(temp);
- }
- }
- }
- }
- }
- return retVect;
- }
- vector<map<int, int> > Comb1(int a, int S)
- {
- vector<map<int, int> > retVect;
- map<int, int> temp;
- temp[a] = (int)S/a;
- retVect.push_back (temp);
- return retVect;
- }
- vector<map<int, int> > Comb2(int a, int b, int S)
- {
- int divisorA = (S-b)/a; //for 5, 25 and Sum = 100, we take at least 1 25 and rest of 5s...
- int divisorB = (S-a)/b;
- vector<map<int, int> > retVect;
- for (int i=1; i<= divisorA; i++)
- {
- for (int j=1; j<= divisorB; j++)
- {
- int val = a*i + b*j;
- if (val == S)
- {
- map<int, int> temp;
- temp[a] = i;
- temp[b] = j;
- retVect.push_back (temp);
- break;
- }
- }
- }
- return retVect;
- }
- vector<map<int, int> > Comb3(int a, int b, int c, int S)
- {
- vector<map<int, int> > retVect;
- int count=0;
- for (count=1; count<(S/c); count++)
- {
- vector<map<int, int> > temp1 = Comb2(a, b, S-(count*c));
- //for temp1, append c count
- for (int i=0; i<temp1.size(); i++)
- temp1[i] [c] = count;
- appendvect(retVect, temp1);
- }
- for (count=1; count<(S/c); count++)
- {
- vector<map<int, int> > temp2 = Comb2(b, c, S-(count*a));
- //for temp2, append a count
- for (int i=0; i<temp2.size(); i++)
- temp2[i][a] = count;
- appendvect(retVect, temp2);
- }
- for (count=1; count<(S/c); count++)
- {
- vector<map<int, int> > temp3 = Comb2(c, a, S-(count*b));
- //for temp3, append b count
- for (int i=0; i<temp3.size(); i++)
- temp3[i][b] = count;
- appendvect(retVect, temp3);
- }
- //now remove duplicates from the resultant vector.
- std::sort(retVect.begin(), retVect.end());
- retVect.erase(std::unique(retVect.begin(), retVect.end()), retVect.end());
- return retVect;
- }
- vector<map<int, int> > Comb4(int a, int b, int c, int d, int S)
- {
- vector<map<int, int> > retVect;
- int count=0;
- for (count=1; count<(S/d); count++)
- {
- vector<map<int, int> > temp1 = Comb3(a, b, c, S-(count*d));
- //for temp1, append d count
- for (int i=0; i<temp1.size(); i++)
- temp1[i] [d] = count;
- appendvect(retVect, temp1);
- }
- for (count=1; count<(S/a); count++)
- {
- vector<map<int, int> > temp2 = Comb3(b, c, d, S-(count*a));
- //for temp2, append a count
- for (int i=0; i<temp2.size(); i++)
- temp2[i] [a] = count;
- appendvect(retVect, temp2);
- }
- for (count=1; count<(S/b); count++)
- {
- vector<map<int, int> > temp3 = Comb3(c, d, a, S-(count*b));
- //for temp3, append b count
- for (int i=0; i<temp3.size(); i++)
- temp3[i] [b] = count;
- appendvect(retVect, temp3);
- }
- for (count=1; count<(S/c); count++)
- {
- vector<map<int, int> > temp4 = Comb3(d, a, b, S-(count*c));
- //for temp4, append c count
- for (int i=0; i<temp4.size(); i++)
- temp4[i] [c] = count;
- appendvect(retVect, temp4);
- }
- //now remove duplicates from the resultant vector.
- std::sort(retVect.begin(), retVect.end());
- retVect.erase(std::unique(retVect.begin(), retVect.end()), retVect.end());
- return retVect;
- }
- int main()
- {
- clock_t start,finish;
- double time;
- start = clock();
- vector <map<int, int> > v1 = Comb4(1, 5, 25, 50, 100);
- vector <map<int, int> > v2 = Comb3(1, 5, 25, 100);
- vector <map<int, int> > v3 = Comb3(5, 25, 50, 100);
- vector <map<int, int> > v4 = Comb3(25, 50, 1, 100);
- vector <map<int, int> > v5 = Comb3(50, 1, 5, 100);
- vector <map<int, int> > v6 = Comb2(1, 5, 100);
- vector <map<int, int> > v7 = Comb2(1, 25, 100);
- vector <map<int, int> > v8 = Comb2(1, 50, 100);
- vector <map<int, int> > v9 = Comb2(5, 25, 100);
- vector <map<int, int> > v10 = Comb2(5, 50, 100);
- vector <map<int, int> > v11 = Comb2(25, 50, 100);
- vector <map<int, int> > v12 = Comb1(1, 100);
- vector <map<int, int> > v13 = Comb1(5, 100);
- vector <map<int, int> > v14 = Comb1(25, 100);
- vector <map<int, int> > v15 = Comb1(50, 100);
- vector <map<int, int> > v;
- appendvect(v, v1);
- appendvect(v, v2);
- appendvect(v, v3);
- appendvect(v, v4);
- appendvect(v, v5);
- appendvect(v, v6);
- appendvect(v, v7);
- appendvect(v, v8);
- appendvect(v, v9);
- appendvect(v, v10);
- appendvect(v, v11);
- appendvect(v, v12);
- appendvect(v, v13);
- appendvect(v, v14);
- appendvect(v, v15);
- finish = clock();
- time = (double(finish)-double(start));
- cout<<"Through Combination algorithm, the result is: /r/n"<<endl;
- cout<<"Total Time taken: "<<time<<" ms. Press any key to view the result.../r/n"<<endl;
- getch();
- printvect (v);
- #ifdef DO_LOG
- logtext("By Combination Algorithm, the result is:/r/n", "logresult.txt");
- logvect(v, "logresult.txt");
- #endif
- //Applying Brute Force Approach to verify the result and compare the time.
- cout<<"Press B or b to try BFS.../r/n"<<endl;
- int c = getch();
- if ('b' != c && 'B' != c)
- return 0;
- cout<<"By Brute Force technique, the result is: /r/n"<<endl;
- vector <map<int, int> > vBruteForce;
- start = finish = 0;
- start = clock();
- vBruteForce = BruteForceTest(1, 5, 25, 50, 100);
- finish = clock();
- time = (double(finish)-double(start));
- cout<<"Total Time taken for BFS: "<<time<<" ms. Press any key to view the result.../r/n"<<endl;
- getch();
- printvect(vBruteForce);
- cout<<"/r/nTotal No of combinations through BFS:"<<vBruteForce.size()<<"/r/n"<<endl;
- cout<<"Press any key to end..."<<endl;
- #ifdef DO_LOG
- logtext("By Brute Force technique, the result is:/r/n", "BFSLogresult.txt");
- logvect(vBruteForce, "BFSLogresult.txt");
- #endif
- getch();
- return 0;
- }
原文地址:http://www.codeguru.com/cpp/cpp/algorithms/combinations/article.php/c15409/