Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс Лекций.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
982.53 Кб
Скачать

Cost(a1, a2, …, ak-1)  cost(a1, a2, …, ak-1, ak)

Когда стоимость обладает этим свойством, мы можем отбросить частичное решение (a1, a2,, ak), если его стоимость больше или равна стоимости ранее вычисленных решений. Это ограничение легко включается в общий алгоритм поиска с возвращением, как видно из следующего алгоритма 4.4. В большинстве случаев функция стоимо­сти неотрицательная и даже удовлетворяет более сильному требо­ванию

[последнее сохраняемое решение есть решение с наименьшей стоимостью]

Алгоритм 4.4. Общий метод ветвей и границ.

Задача коммивояжера

Задача коммивояжера является типичной задачей оптимизации, которую можно решить с помощью метода ветвей и границ. В этой задаче коммивояжер должен посетить n городов и возвратиться в исходный пункт, при этом требуется минимизировать общую стои­мость путешествия. Переезжая из города i в город j он терпит убы­ток Сij. Например, если матрица стоимостей равна

,

то стоимость пути 1—2—5—7—3—6—4—1 равна 277, в то время как стоимость оптимального пути, скажем 1—4—6—7—3—5—2—1, равна только 126.

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

Эта проверка устраняет просмотр некоторых частей дерева, но на самом деле она достаточно слабая, допускающая

  1. Глубокое проникновение внутрь дерева до того, как ветви обрываются.

  2. Произвольный фиксированный порядок городов является причиной того, что много времени теряется на исследование путей, которые начинаются с маршрута 1—2, если города 1 и 2 отстоят далеко друг от друга.

Почти оптимальные решения следует находить на раннем этапе поиска.

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

  1. те, которые включают выделенную дугу, и

  2. те, которые ее не включают.

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

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

  • Стоимость оптимального решения, конечно, меняется, но сам путь не меняется.

  • Стоимость оптимального решения отличается в точности на количество, вычтен­ное из строки или столбца.

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

может быть преобразована вычитанием 3, 4, 16, 7, 25, 3 и 26 из строк 1—7 соответственно и затем вычитанием 7, 1 и 4 из столбцов 3, 4 и 7 соответственно, в результате чего преобразо­ванная матрица будет равна

.

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

Как этот узел распадается на поддеревья?

Предположим, что мы выбираем дугу 4—6, по которой происходит разбиение дерева. Правое поддерево будет содержать все решения, которые исключают дугу 4—6; зная, что дуга 4—6 исключена, мы можем изменить мат­рицу стоимостей, положив С4,6=. Тогда у получившейся матрицы из четвертой строки можно вычесть 32, и поэтому правое поддерево имеет нижнюю границу 96+32== 128.

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

В результате мы будем иметь матрицу стоимостей на единицу меньшего размера. Далее, посколь­ку все решения в этом поддереве используют дугу 4—6, дуга 6— 4 больше не используется, и нужно положить С6,4=.

Наконец, мы можем теперь вычесть 3 из четвертой строки результирующей мат­рицы (i=5), что дает левому поддереву нижнюю границу 96 + 3 = 99. Бинарное дерево имеет вид, показанный на рис. 4.6.

Рис. 4.6. Расщепление корня бинарного дерева

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

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

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

Применяя метод ветвей и границ к приведенной выше в качестве примера матрице стоимостей, мы замечаем некоторую сложность. Первое включенное ребро есть 4—6, следующее — 3—5 и третье — 2—1 (см. самый левый путь из корня дерева на рис. 4.7). В этом узле нижняя граница равна 112, а преобразованная матрица стои­мостей имеет вид:

Нуль, который при замене на бесконечность позволяет вычесть наибольшее количество из соответствующей строки и столбца, на­ходится на месте i==1, j==4. Поэтому мы расщепляем данный узел, используя ребро 1—4. В левом поддереве мы получаем нижнюю оценку 126 и преобразованную матрицу стоимостей

Теперь в соответствии с описанным нами процессом мы хотим пре­дотвратить использование ребра 4—1, но матрица стоимостей не имеет элемента, соответствующего i =4, j =1. Что делать?

Ответ мы найдем; рассмотрев цель предотвращения использова­ния ребра 4—1. Эта цель состоит в том, чтобы в процессе обхода предотвращать посещение города, в котором мы уже побывали, до тех пор пока по самому последнему ребру не вернемся в исходный город. В нашем случае частичный обход, построенный до сих пор, состоит из пути 2—1—4—6 и ребра 3—5; ясно, что мы должны пред­отвратить использование ребра 6—2, поэтому заменим преобразо­ванную матрицу стоимостей на следующую:

