最优装载问题(分支界限算法)

概述

有一批共n个集装箱要装上2艘载重量分别为c1,c2的轮船,其中集装箱i的重量为wi,且要求确定是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船。可证明,采用如下策略可以得到一个最优装载方案:先尽可能的将第一艘船装满,其次将剩余的集装箱装到第二艘船上。

解题思路

  1. 首先将第一艘轮船尽可能装满。
  2. 将剩余的集装箱装上第二艘轮船。

在算法的while循环中,首先检测当前扩展结点的左儿子结点是否为可行结点。如果是则将其加入到活结点队列中。然后将其右儿子结点加入到活结点队列中(右儿子结点一定是可行结点)。2个儿子结点都产生后,当前扩展结点被舍弃。
活结点队列中的队首元素被取出作为当前扩展结点,由于队列中每一层结点之后都有一个尾部标记-1,故在取队首元素时,活结点队列一定不空。当取出的元素是-1时,再判断当前队列是否为空。如果队列非空,则将尾部标记-1加入活结点队列,算法开始处理下一层的活结点。
节点的左子树表示将此集装箱装上船,右子树表示不将此集装箱装上船。设bestw是当前最优解;ew是当前扩展结点所相应的重量;r是剩余集装箱的重量。则当ew+r<bestw时,可将其右子树剪去,因为此时若要船装最多集装箱,就应该把此箱装上船。另外,为了确保右子树成功剪枝,应该在算法每一次进入左子树的时候更新bestw的值。
为了在算法结束后能方便地构造出与最优值相应的最优解,算法必须存储相应子集树中从活结点到根结点的路径。为此目的,可在每个结点处设置指向其父结点的指针,并设置左、右儿子标志。
找到最优值后,可以根据parent回溯到根节点,找到最优解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
变量含义:  
Ew: 扩展节点的载重量
W: 重量数组
Q: 活节点队列
bestw: 当前最优载重量
i: 当前处理到的层数
n: 总货物数

while (true) {
// 检查左儿子结点
if (Ew + w[i] <= c) //x[i] = 1
EnQueue(Q, Ew + w[i], bestw, i, n);
// 右儿子结点总是可行的
EnQueue(Q, Ew, bestw, i, n); //x[i] = 0
Q.Delete(Ew); // 取下一扩展结点

if (Ew == -1) { // 同层结点尾部
if (Q.IsEmpty())
return bestw;
Q.Add(-1); // 同层结点尾部标志
Q.Delete(Ew); // 取下一扩展结点
i++; // 进入下一层
}
}

程序代码

备注:C++和Java版本并没有实现最优解。

C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
#include<queue>
using namespace std;


//Q为队列,wt为当前扩展结点所对应的载重量,bestw最优载重量,
//i当前层数,n为总层数
void enqueue(queue<int> &Q,int wt,int &bestw,int i,int n)
{
if(i==n)
{
if(wt>bestw) bestw=wt;
}else
Q.push(wt);//非叶子结点则加入队列中
}

int MaxLoading(int w[],int c,int n)
{
//初始化
queue<int> q;
q.push(-1);
int i=1;
int EW=0,bestw=0;
while(true)
{
if(w[i]+EW<=c) {
enqueue(q,w[i]+EW,bestw,i,n);
}
enqueue(q,EW,bestw,i,n);
EW=q.front();
q.pop();
if(EW==-1)
{
if(q.empty())
return bestw;
q.push(-1);
EW=q.front();
q.pop();
i++;
}
}
}
void main()
{
//物品的个数
int n=3;
//背包的容量
int c =50;
//物品的重量
int w[100]={10,40,50};

int bestw = MaxLoading(w,c,n);
cout<<"做大装载量为"<<bestw<<endl;
cout<<"end";
}

Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package demo1;

