найти все подмножества, сумма которых равна x, используя начальный код

Я пытаюсь решить проблему, чтобы решить другую аналогичную проблему... ниже приведен код для нахождения общего количества подмножеств, сумма которых равна определенному значению, и я пытаюсь изменить код, чтобы я мог вернуть все подмножества, сумма которых равна этому значению (вместо нахождения количества).

Код для нахождения общего количества подмножеств, сумма которых равна «сумме»:

 /**
 * 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);
}

Может ли кто-нибудь предложить некоторые изменения в этом коде, чтобы он возвращал подмножества, а не количество подмножеств?


person Darth.Vader    schedule 04.09.2013    source источник


Ответы (5)


Во-первых, ваш код неверен.

Функция на каждом шаге выполняет рекурсию с суммой, исключая и включая текущий элемент 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]); рекурсивно с суммой, включающей текущий элемент (а другой рекурсивный вызов рекурсивно с суммой, исключающей текущий элемент), вам должно стать ясно, что вы должны делать.


1: Ну, технически это наоборот (мы начинаем с целевой суммы и уменьшаем до 0 вместо того, чтобы начинать с 0 и увеличивать до суммы), но та же самая идея.

person Bernhard Barker    schedule 05.09.2013
comment
Спасибо, Дуклинг. Ваши комментарии всегда очень полезны. - person Darth.Vader; 07.09.2013
comment
Я ответил на вопрос, основанный на вашем отзыве. пожалуйста, дайте мне знать, если есть какие-либо улучшения, которые вы хотели бы предложить (если таковые имеются). Спасибо! - person Darth.Vader; 07.09.2013

Это код, который работает:

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);
    }
}
person jfly    schedule 05.09.2013

Во-первых, код, который у вас там, похоже, на самом деле не работает (я тестировал его на входе [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], сумма которых равна любой переданной сумме).

К вашему сведению, есть еще один подход "снизу вверх" (т.е. не использующий рекурсию), который должен быть более производительным. Если вы реализуете это таким образом, вам нужно хранить дополнительные данные в статическом состоянии («запоминаемая таблица»)... что немного некрасиво, но практично. Однако, когда вы реализуете это таким образом, вам нужен более умный способ создания подмножеств. Не стесняйтесь задавать этот вопрос в отдельном посте после того, как попробуете.

person rliu    schedule 05.09.2013

Основываясь на комментариях/предложениях здесь, я смог получить решение этой проблемы следующим образом:

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]]
person Darth.Vader    schedule 07.09.2013

Реализация Python с перемещением k от 0 до len() - 1:

import functools
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 = functools.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
person Evgeny Goldin    schedule 06.07.2014