《算法设计与分析》实验报告(六)之装载问题

问题描述:有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记录第几个集装箱,判断该集装箱是否装入

②如果剩余载重量大于该集装箱的重量,先不装入,否则装入。

③再判断如果先不放该集装箱,再放剩下的集装箱是否可以达成最优装载。

④利用回溯法,逐一判断集装箱,直到结束。

 

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
最优装载问题是一个经典的优化问题,目标是在给定的容量和重量限制下,将尽可能多的物品装入一个集装箱中。该问题的数学模型可以表示为一个二维数组,其中每个元素表示一个物品的重量和体积。假设我们有n个物品,其中第i个物品的重量为wi,体积为vi,容量限制为c,要求最大化装载的物品数量。 解决这个问题的常见方法是贪心算法。具体来说,我们可以按照每个物品的单位体积价值(即重量/体积)从大到小排序,然后依次选择单位体积价值最高的物品放入集装箱中,直到集装箱无法再装下任何物品为止。 贪心算法的正确性可以通过贪心选择性质来证明:即每一步选择都是最优的。假设我们已经将前k个物品放入集装箱中,剩余的容量为c-kv,其中v表示前k个物品的体积总和。此时,我们需要决定是否将第k+1个物品放入集装箱中。如果将第k+1个物品放入集装箱中,那么剩余的容量为c-(k+1)v,可以装入的物品数量为m1。如果不将第k+1个物品放入集装箱中,那么剩余的容量为c-kv,可以装入的物品数量为m2。根据贪心选择性质,我们应该选择使得m1>m2的方案。因此,我们可以计算出将第k+1个物品放入集装箱和不放入集装箱两种方案的物品数量,选择数量更多的方案即可。 时间复杂度分析:排序的时间复杂度为O(nlogn),选择物品的时间复杂度为O(n)。因此,总时间复杂度为O(nlogn)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

了一li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值