文章目录
回溯法求解集装箱装载
求解集装箱问题:
有n个集装箱要装上一艘载重量为W的轮船,其中集装箱i(1<=i<=n)的重量为wi。
在装载体积不受限制的情况下,将尽可能重的集装箱装上轮船,当总重量相同时要求选取集装箱个数尽可能少。
用回溯法编写程序求解。要求采用适当剪枝条件提高效率,左孩子结点剪枝的条件是只能装载满足重量要求的集装箱。
问题分析:
(1)全局变量:
将各个集装箱的重量存放在一个数组w中,不用下标0的元素, int w[MAXN];
变量n表示集装箱的个数,W表示轮船的重量;
变量maxw用来存放最优解的总重量;
整型数组x[MAXN]用来存放最优调度序列;
变量maxnum存放当前最优调度的集装箱个数。
(2)回溯函数backtracking参数:
参数i表示考虑的第i个集装箱;
tw表示选择的集装箱重量和;
rw表示剩余集装箱的重量和(初始值等于所有集装箱重量和);
数组op表示一个调度,即一个装载方案;
参数temp存放当前方案的集装箱数量。
(3)剪枝方式:
①左分枝剪枝:仅仅扩展满足tw+w[i]<=W条件的左孩子结点;
②右分枝剪枝:仅仅扩展满足tw+rw-w[i]>=maxw条件的右孩子结点,即不选择第i个集装箱能找到更优解;
(4)最优方案选择:
i>n对应一个可行解(装载集装箱重量和为tw)的叶子结点,将tw与maxw比较选择一个更优的装载方案x。如果tw==maxw,比较装载的集装箱个数,选择集装箱个数少的为更优方案。
代码展示
Java版本
import java.util.Scanner;
public class huiSu {
public static final int MAXN = 20;
public static int n = 0, W = 0, maxw = 0, minnum = MAXN;
public static int[] w = new int[MAXN];
public static int[] x = new int[MAXN];
private static void backtracking(int i, int tw, int rw, int[] op, int temp) {
if (i > n) {
if (tw > maxw || (tw == maxw && temp < minnum)) {
minnum = temp;
maxw = tw;
for (int j = 1; j <= n; j++) {
x[j] = op[j];
}
}
} else {
if (tw + w[i] <= W) {
op[i] = 1;
backtracking(i + 1, tw + w[i], rw - w[i], op, temp + 1);
}
if (tw + rw - w[i] >= maxw) {
op[i] = 0;
backtracking(i + 1, tw, rw - w[i], op, temp);
}
}
}
private static void printSolution(int n) {
System.out.println("最优装载方案:");
System.out.print("选取的集装箱为:[ ");
for (int j = 1; j <= n; j++) {
if (x[j] == 1) {
System.out.print(j + " ");
}
}
System.out.print("]");
System.out.println();
System.out.println("选取的集装箱总重量为:" + maxw);
System.out.println("选取的集装箱个数为:" + minnum);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int rw = 0;
System.out.print("请输入这艘轮船的重量:");
W = sc.nextInt();
System.out.print("请输入集装箱的个数:");
n = sc.nextInt();
System.out.print("请输入每个集装箱的重量:");
for (int i = 1; i <= n; i++) {
w[i] = sc.nextInt();
rw += w[i];
}
int[] op = new int[MAXN]; // 初始化操作数组
backtracking(1, 0, rw, op, 0);
printSolution(n);
}
}
C++版本
#include <stdio.h>
#include <iostream>
using namespace std;
#define MAXN 20
int n = 0, W = 0, maxw = 0, minnum = MAXN;
int w[MAXN];
int x[MAXN];
void backtracking(int i, int tw, int rw, int op[],int temp) {
if (i > n) {
if (tw > maxw || (tw == maxw && temp < minnum)) {
minnum = temp;
maxw = tw;
for(int j = 1; j <= n; j++)
x[j] = op[j];
}
} else {
if (tw + w[i] <= W) {
op[i] = 1;
backtracking(i + 1, tw + w[i], rw - w[i], op,temp+1);
}
if (tw + rw - w[i] >= maxw) {
op[i] = 0;
backtracking(i + 1, tw, rw - w[i], op,temp);
}
}
}
void printSolution(int n) {
cout << "最优装载方案:" << endl;
cout << "选取的集装箱为";
for (int j = 1; j <= n; j++) {
if (x[j] == 1) {
cout << j<<" ";
}
}
cout << endl;
cout << "选取的集装箱总重量为: " << maxw << endl;
cout << "选取的集装箱个数为: " << minnum << endl;
}
int main() {
int rw = 0;
cout << "请输入这艘轮船的重量:";
cin >> W;
cout << "请输入集装箱的个数:";
cin >> n;
cout << "请输入每个集装箱的重量:";
for (int i = 1; i <= n; i++) {
cin >> w[i];
rw += w[i] ;
}
int op[MAXN] = {0};
backtracking(1, 0, rw, op,0);
printSolution(n);
return 0;
}
复杂度分析
(1)时间复杂度
回溯函数backtracking的解空间树中有2n+1-1个结点,所以最坏情况下的算法时间复杂度为O(2n);函数printSolution的时间复杂度为O(n)。
综上所述:时间复杂度为O(2n)。
(2)空间复杂度
回溯函数backtracking为递归调用,产生O(n)的栈空间开销;函数printSolution的空间复杂度为O(1)。
综上所述:空间复杂度为O(n)。
实验总结
本次实验的过程中遇到过一些问题。其一,在计算每种方案的集装箱数量时,我错误的用全局变量temp来记录,但实际递归、回溯的过程中,集装箱的数量有着较为复杂的增加和减少,应该价将temp作为回溯函数backtracking的参数,这样在每次递归和回溯的过程中对应加1,或者不变。其二,在选择最优方案时,重量相同的情况下,我总是选不到集装箱数量最少的,检查回溯函数的叶子结点部分并未发现错误,最后发现错误出现在右剪枝的判断条件tw + rw - w[i] > maxw,导致相等的条件下的右分支被剪掉,改为tw + rw - w[i] >= maxw即可。
通过本次实验我对回溯法有了更深的理解,递归进行纵向遍历;i是横向遍历,对应每层的元素。同时我更清晰的认识到使用回溯算法的解空间树中的结点数作为算法时间复杂度的分析依据,通常解空间树为子集树时对应算法的时间复杂度为O(2n)。