问题引入:超市里或商店里都存在着找零的问题,比如你有四张10块,两张20块,一张50块,要找零90,当然可以一张50加两张20,也可以一张50加四张10,前者消耗3张,后者消耗5张,在这篇文章里,我们就想解决如何算出最少张的找零方法。
当然最初的想法大概都是从最大张开始找,就拿上面那个举例,50里挑出一张,还剩40,再从20里挑出两张,就正好等于90.。大致算法如下代码 其中数组a[n]表示面额,b[n]表示其对应张数,n就表示面额种类数,N表示找零数。
i=b[n-1]
while(i>=1){
//从大面值循环
if(N>=(a[n-1]*i)) {
c[n-1]=a[n-1]; //保存面值
d[n-1]=i; //保存张数
N=N-a[n-1]*i;
n--; //为下一步循环准备
i=b[n-1]+1; //换小面值
if(N==0) {
i=0; //得到结果停止循环
}
}
//某项不符,进行下一次循环
m=(N<a[n-1]*i);
if(m==1&&i==1) {
n--;
i=b[n-1]+1;
for(i=0; i<=n-1; i++) {
c[i]=0;
d[i]=0;
}
}
i--;
}
}
当然我们很容易发现这种算法有很大的弊端,假设你有十张1块,十张30块,十张50块,找零90,你会发现用上面的算法会得出一张50加一张30加十张1块的非最优解,因为我们很容易发现三张30显然是消耗最少张数的。所以我决定改变算法,从寻找局部最优解转到全局最优解。
要找全局最优解,那必然避免不了遍历所有情况,但是要如何遍历呢?运用一下高中求概率时找所有情况的方法,比如上述问题我们可以这样
1*10+30*10+50*10 | 1*10+30*9+50*10 | ...... | 1*0+30*0+50*10 |
1*10+30*10+50*9 | 1*10+30*9+50*9 | ...... | 1*0+30*0+50*9 |
............................ | .......................... | ...... | .......................... |
1*10+30*10+50*0 | 1*10+30*9+50*0 | ...... | 1*0+30*0+50*0 |
也就是先把前面的全部定住,然后开始循环最后一种面额的张数,一直减一,等到其到零,下一次又重新开始循环,而且其前一种面额的张数减一,也就是当后一种面额的张数减到零,前一种面额就减一,其形成一种“递推关系”。我写的代码如下。
while(1){
if(b[0]==-1) break;
b[5]--;
for(j=5;j>=1;j--){
if(b[j]==-1){
b[j-1]--;
b[j]=m[j];
}
}
}
我这里为了避免运算太多,将钞票面额限制在6种 ,数组b[]的意义和上面说的一样。数组m[]就是存储数组b[]的初始值。你可能很疑惑为什么条件判断是b[]==-1,而不是==1。仔细理清这个逻辑,你会发现如果设成==1的话上面那种(1*10+30*10+50*0)的情况就不会出现而是变成(1*10+30*9+50*0)所以设置成-1。至于我为什么叫他”时钟模型“,你可以试着运行下列代码
#include<stdio.h>
int main()
{
int i,j,m[6];
int a[6]={1,5,10,20,50,100};
int b[6]={10,10,10,10,10,10};
for(i=0;i<6;i++){
m[i]=b[i];
}
while(1){
if(b[0]==-1) break;
b[5]--;
for(j=5;j>=1;j--){
if(b[j]==-1){
b[j-1]--;
b[j]=m[j];
}
}
for(i=0;i<6;i++){
printf("%d*%d ",a[i],b[i]);
}
printf("\n");
}
return 0;
}
最后呢给出整个代码以及我的运行结果
#include<stdio.h>
int main()
{
int N,i,j,sum1=0,sum2=0,min=10000;
scanf("%d",&N);
int a[6],b[6],m[6],r[6];
for(i=0;i<6;i++){
scanf("%d*%d;",&a[i],&b[i]);
}
for(i=0;i<6;i++){
m[i]=b[i];
}
while(1){
if(b[0]==-1) break;
b[5]--;
for(j=5;j>=1;j--){
if(b[j]==-1){
b[j-1]--;
b[j]=m[j];
}
}
for(i=0;i<6;i++){
sum1+=(a[i]*b[i]);//总金额
sum2+=b[i];//张数
}
//求出最少张数
if(sum1==N&&sum2<min){
min=sum2;
for(i=0;i<6;i++){
r[i]=b[i];
}
}
sum1=0;sum2=0;
}
printf("%d\n",min);
for(i=0;i<6;i++){
if(r[i]>0) printf("%d*%d\n",a[i],r[i]);
}
return 0;
}
以上就是我所分享的一种遍历算法的全部内容了,希望对你有所帮助。