简体   繁体   English

回溯并找到等于某个值k的所有数组子集

[英]Backtracking and finding all subsets of array that equal some value k

I asked question earlier and i thought i understood it when i went to my terminal to code i am once again completely lost. 我早些时候问过一个问题 ,我以为我理解了,当我去终端进行编码时,我再次完全迷失了。 My problem is i have some array say [1,2,3,4] and i need to find all possible combos that will equal a destination value 5. 我的问题是我有一些数组说[1,2,3,4],我需要找到所有可能等于目标值5的组合。

I understand there is a backtracking approach to this. 我了解有一种回溯方法。 i am not able to get it as online a lot of solution approaches are going over my head i just need a simple explanation or a step by step trace of a very small array to visualize what's going on. 我无法获得它,因为很多解决方案方法都在网上困扰着我,我只需要一个简单的解释或一个非常小的数组的逐步跟踪就可以直观地看到正在发生的事情。

I have spent the last 12 hours in the library and i feel really depressed now since i am not able to understand it, i would also appreciate a simple approach. 我在图书馆度过了最后12个小时,由于我无法理解它,我现在感到非常沮丧,我也希望采用一种简单的方法。 Also i am not familiar with many languages other than C or java. 另外,我不熟悉C或Java以外的许多语言。

You've got your hands on a variant of the Subset Sum Problem . 您已经掌握了子集和问题的变体。 Unfortunately the problem is NP-Complete, so if you're hoping for a quick (polynomial) solution, you're out of luck. 不幸的是,问题是NP-Complete,因此,如果您希望获得快速的(多项式)解决方案,那么您将不走运。

That said, if your array is fairly small and the values are fairly small, a brute force solution may still be quick enough. 就是说,如果您的数组很小并且值很小,那么蛮力解决方案可能仍然足够快。

import java.util.Arrays;
import java.util.LinkedList;


public class SubsetSum {

    /** Helper for the power set generating function.
     *  Starts with a partially built powerSet pSet that includes
     *  numbers with index 0 ... i. For every element in the current power set
     *  Create a new array that is equivalent to the existing array plus it has
     *  the i+1th number (n) concatinated on the end.
     * @param pSet - The completed pSet for a smaller set of numbers
     * @param n - The number of add to this pSet.
     * @return a reference to pSet (not necessary to use). When returning,
     * pSet will have double the size it had when the method began.
     */
    private static LinkedList<Integer[]> addNumb(LinkedList<Integer[]> pSet, int n){
        LinkedList<Integer[]> toAdd = new LinkedList<>();
        for(Integer[] arr : pSet){
            Integer[] arr2 = new Integer[arr.length+1];
            for(int i = 0; i < arr.length; i++){
                arr2[i] = arr[i];
            }
            arr2[arr.length] = n;
            toAdd.add(arr2);
        }

        //Add all of the toAdds to the pSet
        pSet.addAll(toAdd);
        return pSet;
    }

    /** Creates the power set for the given array of ints.
     * Starts by creating a set with the empty array, which is an element of every
     * power set. Then adds each number in the input array in turn to build the final
     * power set.
     * @param numbs - the numbers on which to build a power set
     * @return - the power set that is built.
     */
    private static LinkedList<Integer[]> makePowerSet(int[] numbs){
        LinkedList<Integer[]> pSet = new LinkedList<Integer[]>();
        //Add the empty set as the first default item
        pSet.add(new Integer[0]);

        //Create powerset
        for(int n : numbs){
            addNumb(pSet, n);
        }

        return pSet;
    }

    /** Returns the simple integer sum of the elements in the input array */
    private static int sum(Integer[] arr){
        int i = 0;
        for(int a : arr){
            i += a;
        }
        return i;
    }


    /** Brute-forces the subset sum problem by checking every element for the desired sum.
     */
    public static void main(String[] args) {
        int[] numbs = {1,2,3,4,5,6,7,8}; //Numbers to test
        int k = 7;                 //Desired total value

        LinkedList<Integer[]> powerSet = makePowerSet(numbs);

        for(Integer[] arr : powerSet){
            if(sum(arr) == k)
                System.out.println(Arrays.deepToString(arr));
        }
    }

}

Indeed, there is a story about backtracking, etc. 确实,有一个关于回溯的故事,等等。

