максимальная сумма подмножества размера K с суммой меньше M

Дано: массив целых значений K, M

Вопрос: Найдите максимальную сумму, которую мы можем получить из всех подмножеств K элементов данного массива, такая, что сумма меньше значения M?

Есть ли решение этой проблемы без динамического программирования? или если это только dp [i] [j] [k], может решить только этот тип проблемы! не могли бы вы объяснить алгоритм.


person Shivendra    schedule 05.08.2013    source источник
comment
Я не могу понять ваш вопрос. Не могли бы вы привести здесь один пример. Вот что я понимаю: [3,5,2,6,1,8,15,18,4], если K равно 3, M равно 15, тогда я могу выбрать 8,2,4, это то, каким должен быть ответ?   -  person AKS    schedule 06.08.2013
comment
@AKS вопрос ... мы можем сделать только K подмножеств длины ... и из этих подмножеств длины K, какое подмножество дает максимальную сумму ... при условии, что сумма меньше M   -  person Shivendra    schedule 06.08.2013
comment
хорошо, так что согласно приведенному мной примеру, если k равно 3, то из всех подмножеств длины 3, которые имеют максимальную сумму, но меньше 15. Спасибо !!   -  person AKS    schedule 06.08.2013
comment
Эта проблема обсуждается здесь cs. dartmouth.edu/~ac/Teach/CS105-Winter05/Notes/   -  person darksky    schedule 06.08.2013
comment
@darksky, я не думаю, что это то, что я спросил. Это вариант того же, но не совсем то, о чем просили.   -  person Shivendra    schedule 07.08.2013


Ответы (3)


Многие люди правильно прокомментировали, что приведенный ниже ответ, сделанный несколько лет назад, который использует динамическое программирование, неправильно кодирует решения, позволяющие элементу массива появляться в «подмножестве» несколько раз. К счастью, все еще есть надежда на подход, основанный на DP.

Пусть dp[i][j][k] = true, если существует подмножество размера k первых i элементов входного массива, суммирующего до j

Наш базовый случай - dp[0][0][0] = true

Теперь либо размер k подмножества первых i элементов использует a[i + 1], либо нет, что дает повторение

dp[i + 1][j][k] = dp[i][j - a[i + 1]][k - 1] OR dp[i][j][k]

Сложите все вместе:

given A[1...N]
initialize dp[0...N][0...M][0...K] to false
dp[0][0][0] = true
for i = 0 to N - 1:
    for j = 0 to M:
        for k = 0 to K:
            if dp[i][j][k]:
                dp[i + 1][j][k] = true
            if j >= A[i] and k >= 1 and dp[i][j - A[i + 1]][k - 1]:
                dp[i + 1][j][k] = true
max_sum = 0
for j = 0 to M:
    if dp[N][j][K]:
        max_sum = j
return max_sum

придавая O(NMK) временную и пространственную сложность.

Возвращаясь назад, мы сделали одно предположение здесь неявно, а именно, что A[1...i] все неотрицательны. При отрицательных числах инициализация второго измерения 0...M неверна. Рассмотрим подмножество размера K, состоящее из подмножества размера K - 1 с суммой, превышающей M, и еще одного достаточно отрицательного элемента A[], так что общая сумма больше не превышает M. Точно так же наше подмножество размера K - 1 можно суммировать до некоторого крайне отрицательного числа, а затем с достаточно положительным элементом A[] суммировать до M. Чтобы наш алгоритм работал в обоих случаях, нам нужно увеличить второе измерение с M до разницы между суммой всех положительных элементов в A[] и суммой всех отрицательных элементов (сумма абсолютных значений всех элементов в A[]).

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

