Перейти к основному содержимому

Задача нескольких рюкзаков

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

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

Подобная задача встречается в самых разных бизнес-сценариях, где требуется оптимально распределить ограниченные ресурсы по нескольким вместилищам или каналам. Примеры:

  • Логистика — распределение заказов по автомобилям с разной грузоподъёмностью;
  • Складская деятельность — размещение товаров по складам с разными зонами хранения;
  • Производство — распределение партий продукции по линиям с ограниченной загрузкой;
  • Проектное управление — распределение задач или оборудования по командам с разным доступным временем или ресурсами.

В каждом из этих случаев задача сводится к тому, чтобы максимально эффективно использовать доступные “рюкзаки” (контейнеры, машины, зоны, линии), при этом соблюдая ограничения и получая наибольшую суммарную ценность или прибыль.

Постановка задачи

Нужно разместить набор предметов в нескольких рюкзаках, при этом:

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

Решение задачи

1. Выбор модели

Для решения задачи используем линейную целочисленную модель. Причины выбора:

  • решение задачи требует двоичных (булевых) переменных, указывающих, находится ли предмет в конкретном рюкзаке;
  • ограничения и целевая функция формулируются через линейные выражения;
  • значения переменных должны быть целыми (0 или 1), дробные значения недопустимы.

2. Создание модели

Через менеджер моделей библиотеки О2 создаём объект модели, в котором будут храниться переменные, ограничения и целевая функция.

// Создаем объект линейной модели
Модель = О2
.Модели()
.ЛинейнаяЦелочисленнаяМодель()
.СоздатьМодель();

3. Ввод данных

В качестве входных данных задаём:

  • массив весов предметов;
  • массив ценностей предметов;
  • массив вместимостей рюкзаков.

Данные преобразуем в числовой формат с помощью функции МассивЧиселИзСтроки.

// Вводим данные 
Веса = О2.Утилиты().МассивЧиселИзСтроки("48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36");
Ценности = О2.Утилиты().МассивЧиселИзСтроки("10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25");
Вместимости = О2.Утилиты().МассивЧиселИзСтроки("100, 90, 65, 95, 75");

КоличествоПредметов = Веса.Количество();
КоличествоРюкзаков = Вместимости.Количество();

4. Регистрация переменных

Для каждой пары (предмет, рюкзак) зарегистрируем булеву переменную, значение которой отражает:

  • 1 — предмет помещен в этот рюкзак;
  • 0 — предмет в этот рюкзак не помещён.

Созданные переменные сохраним в двумерном массиве.

// Добавляем булевы переменные по одной на каждую комбинацию (Предмет, Рюкзак)
// Переменная будет принимать значение ИСТИНА, если предмет находится в указанном рюкзаке
ПредметыРюкзаков = Новый Массив(КоличествоПредметов, КоличествоРюкзаков);
Для Предмет = 0 По КоличествоПредметов - 1 Цикл
Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
ПредметыРюкзаков[Предмет][Рюкзак] = Модель.БулеваПеременная();
КонецЦикла;
КонецЦикла;

5. Установка ограничений

Установим следующие ограничения:

Один предмет — не более чем в одном рюкзаке

Суммируем все булевы переменные для предмета и требуем, чтобы сумма была ≤ 1.

// Устанавливаем ограничение: предмет может находить не более чем в одном рюкзаке
Для Предмет = 0 По КоличествоПредметов - 1 Цикл
КоличествоРазмещений = Модель.Выражения().Сумма(ПредметыРюкзаков[Предмет]);
Модель.Ограничения().ЗначениеМеньшеИлиРавно(КоличествоРазмещений, 1);
КонецЦикла;

Ограничение по вместимости каждого рюкзака

Суммируем веса предметов, помещённых в рюкзак, и сравниваем с его вместимостью.

// Устанавливаем ограничение: вес каждого рюкзака не превышает его вместимость
ВесаРюкзаков = Новый Массив(КоличествоРюкзаков);
ВесРюкзака = Модель.Выражения().СоздатьПостроительВыражений();

Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
ВесРюкзака.Очистить();

Для Предмет = 0 По КоличествоПредметов - 1 Цикл
Вес = Веса[Предмет];
ПризнакРазмещения = ПредметыРюкзаков[Предмет][Рюкзак];

