回溯算法 - 最优装载

(1)问题描述:有一批共 n 个集装箱要装上 2 艘载重量分别为 capacity1 和 capacity2 的轮船,其中集装箱 i 的重量为 wi,且装载问题要求确定是否有一个合理的装载方案可将这些集装箱装上这 2 艘轮船。如果有,找出一种装载方案。

例如:当 n = 3, capacity1 = capacity2= 50, 且 w = [10, 40, 40] 时,则可以将集装箱 1 和 2 装到第一艘轮船上,而将集装箱 3 装到第二艘轮船上;如果 w = [20, 40, 40],则无法将这 3 个集装箱都装上轮船。

(2)基本思路: 容易证明,如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
  a. 首先将第一艘轮船尽可能装满;
  b. 将剩余的集装箱装上第二艘轮船。
    将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近 capacity1 。由此可知,装载问题等价于以下特殊的 0-1 背包问题。

  

变量 Xi = 0 表示不装入集装箱 i,Xi = 1 表示装入集装箱 i;     

  用回溯法设计解装载问题的O(2n)计算时间算法。在某些情况下该算法优于动态规划算法。

(3)算法设计:

  子集树模板算法,时间复杂度为:O(2n)

用回溯法解装载问题时,用子集树表示其解空间显然是最合适的,用可行性约束函数可剪去不满足约束条件的子树。

可以引入一个上界函数,用于剪去不含最优解的子树,从而改进算法在平均情况下的运行效率。设z是解空间树第 i 层上的当前扩展结点。currentWeight 是当前载重量;bestWeight 是当前最优载重量;indeterminacyWeight 是剩余集装箱的重。定义上界函数为 currentWeight + indeterminacyWeight。在以 z 为根的子树中任一叶结点所相应的载重量,当currentWeight + indeterminacyWeight <= bestWeight 时,可将z的右子树剪去。

(4)算法代码:

public class ExcellentLoading {

    /**
     * 物品数量
     */
    private static Integer num;

    /**
     * 物品重量数组
     */
    private static Integer[] weight;

    /**
     * 物品存储数组【0:不存放  1:存放】
     */
    private static Integer[] store;

    /**
     * 船的容量
     */
    private static Integer capacity;

    /**
     * 船的最优载重量【最优解】
     */
    private static Integer bestWeight = 0;

    /**
     * 未确定物品的载重量
     */
    private static Integer indeterminacyWeight = 0;

    /**
     * 船的当前载重量
     */
    private static Integer currentWeight = 0;

    /**
     * 物品最优解的下标数组
     */
    private static Integer[] bestIndex;

    /**
     * 初始化数据
     */
    private static void initData() {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入船的容量:");
        capacity = input.nextInt();

        System.out.println("请输入物品的数量:");
        num = input.nextInt();

        System.out.println("请输入各个物品的重量");
        weight = new Integer[num];
        store = new Integer[num];
        bestIndex = new Integer[num];
        for (int i = 0; i < num; i++) {
            weight[i] = input.nextInt();
            indeterminacyWeight += weight[i];
            store[i] = 0;
            bestIndex[i] = i;
        }
    }

    /**
     * 装载
     */
    private static void loadingBacktrack(Integer i) {
        if (i == weight.length) {                               // 到达叶子结点
            if (currentWeight > bestWeight) {                   // 当前船的装载量 > 最优解,赋值操作
                for (int j = 0; j < weight.length; j++) {
                    bestIndex[j] = store[j];
                }
                bestWeight = currentWeight;
            }
            return;
        }
        indeterminacyWeight -= weight[i];                       // 减去已被确定该讨论的物品重量
        if (currentWeight + weight[i] <= capacity) {            // 搜索左子树
            store[i] = 1;                                       // 物品装载
            currentWeight += weight[i];                         // 当前船的载重量 + 该物品重量
            loadingBacktrack(i + 1);
            currentWeight -= weight[i];                         // 当前船的载重量 - 该物品重量【回溯到上一层,讨论该物品不装】
        }
        if (currentWeight + indeterminacyWeight > bestWeight) { // 搜索右子树 || 剪枝函数【如果船的当前载重量 + 未确定物品的重量 <= 当前船的最优值,直接剪掉】
            store[i] = 0;                                       // 该物品不装
            loadingBacktrack(i + 1);
        }
        indeterminacyWeight += weight[i];
    }

    /**
     * 输出
     */
    private static void print() {
        System.out.println("船装载物品最优解:");
        Stream.of(bestIndex).forEach(element -> System.out.print(element + " "));
        System.out.println();
    }

    public static void main(String[] args) {
        // 初始化数据
        initData();

        // 装载
        loadingBacktrack(0);

        // 输出
        print();
    }

}

最优装载核心代码

(5)输入输出

请输入船的容量:
60
请输入物品的数量:
5
请输入各个物品的重量
10 25 30 10 5
船装载物品最优解:
0 1 1 0 1

算法输入输出

(6)总结:最优装载诠释了回溯算法中子集树的核心思想,类似于 0 - 1 背包问题,集装 i 箱装与不装,回溯判断最优解,使用剪枝函数去除不必要的无效搜索,否则进入右子树再次进入深度搜索,直至深度搜索完整棵二叉树,搜索出所有的解,找出最优解即可;

  回溯算法子集树的时间复杂度 O(2n),递归求解,不太好想,希望各位在纸上画一画,模拟我的代码走一遍流程,便于大家理解回溯算法,递归算法。