Let's just go with a bit more complex example: 让我们来看一个更复杂的示例:

The value we want to reach is 11, and the array is [5,4,8,2,3,6] 我们想要达到的值是11,数组是[5,4,8,2,3,6]

Here is one of the possible algorithms: 这是可能的算法之一:

We will list all the way we find to reach every possible number lesser than or equal to 11 (I won't talk about the structure we use or anything, because I am just explaining the algorithm, not its implementation). 我们将列出找到小于或等于11的所有可能数字的所有方式(我不会谈论我们使用的结构或任何东西,因为我只是在解释算法,而不是其实现)。

We will start with nothing, and add consider one new number at a time. 我们将从一无所有开始,然后一次考虑一个新数字。 In this algorithm, I will consider every number in the array is positive, so I won't keep track of reaching numbers higher than the number we want to reach. 在此算法中,我将认为数组中的每个数字都是正数,因此我不会跟踪达到的数字高于我们要达到的数字。

So at the beginning we have nothing. 因此,一开始我们什么都没有。

We introduce our first number : 5 我们介绍我们的第一个数字:5

We so have one way to reach 5, and it is 5 (or 5+0 if you prefer) 因此,我们有一种方法可以达到5,即5(如果您愿意,也可以是5 + 0)

We introduce our second number : 4 The number we can now reach are: 我们介绍第二个数字:4现在可以达到的数字是:

4:{4}
5:{5}
9:{4+5}

We introduce our third number : 8 The number we can now reach are: 我们介绍第三个数字:8现在可以达到的数字是:

4:{4}
5:{5}
8:{8}
9:{4+5}

Nothing more because 8+4 > 11 仅此而已,因为8 + 4> 11

We introduce our forth number : 2 The number we can now reach are: 我们介绍第四个数字:2现在可以达到的数字是:

2:{2}
4:{4}
5:{5}
6:{4+2}
7:{5+2}
8:{8}
9:{4+5}
10:{8+2}
11:{4+5+2}

We introduce our fifth number : 3 The number we can now reach are: 我们介绍第五个数字:3现在可以达到的数字是:

2:{2}
3:{3}
4:{4}
5:{5 ; 2+3}
6:{4+2}
7:{5+2 ; 4+3}
8:{8 ; 5+3}
9:{4+5 ; 4+2+3}
10:{8+2 ; 5+2+3}
11:{4+5+2 ; 8+3}

We introduce our sixth number : 6 The number we can now reach are: 我们介绍第六个数字:6现在可以达到的数字是:

2:{2}
3:{3}
4:{4}
5:{5 ; 2+3}
6:{4+2 ; 6}
7:{5+2 ; 4+3}
8:{8 ; 5+3 ; 2+6}
9:{4+5 ; 4+2+3 ; 3+6}
10:{8+2 ; 5+2+3 ; 4+6}
11:{4+5+2 ; 8+3 ; 5+6 ; 2+3+6}

Conclusion : there are 4 ways to make 11 : 4+5+2 ; 结论:制作11的方式有4种:4 + 5 + 2; 8+3 ; 8 + 3; 5+6 and 2+3+6 5 + 6和2 + 3 + 6

Below is some simple (but horribly inefficient) code to solve this problem. 以下是一些简单(但效率极低)的代码来解决此问题。

The "backtracking" part of this is implemented via recursion. 它的“回溯”部分是通过递归实现的。 Ever time you return from a method in Java you "backtrack" through the stack to wherever it was called from. 每当您从Java中的方法返回时,您都会在堆栈中“回溯”到调用它的位置。 This makes using the call stack to track your "backtrack" state for you really easy. 这使得使用调用堆栈来跟踪您的“回溯”状态确实非常容易。

Here's the basic idea. 这是基本思想。 Let's say I'm searching some array A for the sum n . 假设我要在数组A中搜索总和n We start our search in the array at index i =0. 我们从索引i = 0的数组开始搜索。 Then we try two things: 然后我们尝试两件事:

  • Try including the element A[i] in the running sum. 尝试将元素A [i]包括在运行总和中。 We do this by searching the array from index i+1 for the value nA[i] . 为此,我们从索引i + 1的数组中搜索值nA [i] We need to record this element in our running list of included elements. 我们需要将此元素记录在包含元素的运行列表中。 We'll call this list of all elements included in our running sum xs . 我们称此为运行总和xs中包含的所有元素的列表。
  • Try not including the element A[i] in the running sum. 尝试在运行总和中包括元素A [i] We do this by searching the array from index i+1 for the current value of n . 为此,我们从索引i + 1的数组中搜索n的当前值。 Since we didn't include A[i] we don't need to update xs . 由于我们没有包括A [i],所以我们不需要更新xs

See how we search the whole array for the first case, backtrack and then do the search over again for the second case? 看看我们如何在第一种情况下搜索整个数组,然后回溯 ,然后在第二种情况下再次搜索?

Note that you need to have a preserved copy of xs around after "backtracking" to use in the second search. 请注意,您需要在“回溯”之后保留xs的保留副本,以用于第二个搜索。 I think the simplest way to do this with the standard Java library is to undo the changes to xs when you backtrack. 我认为使用标准Java库执行此操作的最简单方法是在回溯时撤消对xs的更改。 So, if you add some element x to the end of xs to do the "with"-search, then simply remove the last element from xs just before you do the "without"-search. 因此,如果您在xs的末尾添加一些元素x来执行“ with”搜索,那么只需在执行“ without”搜索之前从xs中删除最后一个元素即可。

Rather than trying to store all the answers in a datastructure, I just print an answer as soon as I find it. 与其尝试将所有答案存储在数据结构中,不如我只是在找到答案后立即打印答案。 This is also to simplify the logic of this solution. 这也是为了简化此解决方案的逻辑。

import java.util.Deque;
import java.util.ArrayDeque;

public class SubarraySums {

    /** Program entry point */
    public static void main(String[] args) {
        int[] array = { 1, 8, 7, 9, 5, 2 };
        findSubarraySums(12, array);
    }

    /** Wrapper function for the search */
    public static void findSubarraySums(int goal, int[] array) {
        // Search the whole array with an empty starting set
        search(goal, new ArrayDeque<Integer>(), array, 0);
    }

    /** Helper for printing an answer */
    private static void printAnswer(Deque<Integer> xs) {
        // Print the sum
        int sum = 0;
        for (int x : xs) sum += x;
        System.out.printf("%d =", sum);
        // Print the elements
        for (int x : xs) {
            System.out.printf(" %d", x);
        }
        System.out.println();
    }

    /**
     * Search the array, starting from index i,
     * for a subset summing to n.
     * The list xs includes all of the elements that are already
     * assumed to be included in this answer
     */
    private static void search(int n, Deque<Integer> xs, int[] array, int i) {
        // Base case: we've reached zero!
        if (n == 0) {
            printAnswer(xs);
            return;
        }
        // Base case: solution not found
        if (n < 0 || i >= array.length) return;
        // Recursive case: try searching with and without current element
        // with:
        xs.addLast(array[i]);
        search(n-array[i], xs, array, i+1);
        // without:
        xs.removeLast();
        search(n, xs, array, i+1);
    }

}

The above code has 6 elements in the array, so it's going to make 2 6 =64 recursive calls to search . 上面的代码在数组中有6个元素,因此将对search进行2 6 = 64个递归调用。 That's why it's "super inefficient". 这就是为什么它“超级低效”。 But it's also super simple, so that should help you understand it. 但这也非常简单,因此应该可以帮助您理解它。 You should step through the code with your debugger to see what happens, or just trace out the execution on a piece of paper. 您应该使用调试器单步执行代码以查看会发生什么,或者只是在纸上追踪执行情况。 It should be pretty obvious how the execution "backtracks" up the call stack to try both options (include / not include) during the search. 显而易见,执行如何在搜索过程中“回溯”调用堆栈以尝试两个选项(包括/不包括)。

I used ArrayDeque in the code above to store my xs list simply because the Deque interface has the addLast and removeLast methods. 我用ArrayDeque在上面的代码来存储我的xs列出只是因为Deque接口具有addLastremoveLast方法。 A LinkedList also would have worked (since it also implements the Deque interface). LinkedList也可以工作(因为它也实现了Deque接口)。 An ArrayList would work too, but you'd need to use add and remove(list.size()-1) , which is a bit more verbose. ArrayList也可以使用,但是您需要使用addremove(list.size()-1) ,这有点冗长。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM