问题描述:有n个集装箱要装上2艘载重量分别为C1和C2的轮船,其中集装箱i的重量为wi,且∑wi≦C1+C2。请分别用回溯法跟分支限界法确定是否有一个合理的装在方案可将这些集装箱装上这2艘轮船。
输入:多组测试数据。每组测试数据包括两行:第一行输入集装箱数目n(n<1000),以及两艘轮船的载重C1和C2;第二行输入n个整数,表示每个集装箱的重量。
输出:如果存在合理装载方案,输出第一艘轮船的最大装载重量;否则输出“NO”
(1).回溯法:
#include<stdio.h>
#include<stdlib.h>
#define num 100
int bestx[num] = { 0 }; //存放最优解
int w[num]; //集装箱重量
int x[num]; //解
int bestw = 0; //最优装船重量
int cw = 0; //当前已装船重量
int n; //集装箱个数
int c1; //第一船的重量
int c2; //第二船的重量
//限界函数
int bound(int t) // 选择当前节点又分支的剩余集装箱重之和
{
int rw = 0;
for (int i = t + 1; t < n; t++)
rw = rw + w[i];
return (rw + cw); //上界
}
//递归求解
void loadingRec(int t) {
int i;
if (t > n) //到底叶子节点,求得一个可行解
{
if (cw > bestw) { //当前解比以前解更优
bestw = cw;
for (i = 1; i <= n; i++)
bestx[i] = x[i];
};
return;
}
else {
if (cw + w[t] < c1) //左分支满足约束条件
{
x[t] = 1;
cw = cw + w[t];
loadingRec(t + 1); //前进继续搜索下一节点
//回溯;回复cw与x[t]的值
cw = cw - w[t];
x[t] = 0;
}
if (bound(t) > bestw) //右分支满足限界条件
loadingRec(t + 1);
}
}
int main() {
n = 4; //集装箱个数
printf("集装箱个数:");
scanf("%d", &n);
printf("集装箱重量:");
w[1] = 4, w[2] = 5, w[3] = 3, w[4] = 2; //集装箱重量
for (int i = 1; i <= n; i++) {
scanf("%d", &w[i]);
}
c1 = 8; //第一个船重量
c2 = 7; //第二个船重量
printf("第一个船重量:");
scanf("%d", &c1);
printf("第二个船重量:");
scanf("%d", &c2);
cw = 0;
bestw = 0;
loadingRec(1); //从第一个集装箱开始装箱
printf("第一船的最优装载量为:%d\n", bestw);
printf("第一船的最优解为");
for (int i = 1; i <= n; i++)
printf("%d ", bestx[i]);
//求剩余集装箱的重量
int cw2 = 0;
for (int i = 0; i <= n; i++)
if (bestx[i] == 0)
cw2 += w[i];
if (cw2 > c2)
printf("无法将剩余集装箱转入第二船,问题无解");
else
printf("可以将剩余集装箱装入第二船,问题有解");
getchar();
}
(2).分支限界法
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<queue>
#define num 100
float w[num]; //集装箱重量
int x[num]; //解
float c;
int n; //集装箱个数
float bestw;
using namespace std;
template<class Type>
class HeapNode;
template<class Type>
class bbnode;
template<class Type>
void AddLiveNode(priority_queue<HeapNode<Type> >& H, bbnode<Type>* E, Type wt, bool ch, int lev);
template<class Type>
Type MaxLoading(Type w[], Type c, int n, int bestx[]);
template<class Type>
class bbnode
{
friend void AddLiveNode<Type>(priority_queue<HeapNode<Type> >& H, bbnode<Type>* E, Type wt, bool ch, int lev);
friend Type MaxLoading<Type>(Type*, Type, int, int*);
friend class AdjacencyGraph;//邻接矩阵
public:
bbnode<Type>* parent;//指向父节点的指针
bool Lchild;//左儿子结点标志
};
template<class Type>
class HeapNode
{
friend void AddLiveNode<Type>(priority_queue<HeapNode<Type> >& H, bbnode<Type>* E, Type wt, bool ch, int lev);
friend Type MaxLoading<Type>(Type*, Type, int, int*);
public:
operator Type () const { return uweight; }
public:
bbnode<Type>* ptr;//指向活结点在子集树中相应结点的指针
Type uweight;//活结点优先级(上界)
int level;//活结点在子集树中所处的层序号
};
//将活结点加入到表示活结点优先队列的最大堆H中
template<class Type>
void AddLiveNode(priority_queue<HeapNode<Type> >& H, bbnode<Type>* E, Type wt, bool ch, int lev)
{
bbnode<Type>* b = new bbnode<Type>;
b->parent = E;
b->Lchild = ch;
HeapNode<Type>N;
N.ptr = b;
N.uweight = wt;
N.level = lev;
H.push(N);
}
//优先队列式分支限界法,返回最优载重量,bestx返回最优解
//优先级是当前载重量+剩余重量
template<class Type>
Type MaxLoading(Type w[], Type c, int n, int bestx[])
{
priority_queue<HeapNode<Type> > H;//定义最大堆的容量为1000
Type* r = new Type[n + 1];//剩余重量
r[n] = 0;
for (int j = n - 1; j > 0; j--)
{
r[j] = r[j + 1] + w[j + 1];
}
//初始化
int i = 1;//当前扩展结点所在的层
bbnode <Type>* E = 0;//当前扩展结点
Type Ew = 0;//扩展结点所相应的载重量
//搜索子集空间树
while (i != n + 1)//非叶子节点
{
//检查当前扩展结点的儿子节点
if (Ew + w[i] <= c)
{
AddLiveNode(H, E, Ew + w[i] + r[i], true, i + 1);//左儿子结点为可行结点
}
AddLiveNode(H, E, Ew + r[i], false, i + 1);//右儿子结点
HeapNode<Type>N = H.top();//取下一扩展结点
H.pop();//下一扩展结点,将最大值删去
i = N.level;
E = N.ptr;
Ew = N.uweight - r[i - 1];//当前优先级为上一优先级-上一结点重量
}
//构造当前最优解,类似回溯的过程
for (int j = n; j > 0; j--)
{
bestx[j] = E->Lchild;
E = E->parent;
}
return Ew;
}
int main() {
printf("轮船重量:");
scanf("%f", &c);
printf("请输入物品个数:");
scanf("%d", &n);
printf("物品的重量:");
for (int i = 1; i <= n; i++) {
scanf("%f", &w[i]);
}
bestw = MaxLoading<float>(w, c, n, x);
cout << "分支限界选择结果为:" << endl;
for (int i = 1; i <= n; i++) {
cout << x[i] << " ";
}
cout << endl;
cout << "最优装载重量为:" << bestw << endl;
return 0;
}
程序测试及运行结果:
(1).回溯法
问题有解
问题无解
(2).分支限界法
分析与讨论:
对于该问题可以拆分成两部分来看,第一部分是轮船一的最大载重问题,相当于背包问题。第二部分就是判断轮船一装剩下的是否可以装载的问题,问题需要通过判断节点i(集装箱i)的装与不装形成一个二叉树,一条到叶子节点的通路就是一种方案。再利用一变量存储此时重量,以此来记录最优的装载方案。
伪代码:
①用i记录第几个集装箱,判断该集装箱是否装入
②如果剩余载重量大于该集装箱的重量,先不装入,否则装入。
③再判断如果先不放该集装箱,再放剩下的集装箱是否可以达成最优装载。
④利用回溯法,逐一判断集装箱,直到结束。