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

Оптимизация количества рюкзаков

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

В данной задаче постановка иная: вместимость каждого рюкзака одинакова, а список предметов — фиксирован. Необходимо определить минимальное количество рюкзаков, достаточное для размещения всех предметов.

Цель — сократить затраты, используя как можно меньше рюкзаков, при условии, что вес предметов в каждом рюкзаке не превышает установленный максимум.

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

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

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

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

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

Здесь мы воспользуемся линейной целочисленной моделью, так как:

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

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

Создадим объект модели, в котором будем описывать переменные, ограничения и целевую функцию.

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

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

Укажем следующие входные данные:

  • МаксВесРюкзака — максимальная грузоподъёмность одного рюкзака;
  • ВесаПредметов — массив весов всех предметов;
  • МаксКоличествоРюкзаков — технически максимальное число рюкзаков (равное количеству предметов — случай, когда каждый предмет в отдельном рюкзаке).
// Ввод данных
МаксВесРюкзака = 100;
ВесаПредметов = О2.Утилиты().МассивЧиселИзСтроки(
"48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30"
);
КоличествоПредметов = ВесаПредметов.Количество();
МаксКоличествоРюкзаков = КоличествоПредметов;

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

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

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

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

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

Создадим отдельный массив булевых переменных со значениями:

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

5. Описание ограничений

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

Каждый предмет помещается ровно в один рюкзак

Сумма по всем рюкзакам для каждого предмета должна равняться 1.

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

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

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

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

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

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

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

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

РюкзакИспользуется = ИспользуемыеРюкзаки[Рюкзак];
ЭффМаксВесРюкзака = Модель.Выражения().Терм(РюкзакИспользуется, МаксВесРюкзака);

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

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

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

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

// Устанваливаем целевую функцию. Минимизируем количество используемых рюкзаков
КоличествоРюкзаков = Модель.Выражения().Сумма(ИспользуемыеРюкзаки);
Модель.Минимизировать(КоличествоРюкзаков);

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

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

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

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

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

  • Показываем количество использованных рюкзаков;
  • Для каждого рюкзака выводим его заполнение и список предметов.

Если решения нет, выводим соответствующее сообщение.

// Выводим результаты
Если Решение.РешениеНайдено() Тогда
ИспользованоРюкзаков = Решение.ЗначениеВыражения(КоличествоРюкзаков);
Сообщить("Использовано рюкзаков: " + ИспользованоРюкзаков);

Для Рюкзак = 0 По МаксКоличествоРюкзаков - 1 Цикл
РюкзакИспользован = Решение.ЗначениеПеременной(ИспользуемыеРюкзаки[Рюкзак]);

Если РюкзакИспользован = 0 Тогда
Продолжить;
КонецЕсли;

ВесРюкзака = Решение.ЗначениеВыражения(ВесаРюкзаков[Рюкзак]);

Сообщить(СтрШаблон(
"Рюкзак %1 (вес: %2 из %3)",
Формат(Рюкзак, "ЧН=0; ЧГ=0"),
ВесРюкзака,
МаксВесРюкзака
));

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

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

КонецЦикла;
Иначе
Сообщить("Решение отсутсвует!");
КонецЕсли;

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

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

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

// Ввод данных
МаксВесРюкзака = 100;
ВесаПредметов = О2.Утилиты().МассивЧиселИзСтроки(
"48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30"
);
КоличествоПредметов = ВесаПредметов.Количество();
МаксКоличествоРюкзаков = КоличествоПредметов;

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

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

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

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

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

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

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

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

РюкзакИспользуется = ИспользуемыеРюкзаки[Рюкзак];
ЭффМаксВесРюкзака = Модель.Выражения().Терм(РюкзакИспользуется, МаксВесРюкзака);

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

// Устанваливаем целевую функцию. Минимизируем количество используемых рюкзаков
КоличествоРюкзаков = Модель.Выражения().Сумма(ИспользуемыеРюкзаки);
Модель.Минимизировать(КоличествоРюкзаков);

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

// Выводим результаты
Если Решение.РешениеНайдено() Тогда
ИспользованоРюкзаков = Решение.ЗначениеВыражения(КоличествоРюкзаков);
Сообщить("Использовано рюкзаков: " + ИспользованоРюкзаков);

Для Рюкзак = 0 По МаксКоличествоРюкзаков - 1 Цикл
РюкзакИспользован = Решение.ЗначениеПеременной(ИспользуемыеРюкзаки[Рюкзак]);

Если РюкзакИспользован = 0 Тогда
Продолжить;
КонецЕсли;

ВесРюкзака = Решение.ЗначениеВыражения(ВесаРюкзаков[Рюкзак]);

Сообщить(СтрШаблон(
"Рюкзак %1 (вес: %2 из %3)",
Формат(Рюкзак, "ЧН=0; ЧГ=0"),
ВесРюкзака,
МаксВесРюкзака
));

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

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

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