问题描述:
有n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且∑wi <= c1 + c2。
问是否有一个合理的装载方案,可将这n个集装箱装上这2艘轮船。如果有,找出一种装载方案。
问题分析:
如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
(1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。
将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近c1。由此可知,装载问题等价于以下特殊的0-1背包问题:
max∑ wi * xi && ∑ wi * xi <= c1, xi ∈ {0, 1}, 1 <= i <= n;
算法思路:
用排序树表示解空间,则解为n元向量{x1, ... ,xn }, xi∈{0, 1} 。
约束条件:
当前搜索的层i <= n时,当前扩展结点Z为子集树的内部结点,仅当满足cw+w[i] <= c时进入左子树,x[i]=1; 当cw+w[i] > c ,在以结点Z为根的子树中所有结点都不满足约束条件,因而该子树中解都是不可行解,因而将在该子树删去。
限界函数:
由于是最优化问题, 可利用最优解性质进一步剪去不含最优解的子树:
设Z是解空间树第i层上的当前扩展结点。
设 bestw: 当前最优载重量,
cw : 当前扩展结点Z的载重量 ;
r : 剩余集装箱的重量;
在以Z为根的子树中任意叶结点所相应的载重量不超过cw + r。因此,当cw + r (限界函数) ≤ bestw时,可将Z的右子树剪去。
即:cw + r > bestw 时搜索右子树,x[i]=0;
代码:
#include <bits/stdc++.h> using namespace std; int n; //集装箱数 int cw; // 当前载重量, current weight int bestw; //最优载重重量 int r; //剩余集装箱重量 int c1; //第一艘轮船的载重量 int c2; //第二艘轮船的载重量 int x[100]; //当前解 int bestx[100]; //当前最优解 int w[100]; //集装箱重量数组 void OutPut() { int restweight = 0; for(int i = 1; i <= n; ++i) if(bestx[i] == 0) restweight += w[i]; if(restweight > c2) printf("不能装入\n"); else { printf("船1装入的货物为:"); for(int i = 1; i <= n; ++i) if(bestx[i] == 1) printf(" %d", i); printf("\n船2装入的货物为:"); for(int i = 1; i <= n; ++i) if(bestx[i] != 1) printf(" %d", i); } } void BackTrack(int i) { if(i > n) { if(cw > bestw) { for(int i = 1; i <= n; ++i) bestx[i] = x[i]; bestw = cw; } return; } r -= w[i]; if(cw + w[i] <= c1) //约束条件 { cw += w[i]; x[i] = 1; BackTrack(i + 1); x[i] = 0; cw -= w[i]; } if(cw + r > bestw) //限界函数 { x[i] = 0; BackTrack(i + 1); } r += w[i]; } void Initialize() { bestw = 0; r = 0; cw = 0; for(int i = 1; i <= n; ++i) r += w[i]; } void InPut() { scanf("%d", &n); scanf("%d %d", &c1, &c2); for(int i = 1; i <= n; ++i) scanf("%d", &w[i]); } int main() { InPut(); Initialize(); BackTrack(1); OutPut(); }
测试样例:
测试样例一:
输入:
3
50 50
10 40 40输出
船1装入的货物为: 1 2
船2装入的货物为: 3
截图:
测试样例2:
输入:
3
50 50
20 40 40
输出:
不能装入
截图: