Научная статья на тему 'Перебор субоптимальных решений в дискретных задачах оптимизации'

Перебор субоптимальных решений в дискретных задачах оптимизации Текст научной статьи по специальности «Математика»

CC BY
298
24
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
СУБОПТИМАЛЬНЫЕ РЕШЕНИЯ / SUBOPTIMAL SOLUTIONS / ДИСКРЕТНЫЕ ЗАДАЧИ ОПТИМИЗАЦИИ / DISCRETE OPTIMIZATION / ДИНАМИЧЕСКОЕ ПРОГРАММИРОВАНИЕ / DYNAMIC PROGRAMMING

Аннотация научной статьи по математике, автор научной работы — Романовский Иосиф Владимирович

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

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

The paper presents an approach to enumeration of suboptimal solutions, based on introduction of special processes of enumeration – «enumerators», which solve problems of that kind for various smaller problems. Some special operations presented in the paper allow us to make the required enumerators of simpler ones. Some examples are presented.

Текст научной работы на тему «Перебор субоптимальных решений в дискретных задачах оптимизации»

УДК 519.854.64

Романовский Иосиф Владимирович

ПЕРЕБОР СУБОПТИМАЛЬНЫХ РЕШЕНИЙ В ДИСКРЕТНЫХ ЗАДАЧАХ ОПТИМИЗАЦИИ

Аннотация

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

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

ЧТО ТАКОЕ СУБОПТИМАЛЬНЫЕ РЕШЕНИЯ И ЗАЧЕМ ОНИ НУЖНЫ

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

Почему в дискретной оптимизации важен перебор субоптимальных решений? Прежде всего, ясно, что для задач непрерывной оптимизации сама постановка вопроса неправильна, - оптимальное решение непрерывной задачи содержит обычно целую окрестность решений со сколь угодно близкими значениями целевой функции. Но, кроме этого очевидного аргумента, нужно отметить, что задачи дискретной оптимизации более «капризны», трудоемкость их решения резко меняется при малейшем изменении задачи. Практические задачи трудны очень часто, поэтому при их решении некоторыми существенными ограничениями могут пренебречь, а упрощение математической модели приведет к тому, что полученное оптимальное решение окажется нереализуемым из-за внемоделъныгх факторов, то есть тех условий, которые не стали включать в математическую модель. Кажется естественным такой подход: перебирать допустимые реше-

© Романовский И.В., 2012

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

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

Наш подход будет более общим. Предлагаются средства, пригодные для широкого класса задач. Этот подход заключается в выделении набора операций, с помощью которых процесс перебора решений нужной задачи компонуется из более простых процессов. Элементы такого подхода уже встречались, например, у Е. Лаулера [1], который отметил важность операции слияния, и у Миниеки и Шира [2], где операции слияния и суммирования списков введены для операций над матрицами списков в связи с попыткой переноса известного метода Флойда на задачу построения матрицы списков к-кратчайших путей. Некоторые другие интересные ссылки мы приведем в конце статьи.

Развитие общего подхода предусматривает выяснение возможных различий в постановке задачи. Например, вычислительная сторона задачи существенно меняется в зависимости от того, требуется перебор всех допустимых решений задачи или только некоторого их количества, - в последнем слу-

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

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

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

После этого мы будем готовы к рассмотрению вопросов программной реализации перечислителей в духе объектно-ориентированного программирования.

Эта статья во многом следует моей книге [3].

ПЕРВОНАЧАЛЬНЫЕ ПРИМЕРЫ

Перебор субоптимальных решений в задачах дискретной оптимизации начался с динамического программирования. Еще в работе создателя динамического программирования Ричарда Беллмана и его соавтора Р. Калабы [4], в 1960 г. рассматривалась за-

дача перебора путей в графе (см. также [5]). С этой задачи и с подхода Беллмана-Калабы мы и начнем. Рассмотрим традиционный подход динамического программирования.

1. Кратчайший путь в графе без контуров

Эту задачу можно считать хорошо известной, поэтому ее удобно использовать для введения в методы рекуррентного решения оптимизационных задач. Итак, пусть задан ориентированный граф без контуров (M, N) с множеством вершин M и множеством дуг N. Заданы длины дуг, и длина пути определяется как сумма длин входящих в путь дуг. Требуется найти кратчайший путь с заданными началом i0 и концом i

