题目描述
题目分析
这个问题其实是一个NP难问题,当w1+w2+…wn=c1+c2的时候,装载问题等价于子集和问题。当c1=c2的时候,等价于划分问题.这两个问题都是NP难的问题,所以我们装载问题也是NP难的问题。关于这篇文章的先前相关知识请见回溯法框架
如果一个给定的装载问题有解,则我们采用的策略应该是:先将第一艘轮船尽可能装满,然后将剩余的集装箱上第二艘轮船。将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近c1。由此可知,装载问题等价于特殊的0-1背包问题。
算法设计
这个题目我们可以用我们上次说过的子集树来解决,在某一层,如果此时的装载容量大于了c1,我们就应该将这个子树进行剪枝。我们使用Backtrack(i)搜索子集树中第i层子树。类Loading的数据成员记录子集树中结点信息,以减少传给Backtrack的参数。cw记录当前结点所相应的装载重量,bestw记录当前最大装载重量。
在Backtrack中,当i>n时,算法搜索至叶节点,其相应的装载重量为cw。如果cw>bestw,则表示当前解优于当前最优解,此时应该更新bestw。
当i<=n时,当前扩展结点Z是子集树中的内部结点。该结点有x[i]=1和x[i]=0两个儿子结点。其左儿子结点表示x[i]=1的情形,仅当cw+w[i]<=c时才进入左子树,对左子树递归搜索。其右儿子结点表示x[i]=0的情形,因此进入右子树不需要再判断了。
优化
为了构造最优解,必须在算法中记录与当前最优值相应的当前最优解。为此,在类Loading中增加两个私有数据成员x和bestx。x用于记录从根到当前结点的路径,bestx记录当前最优解。算法搜索到达叶节点处,就修正bestx的值。
递归回溯`
#include <iostream>
using namespace std;
const int n;
//子集树的遍历回溯
void Backtrack(int t)
{
if(t > n)
Output(x);
else
{
for(int i = 0;i <= 1;i++)
{
//子集树的每个分支只能是0,1
//类似0-1背包问题
x[t] = i;
//满足约束条件且满足界限函数就继续递归
if(Constrainit(t) && Bound(t))
Backtrack(t+1);
}
}
}
template<class Type>
class Loading
{
friend Type MaxLoading(Type [],Type,int,int []);
private:
void Backtrack(int i);
int n, //集装箱数
*x,//当前解
*bestx;//当前最优解
Type *w,//集装箱重量数组
c,//第一艘轮船的载重量
cw,//当前载重量
bestw,//当前最优载重量
r;//剩余集装箱重量
};
template<class Type>
void Loading<Type>::Backtrack(int i) //搜索第i层结点
{
if(i > n) //到达叶节点
{
if(cw > bestw)
{
for(j = 1;j <= n;j++)
{
bestx[j] = x[j];//bestx[]中存储着最优装载路径(0,1元素构成)
}
}
return;
}
//搜索子树
r -= w[i];
if(cw+w[i] <= c) //搜索左子树,如果加入之后不超过第一艘床的容量,则可加
{
x[i] = 1;
cw += w[i];
Backtrack(i + 1);
cw -= w[i];//回溯
}
//这里如果cw+r<=bestw我们就更不能访问右子树了,因为访问右子树表示这个集装箱选择不加入,因此不可能出现>bestw的情况
if(cw+r > bestw)
{
x[i] = 0;
Backtrack(i+1);
}
r += w[i];//回溯
template<class Type>
Type MaxLoading(Type w[],Type c,int n,int bestx[])
{
Loading<Type> X;
//初始化X
X.x = new int [n+1];
X.w = w;
X.c = c;
X.n = n;
X.bestx = bestx;
X.cw = 0;
X.r = 0;
for(int i = 1;i <= n;i++)
{
X.r += x[i];
}
X.Backtrack(1);
delete [] X.x;
return X.bestw;
}
}
迭代回溯
#include <iostream>
using namespace std;
template<class Type>
Type MaxLoading(Type w[],Typec,int n,int bestx[])
{
int i = 1;
int *x = new int[n+1];
Type cw = 0,//当前载重量
bestw = 0,//当前最优载重量
r = 0;//剩余集装箱重量
for(int j = 1;j <=n;j++)
{
r+=w[j];
}
while(true)
{
while(i <= n && !x[i])
{
r-=w[i];
cw+=w[i];
x[i]=1;
i++;
}
if(i > n)
{
for(int j = 1;j <= n;j++)
bestx[j] = x[j];
bestw = cw;
}
else
{
r -= w[i];
x[i] = 0;
i++;
}
while(cw + r <= bestw)
{
i--;
while(i > 0 && !x[i])
{
r += w[i];
i--;
}
if(i == 0)
{
delete [] x;
return bestw;
}
//进入右子树
x[i] = 0;
cw -= w[i];
i++;
}
}
}
总结
时间复杂度O(2^n)