回溯 - 给定一组数字,找到总和等于 M 的所有子集(给定 M)

Posted

技术标签:

【中文标题】回溯 - 给定一组数字,找到总和等于 M 的所有子集(给定 M)【英文标题】:Backtracking - Given a set of numbers, find all the subsets with a sum equal to M (M is given) 【发布时间】:2017-01-12 00:36:35 【问题描述】:

正如标题所说,我们得到一组数字,我们必须找到总和等于给定数字(我们称之为 M)的所有子集。

你们中的大多数人可能已经熟悉这个问题,所以让我们切入正题。我最近才开始研究回溯编程(我得告诉你,到目前为止我完全是个失败者),这就是我试图解决更“经典”问题的原因。

现在,您将在下面看到我试图以回溯方式解决此问题的代码。但是,代码给出了

线程“主”java.lang.***Error 中的异常

在第 44 行(我将突出显示它),而且,我真的不知道它是否真的以回溯的方式解决了问题,或者我的代码是否只是完整而彻底的便便。

package project3;

import java.util.*;

public class Main 
    static int[] A =  1, 2, 3, 4 ; // the array in which we are given the numbers.->
    static int n = A.length;  // -> I have it filled with 1, 2, 3, 4 for testing purposes
    static int m = 5;  // the number which the subsets' sum must be
    static int[] Sol = new int[50];  // the array in which solutions are stored up-> 
                            //->until they are syso'ed, after that it gets zero'ed
    static void makeZero()           // make the solution array 0 again
        for (int i = 0; i < 50; i++)
            Sol[i] = 0;
    

    static void show()   // outputs the solution array
        int i = 0;
        while (Sol[i] != 0 && i < 49) 
            System.out.print(Sol[i] + " ");
            i++;
        
    

    public static void main(String[] args) 
        Sol[0]=A[0]; back(0, 1, A[0], 1);// we start with the first number in the array as->
                            // -> both the first element as the solution and part of the sum

    static int back(int i, int j, int S, int nr) 
        if (i < n && j < n) 

            if (A[j] + S == m) // if we got a solution, we output it and then go to the ->
                Sol[nr] = A[j]; // -> next element if possible, if not, we start again with ->
                show();         // -> the following element
                if (j < n - 1)
                    back(i, j++, S, nr);
                else if (i < n - 1) 
                    makeZero();
                    back(i + 1, i + 2, 0, 0);
                
            

            else if (A[j] + S > m)   // condition for stoping and starting over with another element
                if (j < n - 1)  // we try again with the following element
                    back(i, j++, S, nr);// LINE 44 : Exception in thread "main" java.lang.***Error
                else if (i < n - 2 && j == n - 1)  // if not possible, we start again with the following element
                    makeZero();
                    back(i + 1, i + 2, 0, 0);
                 else if (i == n - 2 && j == n - 1)  // if we are down to the last element-> 
                    if (A[i + 1] == m)             // ->we check if it is ==m
                        System.out.println(A[i + 1]);
                


            else if (j < n - 1 && A[j] + S < m)   // obvious
                Sol[nr++] = A[j];
                S = S + A[j];
                back(i, j + 1, S, nr);
            

            else if (j == n - 1 && A[j] + S < m && i < n - 2) // if the sum!=m and the are no more elements-> 
                makeZero();                                   // ->start again with another element
                back(i + 1, i + 2, 0, 0);
            
            else  // if we are down to the last element, we check if it is ==m
                if(A[i+1]==n-1)
                    System.out.println(A[i + 1]);
            

        

        return 0;
    


注意:我希望我的 cmets 有用,但如果它们比帮助忽略它们更令人困惑,我认为您可以了解没有它们我在做什么。

尽管如此,我想知道为什么代码会给出该错误(我确实知道在什么情况下通常会给出该错误,但我不明白为什么我在这里得到它,因为我看不到任何无限循环)以及如何使代码工作,以及是否回溯。

【问题讨论】:

当程序的运行时堆栈中的内存量达到其最大容量时会发生这种情况。实现此错误的最简单方法是使用递归,通过一遍又一遍地调用函数中的函数而不返回,您不断地将事物推送到运行时堆栈。这就是你对 back() 的连续调用所做的事情。 @RAZ_Muh_Taz 我编辑了最后一部分。 首先我会使用一个队列,当你达到“50”个数限制而不是一个大小为 50 的数组时,我会使用一个队列,然后将其删除。另外,你给它一个“更糟糕的情况”场景”为 1,因为数组中任何这些数字的总和永远不会等于 1,这将解释堆栈溢出和对 back() 的大量调用 @RAZ_Muh_Taz 该数组仅用于测试目的。另外,我不明白你的意思是“......你给它一个“更糟糕的情况”1......” 所以你的方法的退出条件是当它找到一个加起来等于你的目标总和的解决方案时返回是否正确?好吧,如果你给它一个给定数组不可能的总和值,那么你将练习每个可能的子集。这被称为最坏的情况,当涉及到导致堆栈溢出错误的递归时 【参考方案1】:

为了在不发生堆栈溢出错误的情况下找到所有子集,我强烈建议不要使用递归。使用递归通常会在运行时产生大量开销。这种开销会导致堆栈溢出错误。您应该使用一种称为动态编程的更稳定的算法方法/设计。

Dynamic Programming Example 应该向您展示如何利用您当前拥有的东西并将其转化为动态编程概念。

【讨论】:

以上是关于回溯 - 给定一组数字,找到总和等于 M 的所有子集(给定 M)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 113. 路径总和 II(dfs,回溯,列表,二叉树,Java)

LeetCode 113. 路径总和 II(dfs,回溯,列表,二叉树,Java)

总和大于或等于 k ​​的最小子集

生成所有总和组合

给定一组数字,返回所有其他数字的产品数组(无分区)

给定一个目标总和和一组整数,找到与该目标相加的最接近的数字子集