1、实验环境
VisualC++ 6.0
2、 实验目的和要求
l 实验目标:利用回溯法求解连续邮资问题。
l 实验内容:
Ø 假设国家发行了n种不同面值的邮票,并且规定每张信封上最多只允许贴m张邮票。
l 实验要求:
Ø 连续邮资问题要求对于给定的n和m的值,给出邮票面值的最佳设计,在1张信封上可贴出从邮资1开始,增量为1的最大连续邮资区间。
l 当n=2、m=3时:
Ø 如果面值分别为1、4,则在1-6之间的每一个邮资都能得到(当然还有8、9和12);
Ø 如果面值分别为1、3,则在1-7之间的每一个邮资都能得到;
l 当n=5、m=4时:
Ø 面值为(1,3,11,15,32)的五种邮票可以贴出邮资的最大邮资区间是1到70.
3、解题思路、伪代码
3.1解题思路
(1).先从简单入手,若前cur张邮票面值已经确定,如何判断由这些面值,在张数不超过m的情况下能否贴出该邮资re。很容易想到,这是一个类似背包问题,可采用用回溯法求解。
checkanser(temp,cur,re,m,num)
其中:
temp数组:记录已经确定下来的面值
cur:当前已经确定的面值数量
re:当前需要贴出的邮资
m:信封最多允许贴m张邮票
num:已经张贴的邮票数量
1向信封上贴第一张邮票,可以有cur种选择,当选择了第i张面值时,问题缩小为:
前cur张邮票面值已经确定,如何判断由这些面值,在张数不超过m的情况下能否贴出该邮资re-temp[i].
即:checkanser(temp,cur,re-temp[j],m,num+1)
2当re=0时,算法结束,表示由已确定下来的邮票,能够在满足条件的情况下,贴出邮资为re.
3当re=1时,表示当前贴法无法贴出邮资re,则选择贴下一张邮票,若所有的邮票都不满足要求,向前回溯。
4约束条件:已经张贴的邮票数量num不得超过m
(2).判断由已选的面值,在张数不超过m的情况下能贴出的最大邮资
即从小能到大依次连续枚举邮资,调用(1)判断该邮资能否取得,从而得出最大连续邮资。
(3).已经选取到第cur种面值邮票,确定下一张面值
若前cur张面值为temp[cur],最大能贴出的最大连续邮资为max,显然下一张面值的可能取值只可能在temp[cur]+1到
max+1之中。由于前面的邮票的面值会影响后面面值能,如n=3,m=4时,最优面值为(1,5,8);n=4,m=4时,最优面值为:(1,3,11,18),因此,该问题不具有无后向性,不是一个贪心问题。于是采用dfs搜索算法进行求解,dfs树的每一个叶子代表可能的面值,每一条从根到叶子的路径表示一组满足条件的解。最后从所有满足条件的解中选出最优解。
代码
#include <stdio.h>
#include <string.h>
int n;//n种面值
int m;//最多允许贴m张
int result[100];//最终结果
int temp[100];//暂存各种可能结果
int maxRe=0;//最大邮资
int checkanser(int temp[],int
cur,int re,int m,int num);
void getNext(int temp[],int
cur,int max,int m);
int maxl(int temp[],int
cur,int m,int max);
//判断由已选的面值,在张数不超过m的情况下能否贴出该邮资re
//回溯法判断
int checkanser(int temp[],int
cur,int re,int m,int num)//cur:当前已经确定的面值数量num:已经张贴的邮票数量
{
if(re==0)
return 1;
if(re<0)
return 0;
for(int j=cur;j>=1;j--)
{
if(num<m)
{
num++;
if(checkanser(temp,cur,re-temp[j],m,num)==1)//使用了第i个面值的邮票
{
return 1;
}else
{
num--;//恢复现场
}
}
}
return 0;
}
//判断由已选的面值,在张数不超过m的情况下能贴出的最大邮资
int maxl(int temp[],int
cur,int m,int max)
{
for(int k=max+1;k<m*temp[cur];k++)
{
if(checkanser(temp,cur,k,m,0)==0)
break;
}
return k-1;
}
void getNext(int temp[],int
cur,int max,int m)//cur:已经选取到第cur种邮票,确定下一张面值
{
if(cur==n)//遍历到叶子
{
if(maxRe<max)
{
maxRe=max;
for(int z=1;z<=n;z++)
{
result[z]=temp[z];
}
}
return;
}
for(int k=temp[cur]+1;k<=max+1;k++)
{
temp[cur+1]=k;
int max1=maxl(temp,cur+1,m,max);//下一个面值取k时能达到的最大邮资
if(max1>max)//k可取
{
getNext(temp,cur+1,max1,m);//继续递归确定接下来的面值
}
}
}
int main()
{
printf("请输入面值数量(n):\n");
scanf("%d",&n);
printf("请输入每张信封最多允许贴的邮票数量(m):\n");
scanf("%d",&m);
temp[1]=1;
getNext(temp,1,m,m);
printf("\n邮票面值分别为:");
for(int i=1;i<=n;i++)
{
printf("%d ",result[i]);
}
printf("\n最大邮资区间为:1-%d\n",maxRe);
return 0;
}
4、实验步骤
4.1输入
4.2输出:
5、讨论和分析
对算法及实验结果进行分析讨论。
实验结果分析:
经验证当输入n=5,m=4情况下
最大邮资区间为1-70
验证成立。
总结:
○1当遇到算法问题时,要先判断它属于什么类型的问题。由于前面的邮票的面值会影响后面面值能,如n=3,m=4时,最优面值为(1,5,8);n=4,m=4时,最优面值为:(1,3,11,18),因此,该问题不具有无后向性,不是一个贪心问题。
○2使用回溯法时要注意恢复现场,确定边界条件和回溯的条件,才能通过回溯得到正确的结果。