邮票问题 回溯法

邮票问题

设有已知面额的邮票m种,每种有n张,问用总数不超过n张的邮票进行组合,能组合的邮票面额中可以连续的面额数最大到多少?
例如:n=4 m=3
v1=1 v2=2 v3=4
最后的解为14。
请设计回溯算法求解以上问题,分析算法的时间复杂度;编码实现,要求结果正确。

问题分析

对给出的不同面值的邮票进行组合,且面值需组合成连续的数,就要求面值的总和从1开始,增量为1,一直增加到不连续为止。取一个结构体,c.value表示每个面值,c.choice表示面值的标号。这个问题的解向量为数组c.value,解向量的取值范围为不同的邮票面值,约束条件为邮票面值的总和需要是连续的。为了保证求得的总面额是连续的,需要在两个while循环之外再加一个for循环。for循环是为了保证求得的总面额数是连续的,每次for循环得到的数与上一个for循环得到的数作比较,若相等,此时得到的解为部分解,继续循环;若不等,此时的得到的解为全部解,继续循环得到的解释不再符合约束条件,则跳出循环,此时得到的面额总值就是所求。

算法设计

1.for k←1 to n k表示选了几张邮票
2.c[k].value←0; 解空间
3.c[k].choice←0; 表示有几种选择 即种类数 从1到m
4.end for
5.flag←false
6.for 当总面值不连续时退出循环
7.while k>=1 (k表示选择了几张)
8.while c[k].choice<m (每一种邮票面值都要选择)
9.c[k].choice←c[k].choice+1
10.j=c[k].choice
11.c[k].value←q[j]
12.tem←add(c,n) 将c中面值相加
13.if 如果面值数继续相等并且没到出口then flag←true并且跳出两个while循环
14.else if如果面值数不变并且没到出口 then k←k+1
15.end if
16.end while
17.c[k].choice←0
18.c[k].value←0
19.k←k-1{回溯}
20.end while
21.if flag then flag←false
22.for j←1 to k
23.c[j].value←0
24.c[j].choice←0
25.end for
26.k←1
27.end if
28.end for
29.return i-2

算法分析

在最坏的情况下会生成(n*((n(m+1))-1))/(n-1)个结点,对于每个生成的节点,都需要O(n)的工作来检查是否是合法解,或是部分解,或是非法解。因此在最坏时间下算法的全部运行时间为O(n(m+1))。空间复杂度为O(n^m)。

代码

#include <iostream>

using namespace std;

typedef struct stamp
{
    int value;//解空间 不同的邮票面额
    int choice;//表示有几种选择 即种类数 从1到m
}stamp;
int add(stamp c[],int n)//计算现有邮票总面值
{
    int tem=0;
    int i;
    for(i=1;i<=n;i++)
    {
        tem+=c[i].value;
    }
    return tem;
}
int maxvalue(int q[],int m,int n)
{
    stamp c[n+1];
    int k,j;
    bool flag;
    for(k=1;k<=n;k++)//k表示选了几张邮票
    {
        c[k].value=0;
        c[k].choice=0;
    }
    k=1;
    int i=1;
    flag=false;
    int tem=0;
    for(i = 1;tem==i-1;i++)//tem 表示总面值数 当不连续时退出循环
    {
        while(k>=1)//k表示选择了几张
        {
            while(c[k].choice<m)//每一种邮票面值都要选择
            {
                c[k].choice=c[k].choice+1;
                j=c[k].choice;
                c[k].value=q[j];
                tem=add(c,n);
                    //cout<<endl<<c[1].value<<' '<<c[2].value<<' '<<c[3].value<<' '<<c[4].value<<' ';注释部分帮助读者理解程序运行过程 输出结果为:第一张邮票取值 第二张邮票取值 第三张邮票取值 第四张邮票取值 现总面值 下一步要选择的运行路径
                    //if(tem == i)cout<<tem<<' '<<"goto Lab"<<endl;
                    //else cout<<tem<<' '<<"k++"<<endl;
                if(tem==i&&k<=n)//如果面值数继续相等
                {
                    flag=true;
                    goto Lab;
                }
                else if(tem<i&&k<n)//如果面值数不变
                {
                    k=k+1;
                }
            }
            c[k].choice=0;//回溯
            c[k].value=0;
            k=k-1;
        }
        Lab:
                if(flag)
                {
                    flag=false;
                    for(j=1;j<=k;j++)
                    {
                        c[j].value=0;
                        c[j].choice=0;
                    }
                    k=1;
                }
        }
        return i-2;
}
int main()
{
    int m,n,i;//m 种类 n 数量
    int MAX;
    cout<<"邮票种类数:";
    cin>>m;
    cout<<"每种张数:";
    cin>>n;
    int q[m+1];
    cout<<"由小到大输入各个面值:";
    for(i=1;i<=m;i++)
    {
        cin>>q[i];
    }
    MAX=maxvalue(q,m,n);
    cout<<"结果:"<<MAX;
    return 0;
}

运行结果

在这里插入图片描述

运行过程

在这里插入图片描述

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值