邮票问题
设有已知面额的邮票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;
}