- Open Notes - http://opennotes.ru -

Алгоритм поиска всех комбинаций чисел дающих заданную сумму (на VBA)

Задача: Есть набор целых чисел (числа не повторяются)

1	3	5	7	8	9	11	2	25	30

Необходимо в Excel написать макрос, который выводит на лист «Результат» все комбинации чисел из набора, дающие в сумме 40. Каждое число можно использовать один раз. Полный перебор комбинаций нежелателен.

Дано: Книга Excel, сотоящая из двух листов. На первом листе (sh1) во второй строке располагается набор целых чисел, а в четвертой строке – необходимая сумма. На втором листе (sh2) будут хранится временные массивы и итоговый результат.

Решение: нижеследующий код из двух процедур находит нужные комбинации не прибегая к полному перебору:

Private Sub Main()
sh2.Cells.ClearContents
lastCol = sh1.Cells(2, sh1.Columns.Count).End(xlToLeft).Column
sSum = sh1.Cells(4, 1).Value '"Сумма для подбора"
'"Временный массив исходных чисел". Заполняем нулями
sh2.Range(sh2.Cells(1, 1), sh2.Cells(1, sSum)).Value = Evaluate("=0")
'"Временный массив с решением". Заполняем нулями
sh2.Range(sh2.Cells(2, 1), sh2.Cells(2, sSum)).Value = Evaluate("=0")
For i = 1 To lastCol 'Заполнение массива исходных чисел
    nNum = sh1.Cells(2, i).Value
    If nNum <= sSum Then
        '"Временный массив исходных чисел".
        'Количество элементов равно значению Суммы для подбора
        'На места, номера которых соответствуют значениям
        'из набора чисел для суммирования, ставятся единицы
        sh2.Cells(1, nNum) = 1
    End If
Next i
sh2.Cells(3, 1).Value = "Результат:"
Call PoiskComb(sSum, sSum) 'Поиск решений
sh2.Rows("1:1").Delete Shift:=xlUp
sh2.Rows("1:1").Delete Shift:=xlUp
sh2.Activate
sh2.Range("A1").Select
End Sub
'Поиск комбинаций чисел из "Временног массива решений"
'(максимальное не больше Ni), дающих в сумме N
Sub PoiskComb(N, Ni)
For i = Ni To 1 Step -1
    If sh2.Cells(1, i) = 1 Then
        N1 = N - i
        If N1 = 0 Then
            'Запись подходящей комбинации
            '---
            'Поиск послденей строки на листе с Результатом
            k = sh2.Cells(sh2.Rows.Count, 2).End(xlUp).Row + 1
            'Добавляем последнее число из комбинации
            'во "Временный массив с решением"
            sh2.Cells(2, i) = 1
            lastColSol = sh2.Cells(2, sh2.Columns.Count).End(xlToLeft).Column
            For j = 1 To lastColSol 'Запись очередного решения
                If sh2.Cells(2, j) = 1 Then
                    lastColI = sh2.Cells(k, sh2.Columns.Count). _
                        End(xlToLeft).Column
                    sh2.Cells(k, lastColI + 1) = j
                End If
            Next j
            'Убираем последнее число из комбинации
            'из "Временного массива с решением" для поиска других
            'возможных решений
            sh2.Cells(2, i) = 0
        ElseIf N1 > 0 Then
            'Если сумма выбранных чисел всё ещё меньше,
            'чем "Сумма для подбора", то ищем числа, в сумме
            'дающие недостающий остаток
            '---
            'Добавляем во "Временный массив с решением" последнее выбранное
            'число и убираем его из "Временного массива исходных чисел"
            sh2.Cells(2, i) = 1
            sh2.Cells(1, i) = 0
            Call PoiskComb(N1, i)
            'Возвращаем это же число во "Временный массив исходных чисел"
            'и удаляем его из "Временного массива с решением"
            sh2.Cells(2, i) = 0
            sh2.Cells(1, i) = 1
        End If
    End If
Next i
End Sub

Итог: на выходе алгоритма имеем следующие комбинации на листе "Результат" (каждая строка – новая комбинация):

1	9	30			
2	8	30			
3	7	30			
1	2	7	30		
2	3	5	30		
1	3	11	25		
1	5	9	25		
1	2	3	9	25	
7	8	25			
2	5	8	25		
3	5	7	25		
1	2	5	7	25	
5	7	8	9	11	
2	3	7	8	9	11