Подмножество, сумма которого больше, но меньше определенного значения

У меня есть массив с положительными значениями. Например, для array= {5,4,4,3.8,2,1.7} мне нужно найти подмассив, сумма которого больше, но меньше 12 . В этом случае это будет {4,4,3.8}

другой ex массив {7,4,3,2} В этом случае максимальная сумма равна 12, а подмножество {7,3,2}

каков будет алгоритм для этого, так как у меня очень большой массив, длина которого превышает 1000. Я пишу эту программу в VBA excel.

Спасибо.


person Abhishek    schedule 02.09.2016    source источник
comment
Пожалуйста, покажите попытку, которую вы сделали.   -  person Scott Craner    schedule 02.09.2016
comment
1000 не так уж и много. Если длина вашего массива ненамного больше 1000, вы можете позволить себе квадратичную сложность - и тогда не очень сложно рассмотреть сумму каждого подмассива.   -  person md5    schedule 02.09.2016
comment
В {5,4,4,3.8,2,1.7} нет контрпримера, но под массивом вы подразумеваете набор (т.е. любую группу чисел) или последовательность.   -  person Comintern    schedule 02.09.2016
comment
Это NP-полная задача. См. en.wikipedia.org/wiki/Knapsack_problem и en.wikipedia.org/wiki/Subset_sum_problem   -  person yogsototh    schedule 02.09.2016
comment
@yogsothoth Вовсе нет. Наивный алгоритм явно может найти решение за квадратичное время. Проблемы, на которые вы ссылаетесь, не связаны (или имеют гораздо более общий характер).   -  person Nelfeal    schedule 02.09.2016
comment
@Nelxiost Это точно так же, как те проблемы, если OP означает подмножества вместо подпоследовательностей. Если он имеет в виду подпоследовательности, то квадратичное решение методом грубой силы действительно возможно. Вот почему Коминтерн спросил.   -  person Mikegrann    schedule 02.09.2016
comment
@Mikegrann Верно. Но подмассив обычно означает подпоследовательность (смежную). Опять же, вопрос, вероятно, должен указать это и показать лучший пример.   -  person Nelfeal    schedule 02.09.2016
comment
@Comintem yes set (т.е. любая группа и подмассив любой длины)   -  person Abhishek    schedule 02.09.2016
comment
Если вы действительно имеете в виду набор, а не подмассив (и ваш последний комментарий только усилил двусмысленность), это явно невозможно, поскольку это NP-полная проблема дискретной оптимизации с пространством поиска более 2 ^ 1000 элементов. Лучше всего было бы использовать алгоритм ветвей и границ, который реализован в решателе (если вы ограничиваете некоторые переменные двоичными или целыми числами и указываете линейный). К сожалению, бесплатная версия решателя, поставляемая с Excel, не может обрабатывать более 1000 двоичных переменных. Вы можете приобрести обновление до премиального решателя или найти альтернативу с открытым исходным кодом.   -  person John Coleman    schedule 25.09.2016


Ответы (2)


попробуйте этот алгоритм для подпоследовательности

Sub aargh()
Dim a As Variant
Dim limsum As Double, highestnum As Double
Dim i As Integer, j As Integer
    a = Array(5, 4, 4, 3.8, 2, 1.7)
    limsum = 12

    highestsum = 0
    For i = 0 To UBound(a)
        s = a(i)
        j = i
        Do
            If s > highestsum Then fs = i: ls = j: highestsum = s
            j = j + 1
            If j > UBound(a) Then Exit Do
            s = s + a(j)
        Loop While s <= limsum
    Next i
    MsgBox "subarray (" & fs & "," & ls & ") sum = " & highestsum
End Sub

отредактировано, чтобы включить примечания ниже и включить решение для подмножества

Sub aargh()

    Dim sol(), csol()
    a = Array(7, 4, 3, 2)

    ReDim sol(LBound(a) To UBound(a))
    ReDim csol(LBound(a) To UBound(a))
    limsum = 13
    findsum a, sol, csol, maxsum, limsum
    ss = "array "
    For i = 1 To sol(0)
        ss = ss & sep & a(sol(i))
        sep = ","
    Next i
    MsgBox ss & " sum =" & maxsum
End Sub
Sub findsum(ByRef a, ByRef sol, ByRef csol, ByRef maxsum, ByRef limsum, Optional s = 0, Optional lvl = 1, Optional si = 0)
' recursive sub
    For i = si To UBound(a)
        s = s + a(i)
        csol(lvl) = i ' current solution contains number i
        If s <= limsum Then
            If s > maxsum Then ' we found a sum greater than current max we save it
                maxsum = s
                sol(0) = lvl
                For j = 1 To lvl
                    sol(j) = csol(j)
                Next j
            End If
            If i < UBound(a) Then ' pick another number
                findsum a, sol, csol, maxsum, limsum, s, lvl + 1, i + 1
            End If
        End If
        s = s - a(i)
    Next i
End Sub

код оптимизирован, если массив отсортирован (по убыванию)