Пример. Рассмотрим простой пример. Задан граф без контуров с шестью вершинами, каждой дуге которого сопоставлена положительная длина (рис. 1). Требуется найти кратчайший путь от вершины A до вершины F.

Прежде всего (и это важная черта динамического программирования) расширим задачу и будем искать кратчайшие пути от A до всех вершин графа. Обозначим длину пути до вершины i, i е {A, B, C, D, E, F} через v (i). Естественно принять v (A) = 0. Все множество путей, проходящих из A в F, можно разбить на три подмножества по тому признаку, какая дуга будет в пути последней, ведущая из C, из D или из E. К примеру, путь ADEF попадет в третью группу, так как он кончается дугой EF. Можно искать путь минимальной длины в каждом из этих множеств отдельно, а затем выбрать минимум из минимумов (по латыни это minimum minimorum). В естественных обозначениях

v (F) = min {vc (F), Vd (F), Ve (F)}.

Но очевидно,

Vc (F) = 20 + v(C), Vd(F) = 17 + v(D), Ve (F) = 6 + v (E),

то есть

v (F) = min {20 + v (C), 17 + v (D), 6 + v (E)}.

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

V (i) = min {c (j) + V (beg j) | end j = i}. (1)

Здесь beg j и end j - соответственно начало и конец дуги j, а c (j) - ее длина.

Применение уравнения (1) прямо приводит к требуемому результату. Имеем: v (B) = 11 + v (A) = 11, v (D) = 12, так как v (A) = 0,

v (E) = min {13 + v (B), 14 + v (D)} = 24,

v (C) = min {19 + v (B), 5 + v (E)}=29, откуда окончательно v (F) = 29.

Уравнение (1) называется уравнением динамического программирования или уравнением Беллмана, а функция v - функцией Беллмана.

Задача о кратчайшем пути может решаться с помощью разных подходов, и впоследствии мы будем говорить об очень эффективном алгоритме Дейкстры для ее решения на произвольном графе, но здесь мы говорим об этой задаче в связи с содержащимся в ней процессом динамического программирования, то есть специфическим процессом последовательного принятия решений. Множество вершин графа M - это множество состояний процесса, A - начальное состояние. Дуги, выходящие из вершины i е M, - это решения, которые можно принимать в состоянии i. Принятие каждого решения j влечет затраты c (j) и переводит процесс в состояние i' = end j.

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

2. Наилучший линейный раскрой

Рассмотрим теперь известную задачу о выборе наилучшего раскроя линейного сырья заданной длины /0 на детали меньшей

длины 1 [г], г е 1: т, для получения наибольшего дохода (цены деталей р известны) [6].1

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

В математических терминах, требуется найти неотрицательные и целочисленные х [г], г е 1: т, - количества рулонов каждого г-го вида, удовлетворяющие условию:

Ье!:^ И Х ХР] < 10

и максимизирующие

Ъе,тР М Х Х] .

Обозначим искомый максимум через V (10). Если рассматривать его как функцию от размера сырья, можно связать значения этой функции для разных размеров уравнением

v(A) = тах{сг + v(A - 1[г]) | г е 1 :т, 1 [г] < А}, где А - длина сырья (переменная, при этом нас интересует V (10, т)), v(l) принимается равным 0, если нет деталей длины меньшей А.

Фактически мы имеем дело с графом (М, 1), где М = 0:10, то есть вершинами являются все возможные промежуточные размеры сырья, а дуги соответствуют возмож-нымрезам: когда от рулона размера А отрезается рулон размера 1[г], получаем рулон размера А- 1[г], - такая возможность определяет дугу, идущую из состояния А - I [ г] в состояние А.

Это тоже уравнение Беллмана. Его можно использовать непосредственно для вычисления значений V (А), начиная от малых значений аргумента и постепенно их увеличивая. Однако эта исходная вычислительная схема может быть существенно улучшена. Рассмотрим разные формы уравнения Бел-лмана

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

1 Первое издание этой книги вышло в 1951 г., когда термина «динамическое программирование» еще не существовало. Авторы предугадали динамическое программирование.