И вообще, если ребро, добавленное к частичному обходу, есть ребро, идущее из iu в j1, и частичный обход содержит пути i1 i2 —....— iu и j1j2—... — jv, то нужно предотвратить использование ребра j1i1.

Применение этого метода к матрице стоимостей, приведенной выше, в качестве примера дает бинарное дерево, показанное на рис. 4.7.

Рис. 4.7. Полное дерево поиска для примера задачи коммивояжера

Заметим, что первое найденное решение оптимально; мы не можем рассчитывать, что это же происходит в общем случае. В данном примере исследуется только 27 узлов; если использовать «очевидную» специализацию общего алгоритма ветвей и границ, то потребуется исследовать 559 узлов. На основе экспериментальных данных можно сделать предположение, что для случайной nn-матрицы расстояний число исследуемых узлов равно О(1,26n).

Метод --отсечений

Теоретическое обоснование метода

Введение

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

История

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

Мак-Карти думал о подобном методе в 1965 г. во время Дартмутской летней исследовательской конференции по искусственному интеллекту, где Бернстайн рассказал об одной из самых ранних шахматных программ, в которой не использовались никакие отсечения. Мак-Карти "критиковал за это программу, но не убедил Бернстайна. В то время спецификации алгоритма подготовлены не были". Очень вероятно, что замечания Мак-Карти, сделанные на этой конференции, привели к тому, что в конце 50-х гг. альфа-бета отсечения стали использовать в шахматных программах.

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

Мак-Карти ввел идентификаторы alpha и beta в своей первой программе на LISP'е, реализующей этот метод. Его программа работала даже более изощренно, чем вышеописанный метод, пос­кольку он предполагал существование двух функций "оптимистическая оценка (p)" и "пессимистическая оценка (p)", которые доставляли верхнюю и нижнюю границы оценки позиции. Программу Мак-Карти, выполняющую те же действия, что приведенная выше процедура F2, можно представить в следующем виде:

if optimistic value(p) <= alpha

then F2 := alpha

else if pessimistic value(p) >= beta

then F2 := beta

else begin <the above body of procedure F2> end.

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

optimistic value(p) = +

и

pessimistic value(p) = -

Он подарил открытие этого факта Харту и Эдвардсу, которые в 1961 году подготовили по этому поводу отчет. В этом неопубликованном отчете они приводят примеры работы общего метода, в том числе, нижних отсечений. Однако (как это было принято в 1961 г.), в нем нет ни обоснования метода, ни указания условий, при которых он работает.

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

Полное описание альфа-бета отсечений появилось в "западной" литературе по вычислениям и компьютерам в 1968 г. в статье Слейгла и Бурского о стратегиях доказательства теорем, однако, это описание страдало нечеткостью и не содержало обсуждения нижних отсечений. Таким образом, можно утверждать, что первое четкое изложение метода на английском языке появилось в 1969 г. в статьях Слейгла и Диксона и Самуэля ; в обеих статьях явно упоминается возможность нижних отсечений и обсуждение идеи включает необходимые детали.

Практика показывает, что очень трудно объяснить метод построения альфа-бета отсечений "на словах" или на обычном математическом языке, поэтому авторы цитированных выше работ были вынуждены прибегать к длинным и сложным описаниям. Более того, при первом знакомстве с методом очень трудно заставить себя поверить в то, что он действительно работает, особенно тогда, когда методы описывают на "обычном" языке и пытаются обосновать возможность нижних отсечений. Быть может, именно поэтому описание метода появилось спустя много лет после того, как он был изобретен. Однако, в разд. 2 мы видели, что метод легко понять и обосновать, если выразить его на алгоритмическом языке; это является хорошим примером случая, в котором "динамический" подход к описанию процесса оказывается значительно более предпочтительным, чем обычный математический.

Очень хорошо метод описан в книгах Нильсона и Слейгла, однако, они представляют метод "в прозе", а не в более легкой для понимания алгоритмической форме. Альфа-бета отсечения стали "широко известными", однако, насколько известно авторам, лишь в двух публикациях процедура описана на алгоритмическом языке. На самом деле, в первой из них, Уэллс приводит не полную альфа-бета процедуру, а нечто даже более слабое, чем процедура F1. (Его алгоритм не только не производит нижние отсечения, но и верхние отсечения производит лишь при выполнении строгого неравенства.) Другая версия алгоритма, принадлежащая Далу и Белснису, появилась в вышедшем на норвежском языке учебнике по структурам данных; однако, альфа-бета метод представлен в ней с использованием параметров-меток, так что доказательство правильности становится достаточно трудным. В другом недавнем учебнике содержится неформальное описание того, что там названо "альфа-бета отсечениями", но снова приведен лишь метод, реализуемый процедурой F1; по-видимому, многие не знают, что альфа-бета процедура способна производить нижние отсечения.

