Найдите наименьшую сумму подмножества, соответствующую другой сумме подмножества

У меня есть реальная проблема (не домашнее задание!), Которая требует нахождения суммы подмножества набора A, которая равна сумме подмножества некоторого другого набора B.

Очень похожий вопрос с полезным ответом здесь.

Рассмотрим этот пример:

@a = qw(200 2000 2000 2000 4000);
@b = qw(528 565 800 1435 2000 2000 2872);

Используя код в принятом ответе на этот вопрос, я получаю следующий результат:

sum(200 2000 4000) = sum(528 800 2000 2872)
sum(200 4000 4000) = sum(528 800 2000 2000 2872)
sum(200 4000) = sum(528 800 2872)
sum(200 2000 2000 2000 4000) = sum(528 800 2000 2000 2000 2872)

Для моих целей мне нужны только ответы, в которых используется наименьшее количество элементов входных наборов. В этом примере я просто хочу

sum(200 4000) = sum(528 800 2872)

потому что во всех остальных ответах также есть 200 и 4000 в сумме. То есть я ищу только «самые простые» возможные суммы (в том смысле, что они используют наименьшее количество элементов). Может ли кто-нибудь предложить достаточно эффективный способ сделать это? (Грубая сила - это нормально, поскольку мои массивы не такие большие.)

Также я должен отметить, что вторая строка вывода, sum(200 4000 4000) ..., неверна, потому что 4000 появляется только один раз в @a. Боюсь, я недостаточно хорошо понимаю алгоритм, чтобы понять, почему это происходит.

Любая помощь с любым из них будет очень признательна!


person itzy    schedule 22.06.2011    source источник
comment
? Почему sum(2000) = sum(2000) неправильный ответ? Почему sum(4000)=sum(2000 2000) неправильный ответ?   -  person mob    schedule 22.06.2011
comment
@mob - вы правы, так и должно быть. Алгоритм, приведенный в другом вопросе, кажется не совсем правильным. (Возможно, это было правильным для этого вопроса, но я не вижу разницы.) Ваши два ответа также будут правильными, и я бы хотел, чтобы алгоритм их доставил. Спасибо.   -  person itzy    schedule 22.06.2011
comment
@itzy: Спасибо, что снова заметили проблемы с моим кодом! Надеюсь, ваша проблема была решена к вашему удовлетворению. Динамическое программирование, подобное тому, что было в моем ответе (кроме правильного ...), должно было решить вашу проблему. Заботиться!   -  person A. Rex    schedule 21.08.2011


Ответы (4)


Эта проблема является NP-полной, поэтому, если P = NP, вы застряли, выполняя экспоненциальную работу над размером ввода. Самое интересное в этом типе проблемы состоит в том, что на самом деле существует два способа решения, которые позволяют оценить различные аспекты проблемы.

Во-первых, если в ваших суммах не слишком много элементов, вы можете просто перебрать эту проблему, перебирая все комбинации. Этот метод экспоненциально зависит от количества элементов в наборах и будет работать достаточно хорошо, скажем, примерно до 20 элементов на контейнер. После этого будет довольно неприятно.

Второй вариант - использовать динамическое программирование. В отличие от предыдущего метода, этот алгоритм экспоненциально зависит от числа битов, необходимых для записи каждого числа. Что вы делаете, так это отслеживаете набор всех возможных сумм, а также минимальное количество элементов, необходимых для их генерации. Это простая модификация решения, на которое вы указали в своем ответе.

Вот код Python, который генерирует все возможные значения, в которых они могут пересекаться:

    def gen_sum(a):
        r = { 0: [] }
        for x in a:
            for y, v in r.items():
                if not (x+y) in r or len(r[x+y]) > len(v) + 1:
                    r[x+y] = v + [x]
        return r

    def gen_sums(a, b):
        asum = gen_sum(a)
        bsum = gen_sum(b)
        return [ (k, v, bsum[k]) for k,v in asum.items() if k in bsum ][1:]

Запустив его на ваших демонстрационных данных, я получил:

[(4000, [4000], [2000, 2000]), (6000, [2000, 4000], [565, 1435, 2000, 2000]), (2000, [2000], [2000]), (4200, [200, 4000], [528, 800, 2872]), (10200, [200, 2000, 2000, 2000, 4000], [528, 565, 800, 1435, 2000, 2000, 2872]), (8200, [200, 2000, 2000, 4000], [528, 800, 2000, 2000, 2872]), (6200, [200, 2000, 4000], [528, 800, 2000, 2872])]

