找到所有与x相加的子集 - 使用初始代码

时间:2013-09-04 22:03:16

标签: algorithm recursion subset subset-sum

我正在尝试建立一个问题,解决另一个类似的问题...下面给出的是一个代码,用于查找总和为特定值的子集总数,我试图修改代码以便我可以返回总和为该值的所有子集(而不是查找计数)。

查找总和为'sum'的suibset总数的代码:

 /**
 * method to return number of sets with a given sum.
 **/
public static int count = 0;
public static void countSubsetSum2(int arr[], int k, int sum) {
    if(sum == 0) {
        count++;
        return;
    }
    if(sum != 0 && k == 0) {
        return;
    }
    if(sum < arr[k - 1]) {
        countSubsetSum2(arr, k-1, sum);
    }
    countSubsetSum2(arr, k-1, sum - arr[k-1]);
    countSubsetSum2(arr, k-1, sum);
}

有人可以对此代码提出一些更改,以使其返回子集而不是子集计数吗?

5 个答案:

答案 0 :(得分:1)

这是有效的代码:

import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;

public class subset{
    public static int count = 0;
    public static List list = new LinkedList();
    public static void countSubsetSum2(int arr[], int k, int sum) {
        if(sum <= 0 || k < 0) {
            count++;
            return;
        }
        if(sum == arr[k]) {
            System.out.print(arr[k]);
            for(Iterator i = list.iterator(); i.hasNext();)
                System.out.print("\t" + i.next());
            System.out.println();
        }
        list.add(arr[k]);
        countSubsetSum2(arr, k-1, sum - arr[k]);
        list.remove(list.size() - 1);
        countSubsetSum2(arr, k-1, sum);
    }

    public static void main(String[] args)
    {
        int [] array = {1, 4, 5, 6};
        countSubsetSum2(array, 3, 10);
    }
}

答案 1 :(得分:1)

首先,你在那里的代码似乎没有实际工作(我在输入[1,2,3, ..., 10]上测试了它的总和为3并输出128)。

要使其正常工作,请首先注意您以非常不正统的方式实现了算法。数学函数接受输入并产生输出。 (可以说)最优雅的编程函数也应该接受输入并产生输出,因为我们可以在推理数学时推理它们。

在您的情况下,您不生成任何输出(返回类型为void),而是将结果存储在静态变量中。这意味着很难确切地说出意味着什么来调用countSubsetSum2。特别是,如果多次调用它会发生什么?它每次不同(因为count变量将具有不同的起始值!)相反,如果您编写countSubsetSum2以便它返回一个值,那么您可以< em>将其行为定义为:countSubsetSum2返回总和为arr[0...k]的输入sum的子集数。然后,您可以尝试证明您的实现符合该规范的原因。

我没有做最好的解释工作,但我认为写一个更自然的方式是:

// Algorithm stops once k is the least element in the array
if (k == 0) {
    if (sum == 0 || sum == arr[k]) {
        // Either we can sum to "sum"
        return 1;
    }
    else {
        // Or we can't sum to "sum"
        return 0;
    }   
}   

// Otherwise, let's recursively see if we can sum to "sum"

// Any valid subset either includes arr[k]
return countSubsetSum2(arr, k-1, sum - arr[k]) +
// Or it doesn't
countSubsetSum2(arr, k-1, sum);

如上所述,此函数接受输入并输出一个我们可以定义并且证明的值在数学上是真的(警告:它通常不是一个证据,因为不幸的是,在大多数编程语言中都存在疯狂的边缘情况。)

无论如何,回到你的问题。上面代码的问题是它不存储任何数据......它只返回计数。相反,让我们在生成它们的同时生成实际的子集。特别是,当我说Any valid subset either includes arr[k]时,我的意思是......我们生成的子集包括arr[k];所以加上它。下面我假设你上面写的代码是java-ish。希望这是有道理的:

// Algorithm stops once k is the least element in the array
if (k == 0) {
    if (sum == 0 || sum == arr[k]) {
        // Either we can sum to "sum" using just arr[0]
        // So return a list of all of the subsets that sum to "sum"
        // There are actually a few edge cases here, so we need to be careful
        List<Set<int>> ret = new List<Set<int>>();

        // First consider if the singleton containing arr[k] could equal sum 
        if (sum == arr[k])
        {   
            Set<int> subSet = new Subset<int>();
            subSet.Add(arr[k]);
            ret.Add(subSet);
        }   

        // Now consider the empty set 
        if (sum == 0)
        {   
            Set<int> subSet = new Subset<int>();
            ret.Add(subSet);
        }   

        return ret;
    }   
    else {
        // Or we can't sum to "sum" using just arr[0]
        // So return a list of all of the subsets that sum to "sum". None 
        // (given our inputs!)
        List<Set<int>> ret = new List<Set<int>>();
        return ret;
    }
}

// Otherwise, let's recursively generate subsets summing to "sum"

// Any valid subset either includes arr[k]
List<Set<int>> subsetsThatNeedKthElement = genSubsetSum(arr, k-1, sum - arr[k]);
// Or it doesn't
List<Set<int>> completeSubsets = genSubsetSum(arr, k-1, sum);

// Note that subsetsThatNeedKthElement only sum to "sum" - arr[k]... so we need to add
// arr[k] to each of those subsets to create subsets which sum to "sum"
// On the other hand, completeSubsets contains subsets which already sum to "sum"
// so they're "complete"