Сверх того? Что ж, ваша проблема тесно связана с суммой подмножества, и литература по широко известным неполным задачам NP довольно обширна. И, как общий принцип, алгоритмы могут быть всех форм и размеров - для меня вполне возможно представить себе, например, рандомизацию, аппроксимацию (просто выберите параметр ошибки достаточно малым!), Простое старое сокращение других полных проблем NP ( преобразуйте вашу проблему в гигантскую логическую схему и запустите решатель SAT). Да это разные алгоритмы. Они быстрее, чем решение для динамического программирования? Наверное, некоторые из них. Являются ли они такими же простыми для понимания или реализации, не говоря уже об обучении, помимо стандартного материала по введению в алгоритмы? Возможно нет.

Это вариант задачи о ранце или подмножестве, где с точки зрения времени (за счет экспоненциального роста требований к пространству по мере увеличения размера ввода) динамическое программирование является наиболее эффективным методом, который ПРАВИЛЬНО решает эту проблему. См. Этот вариант проще решить проблему с суммой подмножества? на вопрос, аналогичный вашему.

Однако, поскольку ваша проблема не совсем такая, я все равно дам объяснение. Пусть dp[i][j] = true, если есть подмножество длины i, которое суммируется до j и false, если его нет. Идея состоит в том, что dp[][] будет кодировать суммы всех возможных подмножеств для каждой возможной длины. Затем мы можем просто найти самый большой j <= M, такой, что dp[K][j] будет true. Наш базовый случай dp[0][0] = true, потому что мы всегда можем создать подмножество, которое суммируется до 0, выбрав одно из размера 0.

Повторение также довольно простое. Предположим, мы вычислили значения dp[][], используя первые n значений массива. Чтобы найти все возможные подмножества первых n+1 значений массива, мы можем просто взять n+1_-е значение и добавить его ко всем подмножествам, которые мы видели раньше. Более конкретно, у нас есть следующий код:

initialize dp[0..K][0..M] to false
dp[0][0] = true
for i = 0 to N:
    for s = 0 to K - 1:
        for j = M to 0:
            if dp[s][j] && A[i] + j < M:
                dp[s + 1][j + A[i]] = true
for j = M to 0:
    if dp[K][j]:
        print j
        break

person weeb    schedule 06.08.2013
comment
ваше решение правильное. незначительная ошибка: если dp [s] [j] && A [i] + j ›= M должно быть, если dp [s] [j] && A [i] + j‹ M - person songlj; 07.08.2013
comment
Что в этом растворе означает А? Не упоминается нигде в описании. - person iftheshoefritz; 24.11.2018
comment
Это предполагает, что вы можете повторять элементы A в одном и том же подмножестве, что не соответствует определению подмножества, т.е. (1, 1) не является подмножеством (1, 2, 3). - person andresp; 19.11.2019
comment
Действительно, @andresp прав. Предлагаемое решение неверно. Вам необходимо отслеживать, какие элементы вы использовали в подмножествах. - person Jimmy; 11.02.2020
comment
Вы, ребята, правы, решение позволяет использовать предмет более одного раза. Я собираюсь записать этот ответ - person weeb; 22.02.2020

Мы ищем подмножество K элементов, для которых сумма элементов максимальна, но меньше M.

Мы можем разместить границы [X, Y] на самом большом элементе в подмножестве следующим образом.

Сначала мы сортируем (N) целых чисел values[0] ... values[N-1], причем элемент values[0] является наименьшим.

Нижняя граница X - это наибольшее целое число, для которого

values[X] + values[X-1] + .... + values[X-(K-1)] < M.

(Если X равно N-1, значит, мы нашли ответ.)

Верхняя граница Y - это наибольшее целое число меньше N, для которого

values[0] + values[1] + ... + values[K-2] + values[Y] < M.

С помощью этого наблюдения мы можем теперь связать второй по величине член для каждого значения самого высокого члена Z, где

X <= Z <= Y.

Мы можем использовать точно такой же метод, поскольку форма задачи точно такая же. Уменьшенная проблема заключается в поиске подмножества K-1 элементов, взятых из values[0] ... values[Z-1], для которого сумма элементов является максимальной, но меньше M - values[Z].

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

Это дает нам древовидную структуру для поиска с гораздо меньшим количеством комбинаций для поиска, чем N выбирает K.