Игры и оценки позиций

Игры двух лиц, а только с ними мы и имеем здесь дело, обычно описывают множеством "позиций" и совокупностью правил перехода из одной позиции в другую, причем предполагают, что игроки ходят по очереди. Будем считать, что правилами разрешены лишь конечные последовательности позиций и что в каждой позиции имеется лишь конечное число разрешенных ходов. Тогда для каждой позиции p найдется число N(p) такое, что никакая игра, начавшаяся в p, не может продолжаться более N(p) ходов - это следует из "леммы о бесконечности" .

Терминальными называются позиции, из которых нет разрешенных ходов. На каждой из них определена целочисленная функция f(p), задающая выигрыш того из игроков, которому принадлежит ход в этой позиции; выигрыш второго игрока считается равным -f(p).

Если из позиции p имеется d разрешенных ходов p1,...,pd, возникает проблема выбора лучшего из них. Будем называть ход наилучшим, если по окончании игры он приносит наибольший возможный выигрыш при условии, что противник выбирает ходы, наилучшие для него (в том же смысле). Пусть F(p) есть наибольший выигрыш, достижимый в позиции p игроком, которому принадлежит очередь хода, против оптимальной защиты. Т.к. после хода в позицию pi выигрыш этого игрока равен -F(pi), имеем

(1)

Эта формула позволяет индуктивно определить F(p) для каждой позиции p.

В большинстве работы по теории игр используется чуть иная формулировка; в ней игроки называются Max и Min и изложение ведется с "точки зрения" игрока Max. Таким образом, если p есть терминальная позиция с ходом Max, его выигрыш равен, как и раньше f(p), если же в терминальной позиции p ходить должен Min, то его выигрыш равен

g(p) = -f(p). (2)

Max пытается максимизировать свой конечный выигрыш, а Min старается минимизировать его. Соотношению (1) при этом соответствуют две функции, именно