ВесРюкзака.ДобавитьТерм(ПризнакРазмещения, Вес);
КонецЦикла;

ВесаРюкзаков[Рюкзак] = ВесРюкзака.ПолучитьВыражение();
Вместимость = Вместимости[Рюкзак];

Модель.Ограничения().ЗначениеМеньшеИлиРавно(ВесаРюкзаков[Рюкзак], Вместимость);
КонецЦикла;

Здесь линейные выражения для веса каждого рюкзака сохраняются в отдельный массив. Эти выражения мы вычислим позднее при выводе результатов.

6. Установка целевой функции

Цель — максимизировать суммарную ценность всех размещённых предметов.

Формируем в цикле линейное выражение для целевой функции: для каждой пары (предмет, рюкзак) добавляем слагаемое Ценность × ПризнакРазмещения.

// Установка целевой функции (максимизация ценности размещенных предметов)
СуммарнаяЦенность = Модель.Выражения().СоздатьПостроительВыражений();

Для Предмет = 0 По КоличествоПредметов - 1 Цикл
Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
Ценность = Ценности[Предмет];
ПризнакРазмещения = ПредметыРюкзаков[Предмет][Рюкзак];

СуммарнаяЦенность.ДобавитьТерм(ПризнакРазмещения, Ценность);
КонецЦикла;
КонецЦикла;

Модель.Максимизировать(СуммарнаяЦенность.ПолучитьВыражение());

7. Решение модели

Выполняем поиск оптимального решения.

// Решаем модель
Решение = Модель.Решить();

8. Вывод результатов

Если решение найдено:

  • выводим статус;
  • по каждому рюкзаку — список предметов, их вес и ценность;
  • показываем суммарный вес и суммарную ценность.
// Выводим результаты     
Если Решение.РешениеНайдено() Тогда
СтатусРешения = Решение.Статус();
ОбщаяЦенность = Решение.ЗначениеВыражения(СуммарнаяЦенность.ПолучитьВыражение());

Сообщить("Статус решения: " + СтатусРешения);

ОбщийВес = 0;
Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
ВесРюкзака = Решение.ЗначениеВыражения(ВесаРюкзаков[Рюкзак]);
ЦенностьРюкзака = 0;

Сообщить(СтрШаблон("Рюкзак %1:", Рюкзак));
Для Предмет = 0 По Веса.Количество() - 1 Цикл
ПредметПомещен = Решение.ЗначениеПеременной(ПредметыРюкзаков[Предмет][Рюкзак]);

Если ПредметПомещен = 1 Тогда
Сообщить(СтрШаблон(
" Предмет %1 (вес: %2, ценность: %3)",
Формат(Предмет, "ЧН=0"),
Веса[Предмет],
Ценности[Предмет]
));

ЦенностьРюкзака = ЦенностьРюкзака + Ценности[Предмет];
КонецЕсли;
КонецЦикла;

Сообщить(СтрШаблон(
"Вес рюкзака: %1 из %2. Ценность рюкзака: %3.",
ВесРюкзака,
Вместимости[Рюкзак],
ЦенностьРюкзака
));

ОбщийВес = ОбщийВес + ВесРюкзака;
КонецЦикла;

Сообщить("Итоги");
Сообщить("Общий вес: " + ОбщийВес);
Сообщить("Общая ценность: " + ОбщаяЦенность);
Иначе
Сообщить("Решение отсутствует!");
КонецЕсли;

Полный код решения задачи

Ниже представлен полный код решения. Вы также можете скачать пример в виде готовой обработки.

// Создаем объект линейной модели
Модель = О2
.Модели()
.ЛинейнаяЦелочисленнаяМодель()
.СоздатьМодель();

// Вводим данные
Веса = О2.Утилиты().МассивЧиселИзСтроки("48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36");
Ценности = О2.Утилиты().МассивЧиселИзСтроки("10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25");
Вместимости = О2.Утилиты().МассивЧиселИзСтроки("100, 90, 65, 95, 75");

КоличествоПредметов = Веса.Количество();
КоличествоРюкзаков = Вместимости.Количество();