Sub aargh()

    Dim sol(), csol()
    a = Array(20, 15, 10, 7, 6, 5, 4, 3, 2)

    ReDim sol(LBound(a) To UBound(a))
    ReDim csol(LBound(a) To UBound(a))
    limsum = 13
    findsum a, sol, csol, maxsum, limsum, UBound(a)
    ss = "array "
    For i = 1 To sol(0)
        ss = ss & sep & a(sol(i))
        sep = ","
    Next i
    MsgBox ss & " sum =" & maxsum
End Sub
Sub findsum(ByRef a, ByRef sol, ByRef csol, ByRef maxsum, ByRef limsum, si, Optional s = 0, Optional lvl = 1)
' recursive sub
    For i = si To LBound(a) Step -1
        If s + a(i) > limsum Then Exit For
        s = s + a(i)
        csol(lvl) = i    ' current solution contains number i
        If s <= limsum Then
            If s > maxsum Then    ' we found a sum greater than current max we save it
                maxsum = s
                sol(0) = lvl
                For j = 1 To lvl
                    sol(j) = csol(j)
                Next j
            End If
            If i > LBound(a) Then    ' pick another number
                findsum a, sol, csol, maxsum, limsum, i - 1, s, lvl + 1
            End If
        End If
        s = s - a(i)
        If maxsum = limsum Then Exit For 'exit if exact match
    Next i
End Sub
person h2so4    schedule 02.09.2016
comment
Вы, вероятно, хотите For i = 0 To UBound(a). В противном случае вы не позволите подпоследовательности начинаться (и заканчиваться) на последнем элементе. Так, например, если ваши настройки были a = Array(5, 4, 4, 3.8, 2, 13.9) и limsum = 14, вы хотели бы вернуть подпоследовательность (5,5) с суммой 13,9 вместо подпоследовательности (1,4) с суммой 13,8. В противном случае хорошее решение грубой силы. - person Mikegrann; 02.09.2016
comment
Я проверил ваш код, но он не дает правильного результата для этого массива (7, 4, 3, 2) максимальная сумма должна быть 12 с подмножеством (7,3,2), но его сумма = 11 - person Abhishek; 02.09.2016
comment
@Abhishek Вы редактировали значение limsum? Это число, ограничивающее сумму. По какой-то причине h2so4 установил его на 13 вместо 12. Если вы измените его на 12, вы обнаружите, что он отлично работает для вашего примера. Однако он работает с подпоследовательностями, а не с подмножествами (поскольку проблема не была четко определена). Если вам действительно нужны подмножества, вам лучше изучить некоторые алгоритмы для задачи о рюкзаке (см. ссылки в комментариях). - person Mikegrann; 02.09.2016
comment
На самом деле я хотел спросить, что номера подмножеств не должны быть непрерывными. Они могут быть из любой группы чисел. - person Abhishek; 02.09.2016
comment
@Mikegrann Я также искал проблему с рюкзаком, но не нашел решения. Можете ли вы направить меня или дать мне соответствующую ссылку для этого. - person Abhishek; 02.09.2016
comment
@Abhishek en.wikipedia.org/wiki/Knapsack_problem#Solving имеет псевдокод и stackoverflow.com/questions/tagged/knapsack-problem содержит множество вопросов и обсуждений по StackOverflow. - person Mikegrann; 02.09.2016
comment
@ h2so4 Большое спасибо .. очень ценю. ты решил мою проблему. Спасибо еще раз. - person Abhishek; 03.09.2016
comment
@Abhishek, не могли бы вы подтвердить это решение, поскольку ваша проблема решена? Спасибо. - person h2so4; 03.09.2016
comment
Я проверил этот код для различных массивов, и я думаю, что он дает правильный результат. На самом деле у меня есть массив из 4000 значений. Но когда я запускаю этот код, это занимает целую вечность. Можете ли вы предложить какую-нибудь идею, с помощью которой я могу сократить время выполнения. Одна вещь, которую я сделал, это выйти из подпрограммы findsum, когда максимальная сумма равна 12. - person Abhishek; 03.09.2016
comment
время, необходимое для решения такого рода задач, пропорционально n! поэтому с n около 4000 значений это займет вечность. Выход из findsum, когда maxsum равен 12, является умной оптимизацией. - person h2so4; 04.09.2016
comment
другая оптимизация заключается в сортировке массива (по возрастанию) и выходе из цикла for, как только сумма превысит 12. - person h2so4; 05.09.2016
comment
@ h2so4, если мы наложим условие на максимальную и минимальную длину подмассива. Например, у меня есть массив из 10 чисел. И мне нужен массив с минимальной длиной 3 и максимальной длиной 6. Поможет ли это в оптимизации. какая будет модификация в приведенном выше коде?? - person Abhishek; 09.09.2016
comment
@ h2so4 какая петля ?? У меня есть отсортированный массив в порядке убывания - person Abhishek; 09.09.2016

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

Простой рекурсивный вызов будет выглядеть следующим образом:

list = [...]
N = length of list
dp[ maximum possible sum ][ N ]

f(‌current_sum, index):
  if dp[current_sum][index] is set
    return dp[current_sum][index]
  if index >= N
    if current_sum > 12
       result = 0
    else
       result = current_sum
  else
    result = max( f(current_sum, index+1), f(current_sum+list[index], index+1)
  dp[current_sum][index] = result
  return result

result = f(0,0) // this will return the desired result
person Saeid    schedule 02.09.2016