(1')

,

которая задает максимальный гарантированный выигрыш игрока Max в позиции p, и

(1'')

которая равна оптимуму, достижимому для игрока Min. Как и раньше, здесь предполагается, что p1,...,pd есть разрешенные в позиции p ходы. Индукцией по числу ходов легко показать, что функции, определяемые соотношениями (1) и (3), совпадают и что для всех p

G(p) = -F(p). (5)

Таким образом, оба подхода эквивалентны.

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

Функция F(p) равна тому максимуму, который гарантирован, если оба игрока действуют оптимально. Следует, однако, заметить, что она отражает результаты весьма осторожной стратегии, которая не обязательно хороша против плохих игроков или игроков, действующих согласно другому принципу оптимальности. Пусть, например, имеются два хода в позиции p1 и p2, причем p1 гарантирует ничью (выигрыш 0) и не дает возможности выиграть, в то время, как p2 дает возможность выиграть, если противник просмотрит очень тонкий выигрывающий ход. В такой ситуации мы можем предпочесть рискованный ход в p2, если только мы не уверены в том, что наш противник всемогущ и всезнающ. Очень возможно, что люди выигрывают у шахматных программ таким именно образом.

Сущность метода

Рассмотрим основные понятия.

  • Дерево игры — это дерево, которое появляется в ре­зультате исследова­ния способом поиска с возвращением всевозмож­ных последовательностей ходов:

  • корень есть начальная конфигура­ция игры;

  • сыновья корня — это возможные положения после хода первого иг­рока; сыновья этих узлов — это возможные положения, после ответного хода вто­рого игрока

Например, на рис. 1 показаны фрагменты дерева игры в крестики-нолики, где первым ставится крестик (); на дереве показаны только неэквивалентные (с точностью до вращения и/или отражения) сыновья узла.

Каждый лист дерева игры представляет собой возможное оконча­ние игры; в случае игры в крестики и нолики окончанием может быть победа крестиков, победа ноликов или ничья. Нас интересует оценка дерева игры с позиций первого игрока; каждый лист помечается значе­нием выигрыша первого игрока: +1 в случае победы. —1 в случае поражения и 0 при ничьей.

Значения других узлов опреде­ляются значениями их сыновей. Для узла N с сыновьями N1, N2, ..., Nk значение V(N) имеет вид:

  • max(V(N1), .. ., V(Nk)), если уровень N четный,

  • min (V(N1), .. ., V(Nk)), если уровень N нечетный.

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

Мы могли бы применить просто очевидную технику возвращения для оценки всех уз­лов дерева и определить значение в корне. Од­нако существует лучший метод, называемый --отсечением, который использует идею метода ветвей и границ для обрывания поддеревьев де­рева игры.

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

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

Такая нижняя граница уровня, в котором вычисляется максимум, называется -значе­нием.

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

В примере на рис.2, как только узлу d присваивается значение 3, сразу же получается -значение 3 для узла m; и мы получаем, что значение V(N) в m будет не больше чем 3.

Располагая такой информацией, мы рассуждаем следующим образом. Если можно по­казать, что значение любого другого потомка узла m будет боль­ше 3, мы можем спокойно игно­рировать оставшуюся часть дерева, расположенную ниже этого потомка.

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

На уровне, в котором вычисляется максимум, имеем сходную ситуацию. Возвращаясь снова к примеру и обнаружив, что для узла m значение равно 3, мы имеем для корня дерева -значение 3, так что, если можно показать, что узел z имеет значение, меньшее 3, мы можем иг­норировать все, что расположено ниже его. Поэтому после уста­новления значения 2 в узле q, мы обрываем оставшуюся часть де­рева, находящуюся ниже узла z, так как оценка не будет больше влиять на окончательное значение корня. Эта процедура назы­вается -отсечением.

Преимущество такой процедуры над алгоритмом исчерпывающе­го поиска очевидно: вместо всех нам нужно проверить только часть узлов дерева. Однако мы быстро замечаем, что на количество сэко­номленной работы сильно влияет порядок узлов дерева, так как этот порядок определяет время появления - и -отсечений.

Алгоритм альфа-бета отсечения

Нижеследующий алгоритм вычисляет F(p) в соответствии с определением (1):

integer procedure F(position p):

begin integer m,i,t,d;

Определить позиции p1,...,pd, подчиненные p;

if d = 0 then F := f(p) else

begin m := -;

for i:= 1 step 1 until d do

begin t := -F(pi);

if t > m then m:= t;

end;

F := m;

end;

end.

Здесь + обозначает число, которое не меньше abs(f(p)) для любой терминальной позиции p; поэтому - не больше F(p) и -F(p) для всех p. Этот алгоритм вычисляет F(p) на основе "грубой силы": для каждой позиции он оценивает все возможные продолжения; "лемма о бесконечности" гарантирует окончание вычислений за конечное число ходов.

Перебор, который осуществляет этот алгоритм, можно уменьшить, используя идею метода "ветвей и границ". Идея состоит в том, что можно не искать точную оценку хода, про который стало известно, что он не может быть лучше, чем один из ходов, рассмотренных раньше. Пусть, например, в процессе перебора стало известно, что F(p1) = -10. Мы заключаем отсюда, что F(p)>=10, и потому нам не нужно знать точное значение F(p2), если каким-либо образом мы как-то узнали, что F(p2)>=-1 (и, таким образом, что -F(p2)<=10). Итак, если p21 допустимый ход из p2 и F(p21)<=10, мы можем не исследовать другие ходы из p2. В терминах теории игр ход в позицию p2 "опровергается" (относительно хода в p1), если у противника в позиции p2 есть ответ, по крайней мере столь же хороший, как его лучший ответ в позиции p1. Ясно, что если ход можно опровергнуть, мы можем не искать наилучшее опровержение.

Эти рассуждения приводят к алгоритму, гораздо более экономному, чем F. Определим F1 как процедуру с двумя параметрами p и bound; наша цель - удовлетворить следующим условиям:

F1(p, bound) = F(p), если F(p) < bound, (6)

F1(p, bound) > bound, если F(p) > bound.

Эти условия не определяют F1 полностью, однако, позволяют вычислить F(p) для любой начальной позиции p, поскольку из них следует, что

F1(p,+ ) = F(p). (7)

Идею метода ветвей и границ реализует следующий алгоритм:

integer procedure F1(position p, integer bound):

begin integer m,i,t,d;

Определить позиции p1,...,pd, подчиненные p;

if d = 0 then F1 := f(p) else

begin m := -;

for i:= 1 step 1 until d do

begin t := -F1(pi, -m);

if( t > m then m := t;

if m >= bound then goto done;

end;

done: F1 := m;

end;

end.

Правильность работы этой процедуры и то, что результат удовлетворяет соот-ношениям (6), можно доказать следующими рассуждениями. Легко видеть, что в начале i-го витка for-оператора выполнено "инвариантное" условие

m = max { -F(p1), ..., -F(pi-1)}. (8)

Здесь предполагается, что максимум по пустому множеству даст -. Поэтому, если -F(pi) > m, то, используя условие (6) и индукцию по длине игры, начинающейся в p, получаем F1(pi,-m) = F(pi). Следовательно, в начале следующего витка (8) также будет выполнено. Если же max {-F(p1,...,-F(pi)} >= bound при всех i, то F(p) >= bound. Отсюда следует, что (6) выполняется для всех p.

Эту процедуру можно еще улучшить, если ввести не только верхнюю, но и нижнюю границу. Эта идея - ее называют минимаксной альфа-бета процедурой или просто альфа-бета отсечениями - является значительным продвижением по сравнению с односторонним методом ветвей и границ. (К сожалению, она применима не во всех задачах, где используется метод ветвей и границ; она работает только при исследовании игровых деревьев.) Определим процедуру F2 с тремя параметрами p, alpha и beta (причем всегда будет выполнено alpha < beta), которая удовлетворяет следующим условиям, аналогичным (6):

F2(p,alpha,beta) <= alpha, если F(p) < alpha, (9)

F2(p,alpha,beta) = F(p), если alpha < F(p) < beta,

F2(p,alpha,beta) >= beta, если F(p) >= beta.

И снова эти условия не определяют F2 полностью, однако, из них следует, что

F2(p,-, +) = F(p). (10)

Оказывается, представление этого улучшенного алгоритма на языке программирования лишь чуть-чуть отличается от алгоритмов F и F1:

integer procedure F2(position p, integer alpha, integer beta):

begin integer m,i,t,d;

Определить позиции p1,...,pd, подчиненные p;

if d = 0 then F2 := f(p) else

begin m := alpha;

for i:= 1 step 1 until d do

begin t := -F2(pi, -beta, -m);

if t > m then m := t;

if m >= beta then goto done;

end;

done: F2 := m;

end;

end.

Динамическое прогамирование

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

При решении таких задач с помощью динамического программи­рования мы основываемся на принципе оптимальности:

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

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

Для задачи коммивояжера определим

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)=Сijj1. (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) представляет множество всех элементов , для которых ai<x<ai+1; 2) b0 представляет собой множество всех элементов , для которых x<a1; 3) bn представляет собой множество всех элементов , для которых x>an.