на оставшиеся детали. Обозначив через v (Я, к) максимальный доход от раскроя сырья длины Я на детали первых к размеров (нас интересует v (Я, к)), получаем следующее уравнение Беллмана v(1, к) = max {cks + v(1- /[к] s, к - 1) | 0 < /[к] s < l}.

Уравнение можно еще упростить, сделав выбор количества резов последней детали последовательным принятием решения

v(1, к) = max {v(1, к - 1), c/(s + v(1- /[к], к)}.

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

Вычисление функций v (Я, к) может быть существенно упрощено, если воспользоваться тем, что при любом к функция v (Я, к) -кусочно-постоянная неубывающая функция от Я и, значит, может задаваться списком скачков. Вычислительный процесс может быть описан как процесс переработки списка скачков к-1 -й итерации в список к -й итерации [7].

3. «Ленивое исполнение»

Однако и исходный вариант уравнения Беллмана не безнадежен в вычислительном отношении. Можно организовать каскадное вычисление значений функции Беллмана, вычисляя ее только в тех точках, которые были запрошены непосредственно или в ходе вычисления в других точках. Такой способ вычислений называется по-английски /azy evaluation - ленивое исполнение.

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

4. Оптимизационная свертка

Уравнение Беллмана можно записывать более компактно, вводя специальные опера-

ции, например операцию оптимизационной свертки. Термин свертка (convolution) заимствован из математического анализа, где сверткой двух последовательностей A = {ак} и B = {¿к} называется последовательность

с = {скЬ где ск = X г е о* a • b* - г.

Оптимизационная свертка может быть мультипликативной, больше похожей на классическую, и аддитивной, более естественной, которую мы и выберем (ср. по этому поводу [8] и [9]). Итак, оптимизационной сверткой двух последовательностей A = {ак} и B = {¿к} называется последовательность C = {ск}, где

ск = max {ai + Ьк-i 1 i е 1:к}.

Это определение симметрично и может быть очевидным образом распространено на любое число слагаемых. Обозначим такую свертку знаком ©, так что C = A © B.

Пусть теперь задано t последовательностей A1, A2, ..., At и требуется найти их свертку A1 © A2 © ... © At. Введем последовательность последовательностей Vs: V1=A1, Vs = Vs1 © As, s >1. Последовательности Vs - это фактически функции Беллмана, определяемые при s > 1 уравнением

v/к] = min {as[i] + v,, ^- i] | i е 0 :к}.

В следующем параграфе для операции свертки найдется применение.

5. Схема Бертеле - Бриоши: выбор поддерева в дереве

Рассмотрим еще одну разновидность динамического программирования, которую ее авторы, итальянские математики Бертеле и Бриоши, назвали Nonseria/ dynamic programming (то есть не последовательное, в противоположность обычному последовательному, в котором есть параметр, аналогичный времени) ([10], см. также [7]).

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

Пусть задано ориентированное дерево {M, N с корнем r е M. Это значит, что в графе число дуг на единицу меньше числа вер-

шин, N = |М| -1, и имеется путь из корня в любую другую вершину. Пусть дугам графа сопоставлены положительные веса м., 7 е N. Требуется по заданному положительному числу т '< |М найти ориентированное дерево (М', N') с корнем в г и такое, что |М'| = т', N' с N, и сумма весов дуг из N' минимальна.

Пусть < = |М| - т'. Наша экстремальная задача, очевидно, может быть заменена задачей об удалении из дерева < дуг таким образом, чтобы оставалось корневое дерево с корнем в г и была максимальна сумма весов удаленных дуг.

Отметим, что каждой вершине г в графе соответствует свое поддерево Гг., в котором эта вершина является корнем. Будем рассматривать задачу о выборе поддерева с удалением < дуг минимального суммарного веса для всех деревьев Г. и произвольных й, используя принципы динамического программирования. Пусть V (г, <) - искомый минимальный вес в этой задаче, а - последовательность таких минимумов для всех возможных <, от 0 до числа дуг в дереве Г... Пусть У1 - это последовательность , к которой добавлен еще один элемент - последний элемент, увеличенный на вес дуги ., входящей в вершину г (мы как бы рассматриваем дерево Г., наращенное на дугу ., и для этого дерева решаем такую же задачу).

Теперь осталось отметить, что, во-первых, V = {0} для конечных вершин дерева (листьев), во-вторых, нас интересует Уг (точнее, только V (г, <)), и, наконец, в-третьих, что у = ©. , . . у..

1 ]■ ^^ 7 = г" ]