РЕДАКТИРОВАТЬ: исправлена ​​опечатка, а также просто замечено, что огромное количество людей уже ответили на это очень быстро.

person Mikola    schedule 22.06.2011
comment
Вы можете сохранить только минимум, вам не нужны все наборы - только минимум. В ведре k вам не нужно знать фактические значения элементов в наборе, это причина, по которой алгоритм псевдо-поли работает вообще. В худшем случае сохранение всех наборов может привести к экспоненциальному увеличению времени и пространства. - person dfb; 22.06.2011
comment
Точнее говоря, сохраняя все наборы, это не псевдополигон в худшем случае, если вы просто соблюдаете минимум, он все равно псевдополигон. - person dfb; 22.06.2011
comment
@spinning_plate: Как ты считаешь? При этом используется только пространство O (n * (| a | + | b |)), где n - размер максимально возможной суммы, а | a |, | b | - длины a и b соответственно. Это все еще должно быть псевдополиномиальным от n. Что касается временной сложности, он выполняет итерацию по массиву один раз для каждого элемента, поэтому временная сложность не должна быть больше, чем O (n * (| a | + | b |) ^ 2), что снова является псевдо-поли. - person Mikola; 22.06.2011
comment
И в качестве дополнения, если вы внимательно посмотрите на него, вы увидите, что он отслеживает только минимальный набор, поэтому я снова не совсем уверен, что вы имеете в виду. - person Mikola; 22.06.2011
comment
Спасибо за код. Мой Python даже хуже, чем мой Perl, но я попытаюсь перенести его на Perl. Если это будет для вас легко, я буду очень признателен за помощь! Но несмотря ни на что, большое спасибо. - person itzy; 22.06.2011
comment
@Mikola - Похоже, ваш вывод отсутствует (2000, [2000], [565, 1435]). Есть идеи, почему? Спасибо. - person itzy; 23.06.2011
comment
@itzy: Решение для 2000 есть, за исключением того, что правильный ответ должен быть (2000, [2000], [2000]), поскольку набор [2000] имеет меньше элементов, чем [565, 1435]. - person Mikola; 23.06.2011

Вот обновленный алгоритм, который дает все суммы:

my @a = qw(200 2000 2000 2000 4000);
my @b = qw(528 565 800 1435 2000 2000 2872);

my %a = sums( @a );
my %b = sums( @b );

for my $m ( keys %a ) {
    print "sum(@{$a{$m}}) = sum(@{$b{$m}})\n" if exists $b{$m};
}

sub sums {
    my( @a ) = @_;

    return () unless @a;

    my %a;

    while( @a ) {
        $a = shift @a;

        for my $m ( keys %a ) {
            $a{$m+$a} = [@{$a{$m}},$a];
        }

        $a{$a} = [$a];
    }

    return %a;
}

Все, что вам нужно сделать, это найти самый короткий (-ые), но другие уже покрыли это :)

HTH,

Павел

person pavel    schedule 22.06.2011
comment
Спасибо за помощь. Похоже, что этот код все еще дает сумму (200 4000 4000) в качестве одного из ответов, но 4000 появляется во входном наборе только один раз. 2000 + 2000 как-то сохраняются как 4000? - person itzy; 22.06.2011
comment
@irzy: ты прав; это потому, что в списке есть 2 x 2000 ... Пока не знаю, как с этим справиться :( - person pavel; 22.06.2011

Вам нужно изменить отношение повторения, а не только вывод. Рассмотрим {1,2,20,23,42} и {45}. Исходный алгоритм выведет {1,2,42}, {45} вместо {20,23}, {45}. Это связано с тем, что 42 считается последним, и когда он суммируется до 45, он перезапишет значение сегмента 45, ранее содержащее {20,23}

Вместо того, чтобы сохранять [набор, сумма] для каждого значения, вам нужно сохранить [минимальный набор, сумма], а затем взять минимальные значения в конце.

У меня ужасный perl, но что-то вроде этого

$a{$m+$a} = [min-length(@{$a{$m}},$a{$m+$a}[0]),$a];

где min-length возвращает меньший набор

person dfb    schedule 22.06.2011
comment
Вы имели в виду {1,2,20,23,42} и {43}? - person itzy; 22.06.2011

Я не очень разбираюсь в Perl. Но,

for $m ( keys %a ) {
print "sum(@{$a{$m}}) = sum(@{$b{$m}})\n" if exists $b{$m};

}

Измените эту строку, чтобы подсчитать количество элементов в наборе $ a и $ b для каждого $ m. Когда вы закончите перебирать все из них, выберите тот, у которого наименьшее количество элементов.

person Localghost    schedule 22.06.2011