Расширенным деревом бинарного поиска для A называется дерево бинарного поиска для A с n+1 листьями, представляющими элементы множества B. Отметим, что в расширенном дереве бинарного поиска листья появляются слева направо в порядке b0, b1, …, bn. В дальнейшем будем называть расширенное дерево бинарного поиска для множества A просто деревом бинарного поиска для A.

Пусть дано подмножество A универсума S и дерево T бинарного поиска для A. Множество S может быть множеством всех слов над английским алфавитом, а упорядочение может быть лексикографическим порядком. Предположим, что нам необходимо определить, принадлежит ли элемент множеству A. Сравним x с элементом, соответствует корню дерева T. При этом могут возникнуть четыре случая, в соответствии с которыми мы продолжаем:

Случай 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 для является оптимальным деревом бинарного поиска для множества { qi, pi+1, qi+1, …, pj, qj }. Пусть rij – корень дерева Tij, а cij – стоимость дерева Tij. Вес wij дерева Tij определяется как qi+(pi+1+ qi+1)+…+(pj+qj). Tii будет деревом, состоящим из листа bi. Поэтому сii=0, а wii=qi.

Предположим, что 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, определив величину , которая минимизирует cij. Это рассуждение образует основу алгоритма, который позволяет рассчитать rij и cij в порядке возрастания величин j-i. Этот алгоритм предложен Гильбертом и Муром.

Вычислив rij с помощью данного алгоритма, мы можем легко построить дерево T0n, используя следующую процедуру:

r0n – корень дерева T0n. Если r0n=ak, то ak имеет в качестве левого сына r0,k-1, а в качестве правого – rkn.

Рассмотрим внутреннюю вершину am. Если rij=am, то am имеет в качестве левого сына rm,k-1, а в качестве правого – rmn.

Алгоритм Гильберта-Мура

  1. Даны неотрицательные веса p1, p2, …, pn и q0, q1, …, qn. Для i=0, 1, 2, …, n положить wii=qi, cii=0, rii=bi.

  2. Для l=1, 2, …, n выполнить шаг 3.

  3. Для i=0, 1, 2, …, n-l выполнить шаг 4.

  4. Положить j=i+l, wij=wi,j-1+pj+qj. Пусть m – значение , для которого сумма ci,k-1+ckj минимальна. Положить cij=wij+ci,m-1+cmj, rij=am.

  5. Конец процедуры