Это тоже уравнение Беллмана, но в обобщении Бертеле-Бриоши. Использование операторной записи делает его очень компактным. Выполним небольшое упражнение, чтобы увидеть функцию Беллмана более наглядно.

Пример. Рассмотрим дерево с корнем Я, изображенное на рис. 2. Вершины В, В, С, Н, /, К, Ь - листья, для каждой из них последовательность V состоит из одного элемента 0. Д ля вершины С последовательность состоит уже из двух элементов VC = {0, 4}, для

вершины F из трех VF = {0, 8, 11}, для I из четырех Vj = {0, 9, 14, 16}.

Теперь перейдем к более сложным расчетам. Для вычисления VA нужно продолжить последовательность VC до VAC = {0, 4, 11}, а уже затем найти VA = VAB © VAC. Имеем vA[0] = 0, vA[1] = max {4,15} = 15, vA [2] = max {11, 15 + 4} = 19, vA [3] = 15 + 11 = 26.

Вычисление VE более громоздко. При каждом k нужно вычислить все суммы вида vEF (i) + vEI (k -i), и минимальную из них принять в качестве vE (k). Имеем

Ve [0] = 0,

ve [1] = max {8, 9} = 9, vE [2] = max {0 + 14, 8 + 9, 11 + 0} = 17, vE [3] = max {0 + 16, 8 + 14, 11 + 9, 18 + 0} = 22, vE [4] = max{0 + 20, 8 + 16, 11 + 14, 18 + 9} = 27, vE [5] = max {8 + 20, 11 + 16, 18 + 14} = 32, vE [6] = max {11 + 20, 18 + 16} = 34, vE [7] = 18 + 20=38.

Далее нужно вычислить промежуточные функции VRA и VRE, а затем по ним окончательно построить vR.

¿-ОПТИМАЛЬНЫЕ РЕШЕНИЯ В ДИНАМИЧЕСКОМ ПРОГРАММИРОВАНИИ

1. Операция слияния упорядоченных массивов

В дальнейшем часто будет использоваться операция слияния (merge). Эта операция была хорошо известна во времена последовательных файлов на магнитных лентах, а

¿та

( В1

VTCJ v

(JX^ \н)

ъХ

(j) y^Xb) ®

Рис. 2. Дерево с корнем

Табл. 1

Шаг 1 2 3 4 5 6 7 8 9 10 11

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

A 5 9 17 26 31

B 2 6 8 12 27 29

C 2 5 6 8 9 12 17 26 27 29 31

k1 1 1 2 2 2 3 3 4 5 5 5

k2 1 2 2 3 4 4 5 5 5 6

сейчас остается известной благодаря, прежде всего, сортировке Неймана.

