回溯算法 --- 例题1.装载问题
一.问题描述
n个集装箱装到2艘载重量分别为c1,c2的货轮,其中集装箱i的重量为wi且Σwi <= c1 + c2 , 1<=i<=n
问题要求找到一个合理的装载方案可将这n个货箱装上这2艘轮船。
例如 当n=3, c1=c2=50, w=[10, 40, 40], 可将货箱1和2装到第一艘船上;货箱3装到第二艘船上; 若w=[20, 40, 40], 则无法将全部货箱装船.
二.解题思路
简单分析可知:
- 当Σwi = c1+c2, 1<=i<=n问题等价于子集和问题
- 当c1 = c2, 且Σwi = 2c1, 1<=i<=n, 问题等价于划分问题
若装载问题有解, 采用如下策略可得一个最优装载方案:
(1)将第一艘轮船尽可能装满; (2)将剩余的货箱装到第二艘轮船上。
将第一艘船尽可能装满等价于如下0-l背包问题:
用回溯法解装载问题,用子集树(从n个元素的集合S中找出满足某种性质的子集)表示其解空间显然是最合适的.
可行性约束函数可剪枝去不满足约束条件ΣWiXi <= C1(1<=i<=n)的子树.在子集树的第i+1层的节点Z处,用cw记为当前的装载重量,即cw = ΣWiXi (1<=i<=j).当cw>c1时,以结点Z为根的子树中所有节点都不满足约束条件,因而该子树中的解都为不可行解,故将该子树减去.
算法还有一个限界条件就是,如果右子树中剩余所有还未考察的集装箱的总重量 加上 当前重量 还是小于目前最优装载量,那么右子树也不用考虑了,故直接回溯一层!思路非常简单,直接看代码就好了.
具体代码如下:
// 最大装载问题回溯算法
#include
using namespace std;
// 一.初始版本,无限接近穷举(仅仅有一个显性约束条件 (cw+w[i] > c)限制一下U?ェ?*U)
// template
// class Loading
// {
// friend Type MaxLoading(Type [], Type, int);
// private:
// void Backtrack(int i);
// int n; //集装箱数目
// Type *w, //集装箱重量数组
// c, //第一艘轮船的载重量
// cw, //当前载重量
// bestw; //当前最优载重量
// };
// template
// void Loading::Backtrack(int i) //回溯搜索
// {
// // 搜索第i层节点
// if(i>n) //到达叶节点
// {
// if(cw > bestw)
// bestw = cw;
// return ;
// }
// if(cw+w[i] <= c) //搜索子树
// {
// cw += w[i]; //当前载重量增加正考虑对象的重量
// Backtrack(i+1); //x[i] = 1;
// cw -= w[i];
// Backtrack(i+1); //x[i] = 0; 无脑进入右子树
// }
// }
// template
// Type MaxLoading(Type w[], Type c, int n) //返回最优载重量
// {
// Loading x; //初始化
// x.w = w;
// x.c = c;
// x.n = n;
// x.bestw = 0; //当前最优载重量的初值赋为0
// x.cw = 0;
// x.Backtrack(1); //计算最有载重量 --- 调用递归函数,实现回溯搜索
// return x.bestw;
// }
// 二.算法改进 --- 引入剪枝函数
// template
// class Loading
// {
// friend Type MaxLoading(Type[], Type, int);
// private:
// void Backtrack(int i);
// int n;
// Type *w,
// c,
// cw,
// bestw,
// r; //剩余集装箱重量 --- 未考察过的集装箱重量,并非没有装载的集装箱重量
// };
// template
// void Loading::Backtrack(int i)
// {
// if(i>n)
// {
// bestw = cw; //直接更新(这是因为上界函数是算法搜索到的每个叶节点都是当前找到的最优解)
// return;
// }
// //搜索子树
// r -= w[i]; //剩余集装箱重量
// if(cw + w[i] <= c) //x[i] = 1; //无脑左子树,一直左;
// {
// cw += w[i];
// Backtrack(i+1);
// cw -= w[i];
// }
// if(cw+r > bestw) //x[i] = 0;限界函数
// Backtrack(i+1);
// r += w[i]; //左边走不了,右边也走不下去(直接被剪枝),回退一格
// }
// template
// Type MaxLoading(Type w[], Type c, int n) //返回最优载重量
// {
// Loading X;
// // 初始化X
// x.w = w;
// x.c = c;
// x.n = n;
// x.bestw = 0;
// x.cw = 0;
// x.r = 0; //初始化r
// for(int i=0; i (为啥函数模板出错,去掉就没事了.晕(((φ(◎ロ◎;)φ))))
class Loading
{
friend int MaxLoading(int [], int , int , int *);
public:
void Init();
private:
void Backtrack(int i);
int n,
*x, //当前解
*bestx; //当前最优解
int* w;
int c;
int cw,
bestw,
r; //剩余集装箱重量
};
// template
void Loading::Backtrack(int i)
{
// 搜索第i层节点
if(i>n)
{
if(cw > bestw)
{
for(int j=1; j<=n; j++)
bestx[j] = x[j];
bestw = cw;
}
return;
}
r -= w[i];
if(cw + w[i] <= c)
{
x[i] = 1;
cw += w[i];
Backtrack(i+1);
cw -= w[i];
x[i] = 0;
}
if(cw + r > bestw)
{
x[i] = 0;
Backtrack(i+1);
}
r += w[i]; //左边走不了,右边也走不下去(直接被剪枝),回退一格
}
// template
int MaxLoading(int *w, int c, int n, int bestx[])
{
Loading X; //private:可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问
// 初始化X
X.x = new int[n+1];
X.w = w;
X.n = n;
X.c = c;
X.bestx = bestx;
X.bestw = 0;
X.cw = 0;
X.r = 0;
for(int i=1; i<=n; i++) X.r += w[i];
X.Backtrack(1);
delete[] X.x;
return X.bestw;
}
int main()
{
cout<<"请输入一个多少个集装箱: ";
int n;
cin>>n;
cout<<"请输入每个集装箱的重量: ";
int *w = new int[n+1];
w[0] = 0;
for(int i=1; i<=n; i++) cin>>w[i];
cout<<"请输入轮船1的载重量: ";
int c;
cin>>c;
int *bestx = new int[n+1];
for(int i=0; i<=n; i++) bestx[i] = 0;
cout<
运行结果如下:
参考毕方明老师《算法设计与分析》课件.
欢迎大家访问个人博客网站---乔治的编程小屋,和我一起加油吧!