原文地址:https://www.cnblogs.com/blogtech/p/12297896.html

时间: 2024-10-29 00:43:59

回溯算法 - 最优装载的相关文章

回溯法--无优化 最优装载问题

//#include "stdafx.h" // 回溯法,解空间分为排列数和子集树,前者是不同节点顺序的排列,后者是一个(0,1,...)的向量子集// 最大装载问题,是一个NP问题,目前只计算第一艘船,属于子集树// 有几个货物,子集树就有几层,当前题目为5层// 我感觉递归还是太过于精巧和经凑,很难挖空心思自己写出来,多熟悉别人现有的程序是一个好办法. #include<iostream>using namespace std; template<class T&

贪心算法之最优装载

贪心算法通过一系列的选择来得到问题的解.它所做的每一个选择都是当前状态下局部最好选择.从许多的贪心算法求解的问题可以看到可用贪心算法求解的问题一般具有两个重要的性质:贪心选择性质和最优子结构性质. 1.贪心选择性质 贪心选择性质是 指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到.与动态规划算法的不同之处是贪心算法只依赖在当前状态下做出最优选择,然后再去解做出这个选择后产生的相应的子问题.贪心算法依赖于以往做出的选择,但是绝不依赖未来做出的选择.所以贪心算法是自顶向下解决问题

贪心算法:最优装载问题

/*----------------------------------------------------- 给出n个物体,第i个物体的重量为wi. 选择尽量多的物体,使得总重量不超过C. 输入: n和C以及n个整数表示的wi. 输出: 按照输入物体的顺序输出n个用空格分隔的Y或N. Y表示该物体被选中,N表示不被选中. 最后一行输出所选中的物体的个数num和总重量w,用空格分隔. 注意:这个地方每个物体是不可再分割的整体. 思路: 先把所有物体按重量排序(从小到大排序) , 然后贪心选择重量

贪心算法之最优装载问题

问题描述: 给出n个物体,第i个物体的重量是Wi,选择尽量多的物体,使得总重量不超过C. 问题分析: 这是一个很典型的用贪心算法的题目.要想让装的物体越多,自然装的最轻的物体就越多.因此可以对物体的重量由小到大进行排序,然后依次装载即可.这就体现了贪心算法只顾眼前,但却可以得到最优解. 解决问题:  代码如下 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> //为了引入memcpy 4 /

从零开始学回溯算法

本文在写作过程中参考了大量资料,不能一一列举,还请见谅. 回溯算法的定义:回溯算法也叫试探法,它是一种系统地搜索问题的解的方法.回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试. 解题的一般步骤是: 1.定义一个解空间,它包含问题的解: 2.利用适于搜索的方法组织解空间: 3.利用深度优先法搜索解空间: 4.利用限界函数避免移动到不可能产生解的子空间. 问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性. 话不多说,我们来看几个具体的例子慢

回溯算法入门及经典案例剖析(初学者必备宝典)

前言 基于有需必写的原则,并且当前这个目录下的文章数量为0(都是因为我懒QAQ),作为开局第一篇文章,为初学者的入门文章,自然要把该说明的东西说明清楚,于是...我整理了如下这篇文章,作者水平有限,有不足之处还望大家多多指出~~~ 概念 首先,回溯是什么意思?很多初学者都会问这样的一个问题.我们可以举这样一个例子: 1 1 1 1 0 1 0 1 0 1 0 1 0 1 1 1 我们看到了如图所示的一个4*4的迷宫了,我们假设数字1标记的位置为道路,数字0标记的位置为一堵墙,一个人由起点(0.0

五大常用算法----贪心、动态规划、分支限界、分治算法和回溯算法

五大常用算法之一:贪心算法 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解. 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择.必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关. 所以对所采用的贪心策略一定要仔细分析其是否满足无后效性. 五大常用算法之二:动态规划算法 五大常用算法之三:分支限界算法

穷举递归和回溯算法终结篇

穷举递归和回溯算法 在一般的递归函数中,如二分查找.反转文件等,在每个决策点只需要调用一个递归(比如在二分查找,在每个节点我们只需要选择递归左子树或者右子树),在这样的递归调用中,递归调用形成了一个线性结构,而算法的性能取决于调用函数的栈深度.比如对于反转文件,调用栈的深度等于文件的大小:再比如二分查找,递归深度为O(nlogn),这两类递归调用都非常高效. 现在考虑子集问题或者全排列问题,在每一个决策点我们不在只是选择一个分支进行递归调用,而是要尝试所有的分支进行递归调用.在每一个决策点有多种

0-1 背包问题、背包问题、最优装载问题、哈夫曼编码,这几个问题的思想是什么?

0-1背包问题: 给定n种物品和一个背包.物品i的重量是Wi,其价值为Vi,背包的容量为C.应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包.不能将物品i装入背包多次,也不能只装入部分的物品i. 背包问题: 与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n. 解决方法:求每个物品的价值重量比,即价值/重量.然后添加价值重量比最大的物品,添加结束如果