Пусть заданы две монотонно неубывающие последовательности А = {аД и В = {ЬД и требуется соединить их в одну последовательность С = {сД. Это соединение выполняется параллельным «чтением» обеих последовательностей от начала: на каждом шаге из прочтенных элементов выбирается наименьший, для того чтобы записать его в последовательность С, а вместо взятого элемента читается новый из той же последовательности. Так выглядит слияние файлов последовательного доступа.

В случае, если последовательности А = {аД и В = {ЬД записаны в массивы, для организации слияния нужно помнить на каждом шаге индексы текущих элементов читаемых массивов и обновлять их. Например, если А = {5, 9, 17, 26, 31} и В = {2, 6, 8, 12, 27, 29}, изменение индексов в ходе слияния выглядит так, как показано в табл. 1.

Индексы к1 и к2 показывают здесь степень использованности (сработанности) массивов А и В на каждом шаге процесса слияния.

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

Табл. 2

Шаг 1 2 3 4 5 6

kC 1 1 1 1 2 2

kD 1 2 2 2 2 2

kE 1 1 2 3 4 4

довательности в небольших группах, а затем слить вместе получившиеся результаты.

2. Перебор k-кратчайших путей в графе без контуров

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

Найденный кратчайший путь ADF проходит через вершину D. Очевидно, что второй кратчайший путь до F - это либо кратчайший путь через C, либо кратчайший путь через E, либо второй кратчайший путь через D. В функции v добавим еще один аргумент, номер пути. Тогда1

v (F, 2) = min { vc (F, 1), Vd (F, 2), Ve (F, 1)},

или

v (F, 2) = min {20 + v (C, 1), 17 + v (D, 2), 6 + v (E, 1)}.

Аналогично v (D, 2) = min {12 + v (A, 2)} = так как v (A, 2) = ¥ Поскольку v (C, 1) = 29, v (E, 1) = 24, то v (F, 2) = 30, эта длина достигается на пути ABEF.

Третий кратчайший путь находится из соотношения

v (F, 3) = min {20 + v (C, 1), 17 + v(D, 2),

6 + v (E, 2)}, он имеет длину v (F, 3) = 32, сам путь ADEF.

Для остальных путей запишем только их длины и соотношения, из которых они получены:

v (F, 4) = min {20 + v (C, 1), 17 + v (D, 2),

6 + v (E, 3)} = 49, v (F, 5) = min {20 + v (C, 2), 17 + v (D, 2),

6 + v (E, 3)} = 50, v (F, 6) = min {20 + v (C, 3), 17 + v (D, 2), 6 + v (E, 3)} = 51.

Выпишем из соотношений для v (F, •) индексы k в функциях v (г, •) в виде таблицы (см. табл. 2).

Получается очень похоже на таблицу индексов сработанности при слиянии двух мас-

1 Такие соотношения приводились еще в 1960 г. в уже упоминавшейся работе Р. Беллмана и Р. Калабы [4].

Леребор субоитимальных решений в дискретных задачах оптимизации

сивов. На самом деле это и есть слияние, слияние массивов значений в предшествующих вершинах графа, к которым «прибавлены» длины дуг.1 В каждой из этих вершин, кроме начальной, ее массив можно также записать как слияние других массивов. Значит, все значения можно получить так просто (это так похоже на программу, что используем «программистский» шрифт):

aA = [0] aB = 11 + aA aD = 12 + aA

aE = merge { (13+aB), (14+aD) }

aC = merge { ( 5+aE), (19+aB) }

aF = merge { ( 6+aE), (17+aD), (20+aC) }

Очень просто! Можно взять любой граф без контуров, написать аналогичные соотношения для массивов значений во всех вершинах и получить практически готовую программу. Более того, существуют такие языки программирования, для которых программа - это почти такая запись.

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

ПЕРЕЧИСЛИТЕЛИ И ОПЕРАЦИИ НАД НИМИ

Наконец-то, после всех вводных рассмотрений перейдем к главному предмету. Мы введем новый тип объектов, логических и программистских, и будем рассматривать наши алгоритмы в терминах операций над этими объектами.

Этот объект называется перечислителем Перечислитель может вызываться, и при каждом вызове он сообщает некоторое числовое значение и сопровождающую это значение текстовую информацию, называемую уяравленмем или решением. Числовые значения, возвращаемые при последовательных вызовах, расположены в порядке неубыва-

ния. По исчерпании значений перечислитель может сообщить, что его работа закончилась.

Так описывается самая простая разновидность перечислителя - последовательный одноразово используемый процесс.

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

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

Рассмотрим операции, позволяющие создавать новые перечислители, в том числе из уже имеющихся.

1. Перечислитель - массив

Самый простой перечислитель может быть задан массивом - монотонной последовательностью значений (неубывающей, если исходная экстремальная задача на максимум, и невозрастающей, если на минимум). Этот массив хранится во внутренней информации перечислителя вместе с индексом очередного значения. На каждый вызов процесс отвечает значением, соответствующим этому индексу, и увеличивает индекс на единицу. При выходе индекса за границу массива перечислитель сообщает об отсутствии новых значений.

1 На роль операции слияния в процессах перебора субоптимальных решений впервые обратил внимание Е. Лаулер [1].

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

3 Первоначально я рассматривал только два типа процессов. Предлагаемая здесь классификация появилась по предложению аспиранта М.Д. Папоркова.

Удобно иметь какое-нибудь обозначение для такого процесса. Пусть это будет ARRAY(a1,_,flk).

2. Преобразование значений перечислителя

Пусть заданы некоторый перечислитель P и монотонно неубывающая функция f, преобразующая его значения в другие значения. Описываемая операция заключается в том, что процесс P вызывается очередной раз и полученное им значение подставляется в функцию f. Результатом операции является перечислитель, возвращающий при каждом вызове преобразованные ответы P.

Наиболее употребительное монотонное преобразование - прибавление некоторой константы с. Мы таким преобразованием и ограничимся, назовем его сдвигом и будем записывать так: Q = PLUS (P, с).

3. Слияние перечислителей

Разумеется, дальше должно пойти слияние процессов, о котором уже много говорилось.

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

Обозначим операцию слияния двух перечислителей так: MERGE(P, Q). Это же обозначение мы сохраним и для операции слияния нескольких перечислителей: MERGER, ..., Pk).

4. Задержанное слияние

Следующая операция - это небольшая модификация слияния. Предположим, что, сливая два процесса, P и Q, мы знаем, что даже первое значение, вырабатываемое процессом Q, будет не меньше некоторой константы с. В этом случае, до тех пор пока значения процесса P не превосходят этого с, процесс Q можно даже и не вызывать и, значит, даже не инициализировать. Таким

образом, здесь слияние происходит с первоначальной задержкой.

Обозначим операцию задержанного слияния двух перечислителей так: DAMERGE(P, Q, с). Здесь буквы D и A от Delayed Absolute, потому что задержка определяется абсолютной величиной значения.

Есть еще один вариант задержанного слияния, при котором значение задержки относительно, оно отсчитывается не от нуля, а от первого значения процесса P. Эта операция будет обозначаться DMERGE(P, Q, с).

Обе эти операции появились уже в результате развития нашего подхода к алгоритмам перебора как к процессам. Они существенно повысили эффективность разрабатываемых алгоритмов.

5. Фильтрация значений перечислителя

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

6. Сложение перечислителей

Пусть заданы два процесса P и Q. Представим себе, что мы располагаем всеми значениями обоих процессов. Рассмотрим всевозможные суммы этих значений - по одному из каждого набора - и введем процесс, который вырабатывает эти суммы в порядке возрастания (неубывания). Такой процесс будет называться суммой перечислителей P и Q и обозначаться SUM(P, Q).

Пример. Если P = [4, 14, 29, 43], Q = [11, 16, 25], то SUM(P, Q) = [15, 20, 25, 29, 30, 39, 40, 45, 54, 54, 59, 68]

Можно рассматривать и сумму нескольких перечислителей, распространяя на нее то же обозначение: SUM (P1, ..., Pk).

Операция суммы нужна для перебора значений функции, заданной на прямом произведении двух (или нескольких) множеств и равной сумме функций от сомножителей. Например, если каждый процесс Pi - это

Леребор субоитмжальных решений s дискретных задачах оптимизации

массив из двух значений р = ARRAY(0, аг), то SUM(P1, ..., Pk) перебирает все суммы набора {а1, ..., ак } по всем его подмножествам.

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

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

А для второго значения суммы нужны еще и вторые значения слагаемых. Если обозначить г-ые значения процессов соответственно через p. и q,, то мы должны выбирать второе значение суммы как максимум из p + q2 и p2 + q1. Заметим, что сумма рг + q2 в этом рассмотрении не участвует, она доминируется двумя другими суммами. Вопрос о недоминируемых парах индексов рассмотрим отдельно.

7. Граница Парето

Пусть заданы два отрезка натурального ряда чисел K = 1: k и L = 1:1, и R - прямое произведение этих множеств, R = K х L = {(г, j) | г е 1: k, j е 1:1}. Возьмем какое-либо подмножество S с R.

Скажем, что пара (г, j) е R доминирует пару (г', j') е R, если г < г' и j < г'.

Границей Ларето множества S называется такое его подмножество B, что любой элемент (г, j) е S \ B доминируется каким-нибудь элементом из B и никакой элемент из B не доминируется другим1.

Теперь можно вернуться к процессу суммирования. При каждом вызове i имеется множество еще не использованных пар индексов S \ R, выбирается один из них, скажем, si, и следующее множество получается удалением выбранного индекса, то есть Si+1 = Si \ {s¿}. Легко можно доказать, что индекс si следует выбирать из i-й границы Парето Bf Действительно, если пара (i, j) доминирует (i', j'), то pi + qj < pi, + qj,.

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

.Продолжение примера. Для наглядности будем определять координаты не индексами, а значениями. Первоначально имеем в границе Парето одну пару B1 = (4, 11). Выбор этой пары с суммой 15 изменяет границу B2 = {(14, 11), (4, 16)}. Пара с суммой 20 выбирается, и мы получаем B3 = {(14, 11), (4, 25)}. Затем BA = {(29, 11), (14, 16), (4, 25)} и т. д.

ИСПОЛЬЗОВАНИЕ

Я использовал этот подход в ряде классических экстремальных задач (при замене их задачами перебора) и в некоторых прикладных задачах. Введенные в статье операции легко программируются в любом языке, допускающем объектно-ориентированный стиль программирования. Очень удобно было использовать для экспериментального программирования язык функционального программирования Miranda ([11], см. пример использования в [12]), интерпретатор для которого мне любезно предоставил Д. Тернер.

Надеюсь, что смогу написать об этом в продолжении статьи.

1 Вилфред Парето (1848-1923) - итальянский экономист и социолог, который ввел понятие точки, оптимальной по нескольким показателям (распределение благ между несколькими лицами), как точки, у которой нельзя улучшить один показатель, не ухудшив других. Наша граница - это множество точек, оптимальных по Парето. В сталинские времена имя Парето было под запретом: утверждалось, что он - «любимец Муссолини», хотя Муссолини (1883-1945) только слушал лекции Парето (1902), еще не будучи душе - фашистским вождем, а премьер-министром стал в 1922 г. Муссолини был человек умный и, действительно, Парето ценил, но это не переносит на Парето ответственность за грехи итальянских фашистов.

Литература

1. Lawler E. L. A procedure for computing the K-best solutions to discrete optimization problems and its application to the shortest path problem // Management Science, 1972. № 18. P. 401-405.

2. Minieka E., Shier D. A note on an algebra for the k-best routes in a network // J. Inst. Math. Appl., 1973. № 11. P. 145-149.

3. Романовский И.В. Субоптимальные решения, Петрозаводск: Изд-во Петрозав. ун-та, 1998.

4. Bellman R., Kalaba R. On k-th best policies // J. of SIAM, 1960. № 8. P. 582-588.

5. Беллман Р., Дрейфус С. Прикладные задачи динамического программирования. М.: Наука, 1965.

6. КанторовичЛ.В., Залгаллер В.А. Рациональный раскрой промышленных материалов. Изд. 3-е. СПб.: Невский диалект, 2012.

7. Романовский И.В. Алгоритмы решения экстремальных задач. М.: Наука, 1977.

8. Bellman R., Karush W. On a new functional transformation // Bull. Amer. math. soc., 1961. № 67. 5. P. 501-503.

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

9. Романовский И.В. Некоторые замечания о функциональном преобразовании Беллмана-Кару-ша // Вестник СПб. университета, сер. мат., 1962. Вып. 3. С. 148-150.

10. Bertelo U., BrioshiF. Non-Serial Dynamic Programming. N.Y.: Academic Press, 1972.

11. Turner D.A. An overview of Miranda. Research Topics in Functional Programming, D.A. Turner, ed., Addison-Wesley, 1990. P. 1-16.

12. Романовский И.В., Стукалов Д.Ю. Алгоритмы перебора неизоморфных деревьев // Вестник СПб. университета, сер. 1, 1997. Вып. 1. С. 46-52.

Abstract

The paper presents an approach to enumeration of suboptimal solutions, based on introduction of special processes of enumeration - «enumerators», which solve problems of that kind for various smaller problems. Some special operations presented in the paper allow us to make the required enumerators of simpler ones. Some examples are presented.

Keywords: suboptimal solutions, discrete optimization, dynamic programming.

© Наши авторы, 2012. Our authors, 2012.

Романовский Иосиф Владимирович, доктор физико-математических наук, профессор, заведующий кафедрой исследования операций математико-механического факультета СПбГУ, josephromanovsky@gmail. сот

i Надоели баннеры? Вы всегда можете отключить рекламу.