Методы решета

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

Методы решета полезны прежде всего в теоретико-числовых вычислениях. Например, одним из наиболее известных методов отыскания простых чисел является «решето Эратосфена». Это реше­то перечисляет составные (не простые) числа между 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 и т. д. Оставшиеся числа и будут возможными решениями.

И решето Эратосфена, и решето для средневековой головоломки являются специальными случаями обобщенного модульного решета. Пусть m1, т2, т3, ..., mtмножество из t целых чисел (называе­мых модулями). Для каждого mi рассмотрим пi арифметических прогрессий

mik+аij, j=1,2, .... ni.

Задача состоит в отыскании всех целых чисел, заключенных в пределах между A и B, которые для каждого mi, одновременно принадлежат одной из ni прогрессий. В решете Эратосфена мы имеем m1=2, m2=3, m3=5,..., mi=р (наибольшее простое число, меньшее или равное N), ni=mi-1 и аij=j. В задаче о женщине и яйцах mi=i+1, ni=l, 1 i 6, ai1=1, 1 i 5, a6,1=0.

Если модули попарно взаимно просты, то мы можем объединить все прогрессии и получить одно множество из Пn1; прогрессий с мо­дулем Пmi. Это осуществляется повторным применением следующей техники объединения прогрессий m1k+a1 и m2k+а2 в одну прогрес­сию т1m2k+а. Поскольку по предположению наибольший общий делитель т1 и m2 равен единице, алгоритм Евклида гарантирует существование целых чисел и и v, таких, что m1u+m2v=1. Выбирая a=m1ua2-m2va1, мы находим, что x a(mod т1m2), если и только если x a1(mod m1) и х а2 (mod m2). Таким образом, для того чтобы определить, какие целые числа из отрезка между А и В удовлет­воряют требованиям одной из ni прогрессий для каждого mi, мы можем взять каждое целое из этого отрезка, разделить его на Пmi и посмотреть, является ли остаток одним из Пni приемлемых остат­ков. Конечно, мощность вновь получившегося множества быстро растет, так быстро, что, имея только 10 множеств из трех прогрес­сий каждое, можно получить одно множество из n=310=59049 прогрессий. Следовательно, пришлось бы вычислять эти 59049 зна­чений и затем просматривать их для каждого остатка, который будет получен. Это большая работа, и обычно более практично использо­вать решето.

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

,

мы получаем, что

F1000000 < 10200000

т. е. F1000000 имеет более 200000 десятичных знаков. Очевидно, «ло­бовой» метод порождения первого миллиона чисел Фибоначчи и проверки каждого из них, не является ли оно полным квадратом, не осуществим, но методом решета вычисление можно легко провести за несколько минут.

Вычисление начинается с установления в памяти миллиона раз­рядов для характеристического вектора, i-й разряд представляет целое число Fi; если этот разряд равен единице, то F, может быть квадратом, если он равен нулю, то Fi квадратом быть не может. Сначала все разряды будут единицами, и в процессе отсеивания на каждом шаге некоторые из этих разрядов становятся нулями. После окончания просеивания, если окажется, что какой-либо из разрядов равен единице, то соответствующее число Фибоначчи нужно исследовать на предмет, не является ли оно полным квадра­том. Миллион разрядов памяти, требующихся для характеристичес­кого вектора,— это много, но не чрезмерно; например, в машине с 32-разрядными словами потребуется только 31250 слов памяти.

Рассмотрим последовательность Фибоначчи по модулю р, где р — простое число. Пусть это будет последовательность P1, P2, P3, , определяемая следующим образом:

Р1 =Р2 = 1, Pi+1=Pi-1 +Pi(mod p).

Эта последовательность периодическая, причем период начинается с P1=l. Для доказательства этого утверждения рассмотрим p2+l упорядоченных пар целых чисел

(Р1, Р2), (Р2 , Рз), .... (Pp2, Pp2+1), (Pp2+1, Pp2+2).

Поскольку по модулю р существует только р2 различных упорядо­ченных пар целых чисел, две из этих пар должны совпадать. Пусть (Рr, Pr+1) и (Ps, Ps+1) будут самыми левыми совпадающими парами

(Рr, Рr+1) = (Рs, Рs+1), r<s.

Если r>1, то мы имеем

(Рr-1, Pr) = (Ps-1, Ps),