/**
* 分支限界FIFO
*
*/
public class BranchLimitFIFOSearch {

public static void main(String[] args) {
// n个货箱
int n = 3;
// 第一艘船的载重量
float c1 = 50;
// 第二艘船的载重量
float c2 = 50;
// 货箱质量数组
float[] w = { 0, 10, 40, 40 };

// 初始化
BranchLimitFIFOSearch bfis = new BranchLimitFIFOSearch(n, c1, c2, w);
// 所有货箱的重量之和
float s = bfis.getS();
if (s <= c1 || s <= c2) {
System.out.println("只需要1首船!");
}
if (s > c1 + c2) {
System.out.println("2首船不能解决问题!");
return;
}
// 获取最大装载
bfis.maxLoading(c1);
// 第一艘船的最大装载
float bestw = bfis.getBestw();

if (s - bestw <= c2) {
System.out.println("第1首船装载: " + bestw);
System.out.println("第2首船装载: " + (s - bestw));
} else {
System.out.println("不能解决问题");
}

}

private int n; // n个货箱
private float c1; // 第一艘船的载重量
private float c2; // 第二艘船的载重量
private float bestw; // 第一艘船的最大装载
private float ew = 0; // 当前船的装载量
private float[] w; // 货箱质量数组
private float s = 0; // 所有货箱的重量之和
private MyQueue mq = new MyQueue(); // FIFO队列,活动队列

/**
* 构造方法
*
* @param _n
* @param _c1
* @param _c2
* @param _w
*/
public BranchLimitFIFOSearch(int _n, float _c1, float _c2, float[] _w) {
n = _n;
c1 = _c1;
c2 = _c2;
w = _w;
for (float f : _w) {
s += f;
}
}

/**
* 最优装载值
*
* @param c
* 第1首船的载重量
*/
public float maxLoading(float c) {
// 初始化结点队列,标记分层
// -1:每一层处理完,都以-1为标记
mq.put(new Float(-1));
// A结点的层
int i = 1;
// 当前船的装载量
ew = 0;
// 目前的最优值
bestw = 0;
// 搜索子集空间树
while (!mq.empty()) {
// 检查A结点的左孩子,货箱i是否可以装载
// ew船目前载重量
// 符合条件放到活动队列中
if (ew + w[i] <= c) {
// 货箱i可以装载
addLiveNode(ew + w[i], i);
}
// 右孩子总是可行的,不装载货物i
addLiveNode(ew, i);
// 获取活动队列
ew = (Float) mq.get();
// 到达层的尾部,每层的结束
if (ew == -1) {
// 活动队列没有数据,则返回最优解
if (mq.empty()) {
return bestw;
}
// 处理完当前层,标记-1
mq.put(new Float(-1));
// 获取活动队列的下一个节点,表示船的载重量
ew = (Float) mq.get();
i++; // ew的层
}
}
return bestw;
}

/**
* 入队
*
* @param wt
* @param i
*/
public void addLiveNode(float wt, int i) {
if (i == n) { // 是叶子
// 最大重量
if (wt > bestw) {
bestw = wt;
}
} else { // 不是叶子
// 添加到队列中
mq.put(new Float(wt));
}
}

public int getN() {
return n;
}

public void setN(int n) {
this.n = n;
}

public float getC1() {
return c1;
}

public void setC1(float c1) {
this.c1 = c1;
}

public float getC2() {
return c2;
}

public void setC2(float c2) {
this.c2 = c2;
}

public float getBestw() {
return bestw;
}

public void setBestw(float bestw) {
this.bestw = bestw;
}

public float getEw() {
return ew;
}

public void setEw(float ew) {
this.ew = ew;
}

public float getS() {
return s;
}

public void setS(float s) {
this.s = s;
}

}

活动队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package demo1;

import java.util.LinkedList;

/**
* 自定义活动队列
*
* @since jdk1.6
* @author 毛正吉
* @version 1.0
* @date 2010.05.25
*
*/
public class MyQueue {
private LinkedList ll = new LinkedList();

/**
* 入队
*
* @param o
*/
public void put(Object o) {
ll.addLast(o);
}

/**
* 出队
*
* @return
*/
public Object get() {
return ll.removeFirst();
}

/**
* 队是否为空
*
* @return
*/
public boolean empty() {
return ll.isEmpty();
}
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×