找零问题的一种遍历算法——“时钟模型”

        问题引入:超市里或商店里都存在着找零的问题,比如你有四张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*101*10+30*9+50*10......1*0+30*0+50*10
1*10+30*10+50*91*10+30*9+50*9......1*0+30*0+50*9
......................................................................................
1*10+30*10+50*01*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;
}

         以上就是我所分享的一种遍历算法的全部内容了,希望对你有所帮助。

  • 15
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值