что противоречит предположению о том, что (Pr, Рr+1) и (Ps, Ps+1) — самые левые совпадающие пары. Таким образом, числа Фибоначчи образуют по модулю р периодическую последователь­ность, начинающуюся с P1 и такую, что Рi=Рi+nTp для некоторого Tp и всех n 0.

По модулю любого числа п существует несколько квадратов (обычно называемых квадратичными вычетами по модулю п1) и несколько неквадратов. Например, 0, 1, 2 и 4 являются квадра­тичными вычетами по модулю 7, в то время как 3, 5 и 6 являютса квадратичными невычетами. Таким образом, любое из чисел m=3, 5-или 6 (mod 7) не может быть квадратом, потому что оно является квадратичным невычетом по модулю 7. Поскольку для каждого простого р числа Фибоначчи по модулю р образуют периодическую последовательность с периодом Тp, мы производим отсеивание, используя арифметические прогрессии Tpk+i для каждого i, та­кого, что Рi является квадратичным невычетом по модулю р. Для иллюстрации процесса отсеивания рассмотрим случай р=7. Ряд Фибоначчи по модулю 7 представляет собой последовательность из 16 элементов

1, 1, 2, 3, 5, 1, 6, 0, 6, 6, 5, 4, 2, 6, 1, 0,

повторяющихся периодически с периодом Т7==16. Поскольку 3, 5 и 6 — квадратичные невычеты, мы знаем теперь, что F4+16n, F5+16n, F7+16n , F9+16n , F10+16n, F11+16n и F14+16n не являются квадратами для. n 0, так что значения в разрядах, соответствующих этим числам. можно поменять с единицы на нуль.

После отсеивания квадратичных невычетов первых 32 простых чисел мы обнаруживаем, что все значения разрядов, кроме перво­го, второго и двенадцатого, изменены на нули. Не изменившие зна­чений разряды соответствуют трем числам F1=F2= 1 и F12=144; поэтому можно заключить, что среди первого миллиона чисел Фибоначчи только они являются квадратами. На самом деле дока­зано, что только они являются квадратами среди чисел Фибоначчи.

Рекурсивное решето

Существует много решет, в которых модули m1, т2, ... заранее не заданы; значение тi будет зависеть от чисел, не удаленных после просеивания по модулю mi-1. Многие решета строятся рекурсив­ным образом. Фактически, решето Эратосфена обычно так и стро­ится: после выписывания чисел 2, 3, ..., N мы вычеркиваем все числа, кратные 2, кроме самой двойки. Затем, поскольку наименьшее оставшееся число, чьи кратные остались неудаленными, равно трем, удаляются все числа, кратные трем, кроме самой тройки, и т. д.

Заметим, что на каждом шаге первое удаленное число является квадратом числа, с которого начинается просеивание; например, первым числом, исключаемым двойкой, будет 4, тройкой — 9 и т. д. Когда число, относительно которого производится просеивание, становится больше , никакие числа уже не могут быть удалены, и процесс заканчивается.

Обычно мы удваиваем размер ячеек решета Эратосфена, осуще­ствляя предпросеивание для двойки; другими словами, мы начинаем только с нечетных чисел, отсеивая кратные 3, 5, 7, 11 и т. д. Пусть Х — двоичный набор; тогда рекурсивный вариант решета Эратосфена (с предпросеиванием для двойки) для нечетных простых чисел до 2N+1 показан в алгоритме 4.5.

Х  (1, 1, .... 1)

for k=3 to by 2 do

[X(k-1)/2 представляет нечетное число k]

if X(k-1)/2 =1 then for i =k2 to 2N+1 by 2k do X(i-1)/20

for k=1 to N do if Xk=1 then вывести 2k+1

Алгоритм 4.5. Решето Эратосфена для нечетных простых чисел.

Другое хорошо известное рекурсивное решето порождает счаст­ливые числа. Из списка чисел 1, 2, 3, 4, 5, ... удаляется каждое второе число, в результате чего получается список 1, 3, 5, 7, 9. Поскольку тройка является первым числом (исключая единицу), которое не использовано в качестве просеивающего, из оставшихся чисел мы удаляем каждое третье число, получая в результате спи­сок 1, 3, 7, 9, 13, 15, 19, 21,... .Теперь удаляется каждое седьмое число, в результате чего получается список 1, 3, 7, 9,13, 15, 21, ... . Числа, которые никогда не удаляются из списка, называются «счаст­ливыми».