// Initialize it with the completed subsets
List<Set<int>> ret = new List<Set<int>>(completeSubsets);
// Now augment the incomplete subsets and add them to the final list
foreach (Set<int> subset in subsetsThatNeedKthElement)
{
    subset.Add(arr[k]);
    ret.Add(subset);
}

return ret;

所有评论的代码都很混乱;但关键是这个实现总是返回它指定返回的内容(从arr [0]到arr [k]的一组int的列表,它们总和为传入的总和)。

仅供参考,还有另一种方法是“自下而上”(即不使用递归),这种方法应该更具性能。如果你以这种方式实现它,那么你需要在静态状态下存储额外的数据(“memoized table”)......这有点难看但实用。但是,当您以这种方式实现它时,您需要有一种更聪明的方法来生成子集。试一试之后,请在另外的帖子中随意提出这个问题。

答案 2 :(得分:1)

首先,您的代码不正确。

该函数在每一步都使用除去并包括当前元素 1 的和来递归,继续下一个元素,这要归功于以下几行:

countSubsetSum2(arr, k-1, sum - arr[k-1]);
countSubsetSum2(arr, k-1, sum);

但是还有这个:

if(sum < arr[k - 1]) {
    countSubsetSum2(arr, k-1, sum);
}

导致它在某些情况下(不应该做的)除去当前元素之前的两次递归两次。

基本上你只需要删除那个if语句。

如果所有元素都是正数且sum - arr[k-1] < 0,我们会继续前进,但由于总和不能增加,我们永远不会得到0的总和,因此我们会做很多不必要的工作。因此,如果元素都是正数,我们可以在第一次调用时添加if(arr[k - 1] <= sum)检查以改善运行时间。如果元素不是全部正数,则代码将找不到所有总和。

现在打印总和

如果你能很好地理解代码,那么改变它来打印总和应该很容易。我建议你更多地了解它 - 跟踪程序将手动执行的操作,然后跟踪你想要程序要执行的操作。

提示解决实际问题:注意countSubsetSum2(arr, k-1, sum - arr[k-1]);使用包含当前元素的总和(以及除了当前元素之外的其他递归调用recurses)进行递归,你应该做什么清楚。


1:嗯,从技术上来说它是相反的(我们从目标总和开始减少到0而不是从0开始并增加到总和),但同样的想法是存在的。

答案 3 :(得分:0)

基于此处的评论/建议,我已经能够以这种方式获得此问题的解决方案:

public static int counter = 0;
public static List<List<Integer>> lists = new ArrayList<>();
public static void getSubsetCountThatSumToTargetValue(int[] arr, int k, int targetSum, List<Integer> list) {
    if(targetSum == 0) {
        counter++;
        lists.add(list);
        return;
    }

    if(k <= 0) {
        return;
    }

    getSubsetCountThatSumToTargetValue(arr, k - 1, targetSum, list);

    List<Integer> appendedlist = new ArrayList<>();
    appendedlist.addAll(list);
    appendedlist.add(arr[k - 1]);
    getSubsetCountThatSumToTargetValue(arr, k - 1, targetSum - arr[k - 1], appendedlist);
}

主要方法如下:

public static void main(String[] args) {

    int[] arr = {1, 2, 3, 4, 5};
    SubSetSum.getSubsetCountThatSumToTargetValue(arr, 5, 9, new ArrayList<Integer>());
    System.out.println("Result count: " + counter);
    System.out.println("lists: " + lists);

}

输出:

Result: 3
lists: [[4, 3, 2], [5, 3, 1], [5, 4]]

答案 4 :(得分:0)

一个Python实现,其中k从0移动到len() - 1:

def sum_of_subsets( numbers, sum_original ):

  def _sum_of_subsets( list, k, sum ):
    if sum < 0 or k == len( numbers ):
      return

    if ( sum == numbers[ k ] ):
      expression = reduce( lambda result, num: str( num ) if len( result ) == 0 else \
                                                          "%s + %d" % ( result, num ),
                           sorted( list + [ numbers[ k ]] ),
                           '' )
      print "%d = %s" % ( sum_original, expression )
      return

    list.append( numbers[ k ] )
    _sum_of_subsets( list, k + 1, sum - numbers[ k ])

    list.pop( -1 )
    _sum_of_subsets( list, k + 1, sum )

  _sum_of_subsets( [], 0, sum_original )

...

sum_of_subsets( [ 8, 6, 3, 4, 2, 5, 7, 1, 9, 11, 10, 13, 12, 14, 15 ], 15 )

...

15 = 1 + 6 + 8
15 = 3 + 4 + 8
15 = 1 + 2 + 4 + 8
15 = 2 + 5 + 8
15 = 7 + 8
15 = 2 + 3 + 4 + 6
15 = 1 + 3 + 5 + 6
15 = 4 + 5 + 6
15 = 2 + 6 + 7
15 = 6 + 9
15 = 1 + 2 + 3 + 4 + 5
15 = 1 + 3 + 4 + 7
15 = 1 + 2 + 3 + 9
15 = 2 + 3 + 10
15 = 3 + 5 + 7
15 = 1 + 3 + 11
15 = 3 + 12
15 = 2 + 4 + 9
15 = 1 + 4 + 10
15 = 4 + 11
15 = 1 + 2 + 5 + 7
15 = 1 + 2 + 12
15 = 2 + 13
15 = 1 + 5 + 9
15 = 5 + 10
15 = 1 + 14
15 = 15