person John    schedule 05.08.2013
comment
удовлетворяет ли это потребность в том, что это подмножество! Я думаю, что решение такое, как если бы это подмассив .. подмножество может быть любой подпоследовательностью! Я попробую ваше решение, хотя .. - person Shivendra; 06.08.2013
comment
Да, это действительно подмножество. Я заказал элементы так, чтобы я мог установить границы для значения самого большого элемента в этом подмножестве, которое удовлетворяет вашему условию. Нет необходимости использовать в качестве наибольшего значения любые значения ниже values[X], потому что максимальная сумма всех подмножеств с K элементами по-прежнему меньше M. Точно так же нет необходимости проверять какие-либо значения, превышающие values[Y], потому что минимальная сумма всех этих подмножеств с элементами K уже больше, чем M. Понимаете, что я имею в виду? - person John; 06.08.2013

Феликс прав, что это частный случай задачи о рюкзаке. Его алгоритм динамического программирования занимает O (K * M) размер и O (K * K * M) количество времени. Я считаю, что его использование переменной N действительно должно быть K.

Задаче о рюкзаке посвящены две книги. Последний, Келлерер, Пферши и Писингер [2004, Springer-Verlag, ISBN 3-540-40286-1], дает улучшенный алгоритм динамического программирования на своей странице 76, рисунок 4.2, который занимает O ( K + M) пространство и O (KM) время, что является огромным сокращением по сравнению с алгоритмом динамического программирования, данным Феликсом. Обратите внимание на опечатку в последней строке алгоритма книги, где должно быть c-bar: = c-bar - w_ (r (c-bar)).

Моя реализация на C # ниже. Я не могу сказать, что я тщательно его тестировал, и я приветствую отзывы по этому поводу. Я использовал BitArray для реализации концепции наборов, представленных в алгоритме в книге. В моем коде c - это емкость (которая в исходном посте называлась M), и я использовал w вместо A в качестве массива, содержащего веса.

Пример его использования:

int[] optimal_indexes_for_ssp = new SubsetSumProblem(12, new List<int> { 1, 3, 5, 6 }).SolveSubsetSumProblem();

где массив optimal_indexes_for_ssp содержит [0,2,3], соответствующие элементам 1, 5, 6.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;

public class SubsetSumProblem
{
    private int[] w;
    private int c;

    public SubsetSumProblem(int c, IEnumerable<int> w)
    {
      if (c < 0) throw new ArgumentOutOfRangeException("Capacity for subset sum problem must be at least 0, but input was: " + c.ToString());
      int n = w.Count();
      this.w = new int[n];
      this.c = c;
      IEnumerator<int> pwi = w.GetEnumerator();
      pwi.MoveNext();
      for (int i = 0; i < n; i++, pwi.MoveNext())
        this.w[i] = pwi.Current;
    }

    public int[] SolveSubsetSumProblem()
    {
      int n = w.Length;
      int[] r = new int[c+1];
      BitArray R = new BitArray(c+1);
      R[0] = true;
      BitArray Rp = new BitArray(c+1);
      for (int d =0; d<=c ; d++) r[d] = 0;
      for (int j = 0; j < n; j++)
      {
        Rp.SetAll(false);
        for (int k = 0; k <= c; k++)
          if (R[k] && k + w[j] <= c) Rp[k + w[j]] = true;
        for (int k = w[j]; k <= c; k++) // since Rp[k]=false for k<w[j]
          if (Rp[k])
          {
            if (!R[k]) r[k] = j;
            R[k] = true;
          }
      }
      int capacity_used= 0;
      for(int d=c; d>=0; d--)
        if (R[d])
        {
          capacity_used = d;
          break;
        }
      List<int> result = new List<int>();
      while (capacity_used > 0)
      {
        result.Add(r[capacity_used]);
        capacity_used -= w[r[capacity_used]];
      } ;
      if (capacity_used < 0) throw new Exception("Subset sum program has an internal logic error");
      return result.ToArray();
    }
}
person Marc Meketon    schedule 30.08.2013