Несмотря на большую схожесть решета Эратосфена и решета для получения счастливых чисел, последнее реализуется труднее. Труд­ность возникает потому, что числа, отсеиваемые на k-м шаге, позиционно зависят от еще неисключенных элементов, а не от элемен­тов первоначального множества. В предположении, что для пред­ставления чисел используется двоичная последовательность в решете для счастливых чисел нужно будет считать, скажем, каждый седь­мой единичный разряд вместо вообще седьмого разряда. Эта задача существенно облегчается, если использовать бирки. Вместо исполь­зования полного машинного слова для представления возможных решений, которые мы должны просеять, мы используем часть этого слова для хранения счетчика числа единиц в остальной части слова. Например, в ЭВМ со словом, состоящим из четырех байтов, каждый из которых состоит из восьми разрядов, мы можем использовать слово так, как это показано на рис. 4.13. Правый байт содержит счетчик числа единичных разрядов в трех левых байтах. С исполь­зованием бирок отыскание t-го невычеркнутого элемента (t-гo еди­ничного разряда) легко осуществляется путем суммирования бирок до тех пор, пока сумма S не станет большей или равной t; соответствующий элемент будет (St+1)-м единичным разрядом справа в левых байтах последнего слова, чья бирка суммировалась.

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

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

Рекурсивное решето несколько другого характера можно ис­пользовать для вычисления следующей последовательности U. Вна­чале U=(1, 2). Если вопрос о вхождении в U решен для всех целых чисел, меньших п, то пU тогда и только тогда, когда п является суммой единственной пары различных элементов из U. Таким обра­зом U=(1, 2, 3, 4, 6, 8, 11, 13, 16, 18, 26,...). Эту последова­тельность можно вычислить с помощью двух параллельных просеи­ваний; целое должно быть просеяно (должно быть суммой, полу­чаемой не менее чем одним способом) и не должно быть отсеяно (дол­жно быть суммой, получаемой не более, чем одним способом). Используем два двоичных вектора. Для i>2

Хi=1, если и только если i представимо в виде суммы не менее чем одним способом.

Уi=1, если и только если i представимо в виде суммы не более чем одним способом.

«Двойное решето» для вычисления элементов U до некоторого числа N приводится в алгоритме 4.6. Значение счетчика k увеличи­вается до тех пор, пока оно не достигнет первого целого числа, большего предыдущего элемента из U, представимого точно одним способом. Это целое принадлежит U, и поэтому разряды, соответ­ствующие всем целым k+i для i<.k, должны быть обновлены.

X  (1, 1, 0, 0, ....,0)

У  (1, 1, 1, 1, ..., 1)

k  1

whil k<N do

for i=1 to N do if Xi Yi=1 then вывести i

Алгоритм 4.6. Двойное решето для последовательности U.

Литература

Рейнгольд Э. и др. Комбинаторные алгоритмы: теория и практика.

Ахо А., Хопкрофт Дж., Ульман Дж. Построение и анализ вычислительных алгоритмов.

Гудман С., Хидетниеми С. Введение в разработку и анализ алгоритмов.

Вирт Н. Алгоритмы + структуры данных = программы.

Гэри М., Джонсон Д. Вычислительные машины и труднорешаемые задачи.

Niemann, Tomas. Сортировка и поиск: Рецептурный справочник Перевод с английского П.Н.Дубнер (infoscope@glasnet.ru).

Сибуя М., Ямамото Е. Алгоритмы обработки данных.

Aho, Alfred V. and Jeffrey D. Ullman [1983]. Data Structures and Algorithms. Addison-Wesley, Reading, Massachusetts.

Cormen, Thomas H., Charles E. Leiserson and Ronald L. Rivest [1990]. Introduction to Algorithms. McGraw-Hill, New York.

Knuth, Donald E. [1998]. The Art of Computer Programming, Volume 3, Sorting and Searching. Addison-Wesley, Reading, Massachusetts.

Pearson, Peter K. [1990]. Fast Hashing of Variable-Length Text Strings. Communications of the ACM, 33(6):677-680, June 1990.

Pugh, William [1990]. Skip Lists: A Probabilistic Alternative to Balanced Trees. Communications of the ACM, 33(6):668-676, June 1990.

Глоссарий

Term

Термин

sort by insertion

сортировка вставками

replacement selection

выбор с замещением

polyphase merge sort

многофазное слияние

array

массив

linked list

линейный список

comparison

сравнение

in-place

на месте (без дополнительных массивов)

stable sort

устойчивая сортировка

external sort

внешняя сортировка

underflow

вырождение

overflow

переполнение

red-black tree

красно-черное дерево

B-tree

Б-дерево

skip list

слоёный список

rotation

вращение

76