// Добавляем булевы переменные по одной на каждую комбинацию (Предмет, Рюкзак)
// Переменная будет принимать значение ИСТИНА, если предмет находится в указанном рюкзаке
ПредметыРюкзаков = Новый Массив(КоличествоПредметов, КоличествоРюкзаков);
Для Предмет = 0 По КоличествоПредметов - 1 Цикл
Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
ПредметыРюкзаков[Предмет][Рюкзак] = Модель.БулеваПеременная();
КонецЦикла;
КонецЦикла;

// Устанавливаем ограничение: предмет может находить не более чем в одном рюкзаке
Для Предмет = 0 По КоличествоПредметов - 1 Цикл
КоличествоРазмещений = Модель.Выражения().Сумма(ПредметыРюкзаков[Предмет]);
Модель.Ограничения().ЗначениеМеньшеИлиРавно(КоличествоРазмещений, 1);
КонецЦикла;

// Устанавливаем ограничение: вес каждого рюкзака не превышает его вместимость
ВесаРюкзаков = Новый Массив(КоличествоРюкзаков);
ВесРюкзака = Модель.Выражения().СоздатьПостроительВыражений();

Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
ВесРюкзака.Очистить();

Для Предмет = 0 По КоличествоПредметов - 1 Цикл
Вес = Веса[Предмет];
ПредметПомещен = ПредметыРюкзаков[Предмет][Рюкзак];

ВесРюкзака.ДобавитьТерм(ПредметПомещен, Вес);
КонецЦикла;

ВесаРюкзаков[Рюкзак] = ВесРюкзака.ПолучитьВыражение();
Вместимость = Вместимости[Рюкзак];

Модель.Ограничения().ЗначениеМеньшеИлиРавно(ВесаРюкзаков[Рюкзак], Вместимость);
КонецЦикла;


// Установка целевой функции (максимизация ценности размещенных предметов)
СуммарнаяЦенность = Модель.Выражения().СоздатьПостроительВыражений();

Для Предмет = 0 По КоличествоПредметов - 1 Цикл
Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
Ценность = Ценности[Предмет];
ПредметПомещен = ПредметыРюкзаков[Предмет][Рюкзак];

СуммарнаяЦенность.ДобавитьТерм(ПредметПомещен, Ценность);
КонецЦикла;
КонецЦикла;

Модель.Максимизировать(СуммарнаяЦенность.ПолучитьВыражение());

// Запускаем поиск решения
Решение = Модель.Решить();

// Выводим результаты
Если Решение.РешениеНайдено() Тогда
СтатусРешения = Решение.Статус();
ОбщаяЦенность = Решение.ЗначениеВыражения(СуммарнаяЦенность.ПолучитьВыражение());

Сообщить("Статус решения: " + СтатусРешения);

ОбщийВес = 0;
Для Рюкзак = 0 По КоличествоРюкзаков - 1 Цикл
ВесРюкзака = Решение.ЗначениеВыражения(ВесаРюкзаков[Рюкзак]);
ЦенностьРюкзака = 0;

Сообщить(СтрШаблон("Рюкзак %1:", Рюкзак));
Для Предмет = 0 По Веса.Количество() - 1 Цикл
ПредметПомещен = Решение.ЗначениеПеременной(ПредметыРюкзаков[Предмет][Рюкзак]);

Если ПредметПомещен = 1 Тогда
Сообщить(СтрШаблон(
" Предмет %1 (вес: %2, ценность: %3)",
Формат(Предмет, "ЧН=0"),
Веса[Предмет],
Ценности[Предмет]
));

ЦенностьРюкзака = ЦенностьРюкзака + Ценности[Предмет];
КонецЕсли;
КонецЦикла;

Сообщить(СтрШаблон(
"Вес рюкзака: %1 из %2. Ценность рюкзака: %3.",
ВесРюкзака,
Вместимости[Рюкзак],
ЦенностьРюкзака
));

ОбщийВес = ОбщийВес + ВесРюкзака;
КонецЦикла;

Сообщить("Итоги");
Сообщить("Общий вес: " + ОбщийВес);
Сообщить("Общая ценность: " + ОбщаяЦенность);
Иначе
Сообщить("Решение отсутствует!");
КонецЕсли;
  Скачать пример