我通过回溯解决了以下问题:


我们获得了军事单位,其重量和力量(数字)的清单。我们有许多船的
运力有限。确定如何为军事单位装载船只,以便
不超过每艘船的承载能力,以使我们具有
我们的船只上可能的最大军事实力。


但是,我的代码不够快,因为正在执行许多不必要的计算,因此无法找到解决方案。我将如何优化代码?我的想法是使用备忘录,但是我将如何实现它呢?

我试图注释我的代码,并尽我所能重构代码。

public class HeroesOntTheBoat {

private int[] arrayWithGeneratedNumbers;
private int numberOfShips;
private int max;
private int[] bestArray;
private ArrayList<Integer> strengths;
private ArrayList<Integer> weights;
private ArrayList<Integer> carryingCapacities;

public HeroesOntTheBoat() {
    carryingCapacities = new ArrayList<Integer>(); // create arraylist for the carrying capacities of ships
    strengths = new ArrayList<Integer>(); //create arraylists for strengths and weights of units
    weights = new ArrayList<Integer>(); //create arraylists for weights
    max = 0; // global variable max for finding the best strength
}

private void generate(int fromIndex) { // I generate all combinations between 0 and numberOfShips
    if (fromIndex == arrayWithGeneratedNumbers.length) { // 0 is representing that unit is being left behind
        processGeneratedNumbers();      // 1,2,3,4..n are representing the number of ship that the unit is loaded on
        return;                         // index of a number in array is representing a specific unit               
    }

    for (int i = 0; i <= numberOfShips; i++) {
        arrayWithGeneratedNumbers[fromIndex] = i;
        generate(fromIndex + 1);
    }
}

public void input(String input) {
    Scanner sc = null;
    try {
        sc = new Scanner(new File(input)); // load my input from a textfile
        numberOfShips = sc.nextInt();  // load the number of ships
        for (int i = 0; i < numberOfShips; i++) { //add carrying capacities to arraylist
            carryingCapacities.add(sc.nextInt());
        }
        bestArray = new int[weights.size()]; // array where we will remember the best combination of units
        while (sc.hasNext()) {
            weights.add(sc.nextInt());
            strengths.add(sc.nextInt());
        }
        arrayWithGeneratedNumbers = new int[weights.size()]; // array where we will generate numbers
        generate(0); // run the generation
        System.out.println(Arrays.toString(bestArray) + " this is the best layout of units"); // after the generation is over
        System.out.println(max + " this is the max strength we can achieve"); // print the results
    } catch (FileNotFoundException e) {
        System.err.println("FileNotFound");
    } finally {
        if (sc != null) {
            sc.close();
        }
    }
}

public void processGeneratedNumbers() {
    int currentStrength = 0; // process every generated result
    boolean carryingCapacityWasExceeded = false;
    int[] currentWeight = new int[numberOfShips + 1];
    for (int i = 0; i < arrayWithGeneratedNumbers.length; i++) { // calculate weights for every ship and ground
        currentWeight[arrayWithGeneratedNumbers[i]] += weights.get(i);
    }
    for (int i = 0; i < currentWeight.length; i++) { // is capacity exceeded?
        if (i != 0 && currentWeight[i] > carryingCapacities.get(i - 1)) { // ignore 0, because 0 represents ground(units I left behind)
            carryingCapacityWasExceeded = true;
        }
    }
    if (carryingCapacityWasExceeded == false) { // if capacity isn't exceeded
        for (int i = 0; i < arrayWithGeneratedNumbers.length; i++) { // calculate strength
            if (arrayWithGeneratedNumbers[i] != 0) { // ignore 0, because I left units with 0 behind on the ground
                currentStrength += strengths.get(i);
            }
        }
        if (currentStrength > max) { // is my current strength better than global maximum?
            max = currentStrength; // replace my global maximum
            bestArray = arrayWithGeneratedNumbers.clone(); // remember the new best layout of units
        }

    }

}


public static void main(String[] args) {
    HeroesOnTheBoat g = new HeroesOnTheBoat();
    g.input("inputFile");
}


示例输入:

2 60 40
30 400
20 500
50 100
40 100
30 50
60 75
40 20


及其结果:

[1, 1, 0, 2, 0, 0, 0] this is the best layout of units
1000 this is the max strength we can achieve


评论

您可以输入示例输入的结果吗?

@dfhwze结果为[1、1、0、2、0、0、0],表示将在设备上放置0、1和4。船0上的0和1单元,船1上的4单元。其余部分留在后面。 1000是我们可以达到的最大强度,因为400 + 500 + 100。

我错了,这基本上是多个背包的背包问题吗?

@ D.BenKnoble:是的,几乎所有的OR问题都归结为一些著名的NP难题。出差推销员,背包,装箱等是一些常见的规范OR问题,即使在OR外部也很常见,而其他很多OR问题都是这些OR的变体。

#1 楼

算法
该算法创建舰船的每种组合,并逐一测试它们,其中包括被认为是首选的组合。例如,如果第一选择是将重量单位60分配给容量为50的船,那么arrayWithGeneratedNumbers的其余内容将不再重要,当已经存在这样的递归调用generate时没有意义问题。
解决方案是尽早检查。因此,在generate中,不仅要检查解决方案的格式,还应每次检查容量是否违反。这似乎是浪费的工作,但这不是浪费,它可以防止大量递归。如果容量检查过早失败,则仅生成它们就将跳过成千上万个组合。
例如,使用下面的名称,例如:
private void generateAssignments(int fromIndex) {
    if (!isValidAssignment(unitAssignment))
        return;
    ...
for (int i = 0; i <= numberOfShips; i++) {
    unitAssignment[fromIndex] = i;
    if (isValidAssignment(unitAssignment))
        generateAssignments(fromIndex + 1);
}

类似的相关技巧是尽早计算强度,一旦发现当前分支无法提供比现有解决方案更好的解决方案,则返回递归树。因此,这仅在已经存在一些非平凡的解决方案的情况下才有效,并且首先进行简单的贪婪传递以进行初始化很有用,因此从一开始就修剪了更多的递归。
例如:
private void generateAssignments(int fromIndex, int currentStrength, int remainingStrength) {
    if (currentStrength + remainingStrength <= bestStrengthSoFar)
        return;

其中currentStrength是已分配给船舶的单位强度的总和,而remainingStrength是未作出选择的单位的强度的总和,在作出选择时,都可以轻松维护两者。这是一种非常幼稚的技术,它不会考虑重量,您可以这样做,例如,选择没有最佳选择的具有最佳强度重量比的设备,然后将剩余强度设置为(double)remainingCapacity / unitWeight * unitStrength。假装您可以用最好的单位填充所有剩余容量,即使需要将其克隆并切成碎片也是如此。
通过修剪,做出选择的顺序很重要:在树根附近修剪比在叶子附近修剪好几个数量级。一般的经验法则是先选择约束最大的项目,然后首先尝试约束最小的项目。因此,在此领域中,选择最重的部队,并将其放在最大的战舰上。但是,我们也可以利用优势,首先选择具有较高的强度/重量比的单元,这要依靠先将好的单元填充到船上,然后再尝试用差的单元来“填补空白”的想法。这里有不同的策略,可能几乎所有东西都比不使用策略要好。
命名
这段代码中有几个极其通用的名称,与无意义接壤。 generate,产生什么? processGeneratedNumbers,什么是过程?什么是“生成的数字”? arrayWithGeneratedNumbers,变量名不应重复其类型,也不能重复“生成的数字”。这不是一个抽象领域,在这里我们不知道生成的是什么或数字的含义,这里的数据具有特定的含义,而方法则可以完成特定的事情。因此,就分配单位给船舶而言,您可以使用诸如


unitAssignment之类的名称。还是shipAssignment,在相同的意义上。

isAssignmentValidcheckCapacities等等。那是针对至今还没有更新最佳解决方案的版本,只是出于修剪目的而进行的纯检查。 br Boolean标记逻辑
总是有争议的一点,但我要说,作为粗略的近似,布尔标记越少越好。当第一次检查失败时返回,没有带有标志的混乱事务。

评论


\ $ \ begingroup \ $
您的评论很现场。做得好。
\ $ \ endgroup \ $
–桅杆
19年8月12日在8:06

\ $ \ begingroup \ $
谢谢!以及如何返回生成?我尝试验证generate中的权重,但是在generate中调用return会导致它停止生成,因此找不到最佳值。
\ $ \ endgroup \ $
–杰克
19年8月14日在11:39

\ $ \ begingroup \ $
@Jack我不确定您做了什么,但是想法是要返回一个级别,而不是一直返回,这是逻辑上的一些错误。无论如何,我添加了一些示例。
\ $ \ endgroup \ $
–哈罗德
19年8月14日在12:43

\ $ \ begingroup \ $
@haroldstackoverflow.com/questions/57494618/…我正在尝试解决我的问题。问题在于它总是跳过[1,0,0,1]数组,该数组实际上包含最佳值。
\ $ \ endgroup \ $
–杰克
19年8月14日在12:51



#2 楼

编写有用的注释

carryingCapacities = new ArrayList<Integer>(); // create arraylist for the carrying capacities of ships
strengths = new ArrayList<Integer>(); //create arraylists for strengths and weights of units
weights = new ArrayList<Integer>(); //create arraylists for weights
sc = new Scanner(new File(input)); // load my input from a textfile
numberOfShips = sc.nextInt();  // load the number of ships
for (int i = 0; i < numberOfShips; i++) { //add carrying capacities to arraylist
...


所有这些注释(以及几乎所有其他注释,为简洁起见,我都省略了)是对代码功能的多余英语解释。他们没有任何价值。相反,通过打断代码,它们使代码更难阅读。这可以通过使用描述字段包含的内容或方法的功能的名称来完成(您已经很好地确定了该名称)。注释不应重复该说明,而应解释代码为什么会执行其操作,因为有时无法仅用代码来表达。

无需解释从中读取数据的原因。当您具有声明扫描程序和文件的代码时,或者在调用new ArrayList<>()时正在创建ArrayList的文件中,则该文件为行。 ,编写和维护。避免它们。

#3 楼

看来您已经大大低估了任务的复杂性。这是一个经典的多重背包问题,与所有NP难题一样,解决问题的时间和/或所需的内存随输入大小的增长非常非常非常快。

算法的选择至关重要。如果算法选择不当,抛光代码没有任何意义,而且天真的迭代也不好。 Coursera上的“离散优化”课程。第2周的视频介绍了(单个)背包问题。对我来说,最有效的方法是“背包5-放松,分支和约束”中描述的方法。基本上,算法是这样工作的:


您做出选择,例如哪艘船可以放第1单元,或者根本不穿。或先将哪个单位放到第1艘船上。您可以选择一个,不是很酷吗?
对于每个选择,您都估计最差和最好的结果。对于您的问题,最坏的结果可能是对剩余任务的快速贪婪解决方案,而最好的结果是贪婪的分数解决方案:您想象一个单元可能被分割,并且其一部分将具有比例值。
如果一个选择的最佳结果比另一个选择的最坏结果差,您可以放心地放弃该选择。否则,请选择最吸引人的分支(不要选择结果最好的分支,而是引入一定的随机性,然后重复步骤1-3。

最终(实际上,相当快),您将获得一个可能的解决方案。它不会是最好的(可能是,但是对于足够大的尺寸,这是不可能的)。最简单的方法是立即尝试下一个分支(深度优先树搜索),通过简单的递归即可实现,并且对于开发人员来说几乎是免费的。但是,这是次优的方法:您将在解决方案树的一个分支上花费大量时间,而解决方案很可能位于其他地方。拥有更明智的搜索策略,能够返回更远的位置并从较早的状态分支会有所作为。您需要具有一些数据结构来跟踪已检查的内容。请注意,它并不会耗尽您的所有内存,这才是真正的目的。

这种方法的一个好处是,对于NP难题,大多数实用算法都共享这种方法,它可以完成任务太大而找不到最佳解决方案,如果您在达到时间限制之前停止它,那么它仍然可能会为您提供足够好的解决方案。

玩得开心!