Лекция 18
Динамическое прогамирование
Задача коммивояжера из предыдущего раздела является примером
многошагового процесса решения, т. е.
процесса, при котором осуществляется последовательность решений, возможный
выбор которых зависит от текущего состояния системы, т. е. предыдущих решений.
Для задачи коммивояжера решением на каждом шаге является город, который надо
посетить следующим. В таком процессе задача состоит в определении оптимальной
последовательности решений, т. е. такой последовательности, которая
минимизирует (или, возможно, максимизирует) некоторую целевую функцию. В задаче
коммивояжера целевой функцией, которую надо минимизировать, является стоимость
обхода.
При решении таких задач с помощью динамического программирования мы основываемся на принципе оптимальности:
Оптимальная последовательность решений обладает тем свойством,
что какими бы ни были начальное состояние и начальное решение, остальные решения
должны быть оптимальной последовательностью решений но отношению к состоянию,
получающемуся в результате первого решения.
Применение этого принципа к решению комбинаторных задач по
существу означает использование принципа декомпозиции: вначале находятся
решения подзадач, затем они
используются для отыскания решения больших подзадач и, наконец, для решения
самой задачи.
Для задачи коммивояжера определим
T( i; j1,
j2, …, jk) =
|
Стоимость оптимального маршрута из i в 1,
который проходит через каждый из городов j1, j2, …, jk в
точности один раз, в любом порядке и не проходит ни через какие другие города.
|
Принцип оптимальности утверждает, что
Т (i; ji, ..., jk) = min
{Cijm +T(jm; j1, j2,..., jm-1, jm+1,
…, jk)}, (4.1)
где, Сij — стоимость прямого переезда из города i в город j. Далее, по определению
мы имеем,
T(i; j)=Сij+Сj1.
(4.2)
Мы заинтересованы в вычислении Т (1; 2, 3, ..., n) и нахождении маршрута
такой длины. Соотношения (4.1) и (4.2) определяют рекурсивную процедуру
вычисления Т(1, 2, 3, ..., п);
последовательность значений, получаемых при рекурсивном применении равенства
(4.1), дает оптимальный маршрут. Для n=5 последовательность рекурсивных обращений показана в виде
дерева на рис. 4.11.
Обратите внимание на то, что здесь присутствует значительное
дублирование в листьях; для больших значений п встречаются деревья большей высоты, с дублированием на более
высоких уровнях дерева. Чем выше в дереве появляются дублирования, тем они
дороже. При рекурсивном вычислении дерева в глубину (см. разд. 4.1.4) трудно
распознать идентичные поддеревья и предупредить дублирующие вычисления.
Используя принципы склеивания ветвей и переупорядочивания поиска, мы
организуем вычисления снизу вверх, уровень за уровнем. Так как значения Т на одном уровне зависят от значений Т на ближайшем снизу уровне, мы можем
начинать с листьев (уровень с номером п—2),
значения которых мы знаем из равенства (4.2), и производить обработку по
направлению вверх к корню (уровень с номером 0), уровень за уровнем. Конечно,
этот подход требует большей памяти, чем поиск в глубину. Если для каждого
отдельного узла дерева мы используем одно слово памяти, то всего слов памяти
потребуется
Такое относительно большое требование к памяти типично для такого
поуровневого применения динамического программирования.
Число сложений, требуемых для вычисления (4.1) и (4.2), равно 1
для каждого листа, 2 для каждого узла, чьи сыновья являются листьями, 3 для
каждого узла, чьи внуки являются листьями, и т. д., т. е. п — i—1 сложений для каждого узла на уровне с номером i, а всего сложений будет
Число сравнений почти то же самое, что и число сложений, а
именно n-i-2 для каждого узла изуровня
сномером i; таким образом, общее число
сравнений равно
Заметим, что при движении снизу вверх уровень за уровнем объем
требуемой работы является функцией только
числа городов и не зависит от элементов матрицы стоимостей в противоположность
методам ветвей и границ, при поиске в глубину, которые существенно зависят от
порядка строк и столбцов, а также от элементов матрицы.
К сожалению, применение принципа оптимальности и динамического
программирования к задаче коммивояжера не уменьшает существенно времени
вычисления; оно только реорганизует его. Однако для других задач принцип
оптимальности может резко уменьшить объем поиска, и поэтому динамическое
программирование может существенно сократить время вычисления. Пример такого
сокращения мы увидим в алгоритме построения оптимальных деревьев бинарного поиска,
в котором время вычисления уменьшается с величины, пропорциональной 4n/n3/2, если пользоваться очевидным исчерпывающим алгоритмом,
до О(n3)
при подходе с позиций динамического программирования; улучшение действительно
существенное.
Построение оптимальных деревьев бинарного
поиска
В качестве примера применения алгоритма динамического
программирования рассмотрим Алгоритм построения таблицы Гильберта-Мура при
поиске оптимальных деревьев бинарного поиска [http://www.neuroproject.ru/papers.htm].
Бинарный поиск в последовательно распределенной таблице
обеспечивает очень быстрое нахождение имен, которые являются средними точками
на раннем этапе процесса деления пополам, именно имен, близких к вершине дерева
T(l, n). Таким
образом, любое имя в таблице можно выбрать примерно за lg n сравнений.
На практике в большинстве таблиц встречаются имена, к которым обращаются
гораздо чаще, чем к другим, и "привилегированные" места в таблице
разумно постараться использовать для наиболее часто вызываемых имен, а не для
имен, а не для имен, выбранных для этих мест в результате бинарного поиска. Это
невозможно осуществить для последовательно распределенных таблиц, поскольку
место имени определяется его положением относительно естественного порядка имен
в таблице. Введем структуру данных, легко приспосабливаемую как к методу
бинарного поиска, так и к возможности выделять точки, в которых таблица делится
на две части.
Упорядоченным деревом называется ориентированное дерево, для
которого определен порядок сыновей любой вершины дерева. Обычно мы изображаем
упорядоченное дерево так, что корень размещается вверху, а сыновья каждой
вершины расположены в соответствии с порядком слева направо. Для бинарного
упорядоченного дерева поддерево, корень которого является левым сыном вершины v, называется левым поддеревом вершины v. Аналогично
определяется правое поддерево вершины v. Пусть A = {a1, a2, …, an} – множество
элементов, упорядоченных следующим образом: a1<a2<…<an. Бинарным
деревом поиска для множества A является
упорядоченное бинарное дерево, в котором каждая вершина v так помечена элементом l(v) множества
A, что 1) для каждого u в
левом поддереве вершины v, l(u)<l(v); 2) для
каждого u в правом
поддереве вершины v, l(u)>l(v); 3) для
каждого элемента x множества A существует точно одна такая вершина
v, что l(v)=x. Предположим A является
подмножеством универсума S. Тогда пусть B = { b0, b1,
… bn } – такое
множество, что 1)
Расширенным деревом бинарного поиска для A называется
дерево бинарного поиска для A с
n+1 листьями, представляющими элементы множества B. Отметим, что в расширенном дереве бинарного поиска листья
появляются слева направо в порядке b0, b1, …, bn. В
дальнейшем будем называть расширенное дерево бинарного поиска для множества A просто деревом бинарного поиска для
A.
Пусть дано подмножество A универсума
S и дерево T бинарного поиска для A. Множество S может быть множеством всех слов над английским алфавитом, а
упорядочение может быть лексикографическим порядком. Предположим, что нам
необходимо определить, принадлежит ли элемент
Случай 1. Корень отсутствует (дерево T бинарного поиска пусто).
Следовательно, x не
принадлежит A, и поиск завершается безуспешно.
Случай 2. x равен элементу, соответствующему корню. Следовательно, поиск
завершается успешно.
Случай 3. x меньше
элемента, соответствующего корню. Поиск продолжается ниже в левом поддереве
корня.
Случай 4. x больше
элемента, соответствующего корню.
Поиск продолжается ниже в правом поддереве корня. Ясно что успешный поиск
завершается во внутренних вершинах, а безуспешный - в листьях дерева T.
Пусть p1, p2,
…, pn – частоты обращения к элементам a1, a2, …, an соответственно, и пусть
q0, q1, …, qn – частоты, с которыми поиск завершается в
листьях T, представляющих
b0, b1, …, bn соответственно.
Таким образом, даны неотрицательные веса pi и qi. Необходимо построить
дерево бинарного поиска минимальной стоимости для множества A = {a1, a2,
…, an}, элементы которого упорядочены следующим
образом: a1<a2<…<an. Нам часто
будет удобно ссылаться на такое дерево как на оптимальное дерево бинарного
поиска для весов q0, p1, q1, p2, …, qn-1, pn, qn. Алгоритм построения оптимального дерева
бинарного поиска, который мы сейчас рассмотрим, основан на следующем свойстве
таких деревьев:
Пусть T – оптимальное дерево бинарного
поиска для весов q0, p1, q1, p2, …, qn-1, pn, qn. Если ak – элемент,
соответствующий корню дерева T, то левое поддерево
корня дерева T является
оптимальным деревом поиска для весов q0, p1, q1, p2, …, pk-1, qk-1, а правое поддерево корня дерева T - оптимальным деревом поиска для весов qk-1,
pk, qk, pk+1, …, qn-1,
pn, qn. Пусть Tij для
Предположим, что ak – корень
дерева Tij(i<j). Тогда,
как мы уже видели, левое поддерево с корнем ak является деревом Ti,k-1, которое в свою очередь является оптимальным
деревом бинарного поиска для множества { qi, pi+1,
…, pk-1, qk-1
}, а правое поддерево с корнем ak является деревом Tkj, которое в
свою очередь является оптимальным деревом бинарного поиска для множества { qk, pk+1, …, pj, qj }. Так как глубина вершины в дереве Tij на единицу больше ее
глубины в дереве Ti,k-1 или Tkj, то отсюда
следует, что
cij = ci,k-1 + wi,k-1 + ckj + wkj + pk = wij + ci,k-1 + ckj.
Ясно, что мы можем найти корень rij дерева Tij, определив величину
Вычислив rij с помощью данного алгоритма, мы можем легко построить
дерево T0n, используя
следующую процедуру:
r0n
– корень дерева T0n.
Если r0n=ak, то ak имеет в качестве левого сына r0,k-1, а в качестве правого – rkn.
Рассмотрим внутреннюю вершину am.
Если rij=am, то am имеет в качестве левого
сына rm,k-1, а в качестве
правого – rmn. Алгоритм Гильберта-Мура
Методы решета
Как можно предположить из самого названия, решето представляет собой метод комбинаторного программирования,
который рассматривает конечное множество и исключает все элементы этого
множества, не представляющие
интереса. Этот метод является логическим дополнением к процессу поиска с
возвращением, описанного в предыдущем разделе, который перечисляет все элементы
множества, представляющие интерес.
Методы решета полезны прежде всего в теоретико-числовых
вычислениях. Например, одним из наиболее известных методов отыскания простых
чисел является «решето Эратосфена». Это решето перечисляет составные (не
простые) числа между N и N2 для некоторого N так, как показано на рис. 4.12 для случая N==6. Просеивание начинается с
выписывания всех чисел от N до N2, и затем из них поэтапно
удаляются составные числа. Сначала удаляются все числа, кратные двум, затем —
все, кратные трем, и т. д. Процесс прекращается после просеивания для
наибольшего простого числа, меньшего N.
Шаг 0 (исходный)
6 7 8 9 10 11 12 13 14
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Шаг 1 (исключаются
кратные 2)
Остались: 7 9 11 13 15
17 19 21 23 25 27 29 31 33 35
Шаг 2 (исключаются кратные
3)
Остались: 7 11 13 17 19
23 25 29 31 35
Шаг 3 (исключаются
кратные 5)
Остались: 7 11 13 17 19
23 29 31
Рис. 4.12. Решето Эратосфена применяемое для отыскания всех простых чисел между 6 и 36: 7, 11, 13. 17,
19, 23, 29, 31
Легко понять, почему
методы решета могут быть полезны. Если в множестве, возможных решений элементы
можно удобно занумеровать натуральными числами, то хранить нужно только
характеристический вектор (см. разд. 2.2.1); в этом векторе i-й разряд равен нулю, если i-й элемент не является решением, и равен единице в противном
случае. Таким образом, на множествах, состоящих буквально из миллионов
элементов, возможен поиск без явного порождения и исследования каждого элемента
множества. Кроме того, в большинстве вычислительных устройств булевы операции
можно производить параллельно над многими разрядами, обеспечивая тем самым
значительную экономию времени.
Нерекурсивное модульное решето
Решето Эратосфена мы можем интерпретировать как поиск всех
чисел между N и N2,
которые одновременно являются членами одной из арифметических прогрессий в
каждом из следующих множеств:
где р — наибольшее
простое число, меньшее или равное N.
Тот факт, что число принадлежит прогрессии вида 2k+1,
означает, что оно нечетно; факт, что оно принадлежит одной из прогрессий 3k+1 или 3k+2, означает, что оно не
является кратным 3, и т. д. Таким образом, между N и N2 простыми являются
только те числа, которые удовлетворяют всем этим условиям.
Рассмотрим еще одну средневековую головоломку о женщине и
яйцах. По пути на базар, куда женщина несла продавать яйца, ее случайно сбил с
ног всадник, в результате чего все яйца разбились. Всадник предложил оплатить
убытки и спросил, сколько у нее было яиц. Женщина сказала, что точного числа
она не помнит, но когда она брала яйца парами, то оставалось одно яйцо. Одно
яйцо оставалось также, когда она брала сразу по 3, 4, 5 и 6 яиц, но когда она
брала сразу по 7 штук, то в остатке ничего не было. Ясно, что она могла бы
иметь N яиц, если и только если N одновременно является членом каждой
из арифметических прогрессий
2k+1, 3k+1, 4k+1, 5k+1, 6k+1 и 7k.
Как могла бы женщина определить все возможные значения числа
яиц, меньшие 1000? Для решения этой задачи можно использовать решето, выписывая
целые числа 1,..., 1000 и затем удаляя все элементы, не принадлежащие
различным прогрессиям: сначала все четные числа, потом числа вида 3k или 3k+2 и т. д. Оставшиеся
числа и будут возможными решениями. Продолжение в лекции 19
|