Основы теории алгоритмов - Учебно

advertisement
Министерство образования и науки Российской Федерации
Федеральное агентство по образованию
Ярославский государственный университет им. П.Г. Демидова
В.С. Рублев
Основы теории алгоритмов
Учебное пособие
Допущено
учебно-методическим советом
по прикладной математике и информатике
УМО по классическому университетскому образованию
в качестве учебного пособия для студентов высших учебных
заведений, обучающихся по специальности 010200
“Прикладная математика и информатика”
и по направлению 510200
“Прикладная математика и информатика”
Ярославль 2005
УДК 510.5
ББК В181я73
Р82
Рекомендовано
Редакционно-издательским советом университета
в качестве учебного издания. План 2005 года
Рецензенты:
кандидат физико-математических наук, доцент кафедры теории и методики
обучения информатике Ярославского государственного педагогического
университета П.А. Корнилов;
кафедра прикладной математики и вычислительной техники Ярославского
государственного технического университета
Р82
Рублев, В.С. Основы теории алгоритмов: учеб. пособие.
/ В.С. Рублев; Яросл. гос. ун-т. – Ярославль: ЯрГУ, 2005. – 143 с.
ISBN 5-8397-0382-6
В учебном пособии излагаются основы алгоритмической грамотности (уточнение понятия алгоритма и алгоритмическая неразрешимость,
анализ сложности алгоритмов, построение и анализ алгоритмов сортировки и поиска информации, выделение класса труднорешаемых задач).
С целью усвоения материала и развития алгоритмических навыков в
каждом разделе даются упражнения для самостоятельной работы.
Предназначено для студентов, обучающихся по специальности 010200
Прикладная математика и информатика и направлению 510200 Прикладная математика и информатика (дисциплина “Информатика”, блок
ЕН). Может быть использовано для других специальностей и дисциплин специализации.
Табл. 1. Ил. 24. Библиогр. 24 назв.
УДК 510.5
ББК В181я73
ISBN 5-8397-0382-6
c Ярославский
°
государственный
университет, 2005
c
° В.С. Рублев, 2005
Оглавление
Предисловие
6
1 Определение алгоритма
1.1 Проблема определения алгоритма . . . . . . . . . . . .
1.2 Машины Тьюринга . . . . . . . . . . . . . . . . . . . . .
1.2.1 Описание машины Тьюринга . . . . . . . . . . .
1.2.2 Тезис Тьюринга . . . . . . . . . . . . . . . . . .
1.2.3 Упражнения . . . . . . . . . . . . . . . . . . . .
1.3 Частично-рекурсивные функции . . . . . . . . . . . . .
1.3.1 Исходные функции . . . . . . . . . . . . . . . .
1.3.2 Операция суперпозиции C . . . . . . . . . . . .
1.3.3 Операция примитивной рекурсии Pr . . . . . . .
1.3.4 Операция минимизации µ . . . . . . . . . . . .
1.3.5 Определение и примеры . . . . . . . . . . . . . .
1.3.6 Тезис Чёрча . . . . . . . . . . . . . . . . . . . .
1.3.7 Нумерация наборов . . . . . . . . . . . . . . . .
1.3.8 Совместная рекурсия . . . . . . . . . . . . . . .
1.3.9 Упражнения . . . . . . . . . . . . . . . . . . . .
1.4 Нормальные алгоритмы Маркова . . . . . . . . . . . . .
1.4.1 Определение нормального алгоритма и его выполнение . . . . . . . . . . . . . . . . . . . . . .
1.4.2 Возможности нормальных алгоритмов и тезис
Маркова . . . . . . . . . . . . . . . . . . . . . . .
1.4.3 Упражнения . . . . . . . . . . . . . . . . . . . .
1.5 Эквивалентность моделей алгоритма . . . . . . . . . .
1.5.1 Общие черты моделей алгоритмов . . . . . . . .
1.5.2 Вычисление частично-рекурсивной функции
на машине Тьюринга . . . . . . . . . . . . . . .
3
7
7
12
12
16
22
24
24
25
26
28
29
31
32
33
34
36
36
38
41
41
41
43
1.5.3
1.5.4
Арифметизация машин Тьюринга . . . . . . . .
Частичная рекурсивность вычислимой по Тьюрингу функции . . . . . . . . . . . . . . . . . . .
1.6 Алгоритмическая неразрешимость . . . . . . . . . . . .
1.6.1 Массовые алгоритмические проблемы . . . . . .
1.6.2 Метод сводимости . . . . . . . . . . . . . . . . .
1.6.3 Проблема самоприменимости . . . . . . . . . . .
1.6.4 Упражнения . . . . . . . . . . . . . . . . . . . .
1.7 Литература . . . . . . . . . . . . . . . . . . . . . . . . .
45
48
50
50
50
51
54
54
2 Сложность алгоритмов
2.1 Характеристики сложности алгоритмов . . . . . . . . .
2.2 Трудоемкость алгоритмов . . . . . . . . . . . . . . . . .
2.2.1 Определение трудоемкости алгоритма . . . . . .
2.2.2 Оценка трудоемкости алгоритма . . . . . . . . .
2.2.3 Анализ алгоритмов и методика оценивания трудоемкости . . . . . . . . . . . . . . . . . . . . . .
2.2.4 Характеристики временной сложности программы
2.2.5 Упражнения . . . . . . . . . . . . . . . . . . . .
2.3 Трудоемкость задач . . . . . . . . . . . . . . . . . . . .
2.3.1 Определение трудоемкости задачи . . . . . . . .
2.3.2 Задача поиска в упорядоченном массиве . . . .
2.3.3 Задача сортировки массива сравнением его элементов . . . . . . . . . . . . . . . . . . . . . . . .
2.3.4 Упражнения . . . . . . . . . . . . . . . . . . . .
2.4 Литература . . . . . . . . . . . . . . . . . . . . . . . . .
56
56
57
57
58
3 Сортировка и поиск
3.1 Способы организации информации для поиска . .
3.2 Сортировка массива . . . . . . . . . . . . . . . . .
3.2.1 Виды сортировки . . . . . . . . . . . . . . .
3.2.2 Основные методы внутренней сортировки
3.2.3 Внешняя сортировка . . . . . . . . . . . . .
3.2.4 Упражнения . . . . . . . . . . . . . . . . .
3.3 Поисковые деревья . . . . . . . . . . . . . . . . . .
3.3.1 Бинарные деревья и поиск . . . . . . . . .
77
77
78
78
78
86
90
91
91
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
61
65
69
70
70
71
72
75
76
3.3.2 Сбалансированные АВЛ-деревья . . . .
3.3.3 Сбалансированные (3-2)-деревья . . . .
3.3.4 Внешний поиск по дереву и B-деревья
3.3.5 Упражнения . . . . . . . . . . . . . . .
3.4 Функции расстановки и хеширование . . . . .
3.4.1 Функции расстановки . . . . . . . . . .
3.4.2 Хеширование . . . . . . . . . . . . . . .
3.4.3 Хеширование с открытой адресацией .
3.4.4 Упражнения . . . . . . . . . . . . . . .
3.5 Литература . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
94
100
103
104
105
105
106
107
109
110
4 Класс NP трудоемкости задач
4.1 Труднорешаемые задачи . . . . . . . . . . . . . . . . . .
4.2 Класс NP задач и недетерминированная машина Тьюринга . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Взаимоотношение между классами P и NP . . . . . . .
4.4 Полиномиальная сводимость
и NP-полные задачи . . . . . . . . . . . . . . . . . . . .
4.5 Теорема Кука . . . . . . . . . . . . . . . . . . . . . . . .
4.6 Методы доказательства NP-полноты . . . . . . . . . .
4.6.1 Локальная замена и NP-полнота задачи
3-ВЫПОЛНИМОСТЬ . . . . . . . . . . . . . . .
4.6.2 Построение компонент и NP-полнота задач
ВЕРШИННОЕ ПОКРЫТИЕ и КЛИКА . . . . .
4.6.3 Метод сужения . . . . . . . . . . . . . . . . . . .
4.6.4 Список некоторых известных NP-полных задач
4.7 Упражнения . . . . . . . . . . . . . . . . . . . . . . . . .
4.8 Литература . . . . . . . . . . . . . . . . . . . . . . . . .
111
111
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
113
118
119
123
130
130
133
136
137
140
142
Предисловие
Настоящее учебное пособие посвящено одному из центральных понятий в образовании специалиста по прикладной математике и информатике. Компьютерная программа представляет собой выражение
алгоритма решения задачи на одном из алгоритмических языков. Но
всегда ли возможна разработка алгоритма? Если для решения определенной задачи возможна разработка алгоритмов, то как их сравнить и как выбрать лучший? Как определить эффективность алгоритма и трудоемкость решения задачи? Есть ли задачи, для которых
не существует эффективных алгоритмов? Ответы на эти вопросы
составляют основу алгоритмической грамотности, и они рассматриваются в данном пособии.
Глава 1 посвящена уточнению понятия «алгоритм» и существованию алгоритмически неразрешимых проблем. В главе 2 рассматриваются характеристики сложности алгоритмов и задач. Глава 3 посвящена задачам поиска информации, ее сортировки и организации;
здесь рассматриваются многочисленные алгоритмы для этих задач.
Глава 4 посвящена выделению класса задач, для которых построение
эффективных алгоритмов, по всей видимости, невозможно.
Небольшой объем учебного пособия не позволяет подробно и полно осветить все вопросы теории алгоритмов. В конце каждой главы
приведен список литературы, позволяющий студенту расширить свои
знания по материалу главы.
Для усвоения и закрепления материала в конце каждой главы
и многих разделов приведены упражнения, которые рекомендуется
выполнить.
6
Глава 1
Определение алгоритма
1.1 Проблема определения алгоритма
Под алгоритмом решения задачи принято понимать описание вычислительного процесса, приводящего к ее решению. Этот термин
обязан имени арабского математика начала IX века Мухамеда бен
Мусы по прозвищу ал-Хорезми (из Хорезма), который в своем трактате «Хисаб ал-джебр вал-мукабала» в словесной форме дал правила
решения алгебраических уравнений 1-й и 2-й степени 1 .
С этих пор алгоритм описывается как последовательность вычислительных шагов, каждый из которых определяет элементарные действия над исходными данными задачи и промежуточными величинами, вводимыми в описание алгоритма, а также определяет, какой шаг
будет выполняться следующим. Такое определение алгоритма принято называть неформальным. До начала XX века казалось, что такого
определения вполне достаточно. Но в 1900 году на математическом
конгрессе в Париже великий немецкий математик Давид Гильберт в
своем докладе о будущем развитии математики определил ряд проблем, которые XIX век оставил XX веку (эти 23 проблемы получили
название проблем Гильберта). Некоторые из них формулировались
как проблемы разработки алгоритмов. Например, десятая проблема
Гильберта заключалась в разработке алгоритма решения диофантовых уравнений (алгебраические уравнения или системы уравнений с
рациональными коэффициентами, решения которых ищутся в рациональных числах). Долгое время многие из этих проблем не подда1
Термин алгебра происходит от второго слова в названии трактата.
7
вались решению, и к началу 30-х годов XX века возникло сомнение
в том, можно ли построить алгоритм для их решения. Но получение математического доказательства невозможности построения алгоритма решения некоторой проблемы требует формализации понятия «алгоритм». Чтобы разобраться в трудностях формализации понятия «алгоритм», прежде всего рассмотрим свойства этого понятия,
которые справедливы для указанного неформального определения и
должны обязательно быть приняты во внимание при формальном
определении.
В первую очередь следует отметить, что каждый алгоритм является способом решения некоторой потенциально бесконечной совокупности задач. Действительно, если рассматривать конечную совокупность задач, то, перенумеровав задачи, получив каким-либо образом
(не обязательно алгоритмическим) решение каждой из них и перенумеровав эти решения, можно построить способ, который каждой
задаче из этой совокупности по ее номеру определяет решение с тем
же номером. Такой способ выбора решения не следует понимать как
алгоритм. Бесконечная совокупность задач, для которой определяется алгоритм, характеризуется выбором исходных данных каждой
ее задачи из некоторого бесконечного набора данных. Отмеченное
первое свойство назовем массовостью алгоритма.
Второе свойство понятия «алгоритм» характеризует его как совокупность отдельных шагов и называется дискретностью алгоритма.
Каждый шаг алгоритма связан с входными данными шага, которыми являются как исходные данные алгоритма, так и выходные
данные предыдущих выполненных шагов. Каждый шаг алгоритма
связан также с выходными данными шага, которые однозначно образуются из его входных данных элементарным действием. Это характеризует третье и четвертое свойства алгоритма как детерминированность и элементарность шагов алгоритма.
Результатом выполнения шага алгоритма являются не только выходные данные шага, но и номер следующего выполняемого шага. Во
многих случаях следующим при выполнении является шаг, номер которого на 1 больше, чем номер выполняемого шага. Но в некоторых
случаях единственное действие алгоритма – определение следующе8
го шага для выполнения. Это пятое свойство алгоритма называется
направленностью алгоритма.
Число шагов алгоритма при его выполнении должно быть конечным, что составляет его шестое свойство – конечность числа
шагов 2 . Это не означает, что при выполнении алгоритма каждый
шаг должен выполняться только 1 раз. Некоторые шаги могут выполняться лишь при выполнении условия, которое проверяется на
предыдущем шаге. Это дополнительное свойство называется ветвлением алгоритма. Некоторые шаги алгоритма могут выполняться
многократно, если следующим выполняемым шагом становится один
из предыдущих шагов. Такое дополнительное свойство называется
цикличностью алгоритма. Среди шагов алгоритма обязательно должен быть шаг, прекращающий выполнение алгоритма. Выходные
данные этого шага и являются результатом выполнения алгоритма.
Если исходные данные алгоритма таковы, что при его выполнении
никогда не выполняется шаг, прекращающий вычисления, то говорят,
что алгоритм зациклился на этих исходных данных и что алгоритм
недопустим для этих исходных данных (некорректные данные)3 .
Рассмотрим в качестве примера описание схемы Горнера вычисления значения полинома y = a0 · xn + a1 · xn−1 + ... + an−1 · x + an .
Исходными данными в этом алгоритме являются степень полинома
n, вектор коэффициентов {a0 , a1 , ..., an−1 , an } и значение аргумента x.
Описание алгоритма дается следующей последовательностью шагов:
1. Положить y = a0 , k = 1.
2. Если k > n, перейти к шагу 4.
3. Положить y = y · x + ak , k = k + 1; перейти к шагу 2.
4. Закончить вычисления с результатом y.
Массовость этого алгоритма демонстрируется его применением к любому полиному и любому аргументу из бесконечной совокупности
полиномов и значений аргументов. Дискретность и конечность обеспечивается выделением 4 шагов алгоритма. Элементарность шагов
Это свойство иногда называют результативностью алгоритма.
Корректный алгоритм должен содержать шаг проверки исходных данных и
отвергать некорректные данные.
2
3
9
алгоритма следует из простоты вычислений каждого шага. Направленность обеспечивается указанием следующего шага по умолчанию
(для шага 1 и для шага 2, если не выполнено условие), либо указанием следующего шага (для шага 3 и для шага 2, если выполнено
условие), либо указанием прекращения вычислений (шаг 4). Дополнительное свойство ветвления алгоритма использовано на шаге 2, а
свойство цикличности алгоритма использовано в организации цикла
шагов 2 и 3.
С процессом выполнения алгоритма связано понятие конфигурации выполнения. Конфигурация описывается номером шага при выполнении алгоритма, значением всех исходных данных и всех промежуточных величин алгоритма и результата, записанных в определенном порядке. Так, для вышеописанного примера определим конфигурацию как следующую совокупность данных:
< N, x, n, a0 , a1 , ..., an , k, y >,
где N – номер шага. Перед выполнением алгоритма N = 0. В результате вычисления каждого шага конфигурация изменяется. Выполнению алгоритма соответствует последовательность конфигураций.
Например, для вычисления значения y = 52 − 2 · 5 + 1 следующая последовательность конфигураций описывает выполнение алгоритма:
< 0, 5, 2, 1, −2, 1, ?, ? >
< 1, 5, 2, 1, −2, 1, 1, 1 >
< 2, 5, 2, 1, −2, 1, 1, 1 >
< 3, 5, 2, 1, −2, 1, 2, 3 >
< 2, 5, 2, 1, −2, 1, 2, 3 >
< 3, 5, 2, 1, −2, 1, 3, 16 >
< 2, 5, 2, 1, −2, 1, 3, 16 >
< 4, 5, 2, 1, −2, 1, 3, 16 > .
Подводя итог анализу свойств алгоритма, мы можем уточнить его
определение:
10
Алгоритм есть описание вычислительного процесса решения задачи, обладающее свойствами массовости, дискретности, детерминированности, направленности, конечности и элементарности.
С информационной точки зрения алгоритм есть средство переработки информации. Для каждого набора входной информации, который
является корректными исходными данными для алгоритма, он (алгоритм) однозначно определяет выходную информацию. Эта функциональная связь наборов входной информации с наборами выходной
информации позволяет нам рассматривать алгоритм как частичную
функцию, которую мы будем называть интуитивно вычислимой
функцией. Основной вопрос теории алгоритмов можно сформулировать следующим образом: является ли любая заданная функция
интуитивно вычислимой, т. е. для любой ли функции можно построить алгоритм, который осуществляет вычисление значения этой
функции? Этот вопрос эквивалентен следующему: если произвольная задача предполагает однозначное решение для своих исходных данных, то всегда ли существует алгоритм решения этой
задачи? Для ответа на этот вопрос необходимо знать точно, что
такое алгоритм. Поэтому прежде всего нужно заняться формализацией понятия «алгоритм».
Проанализируем возможность формализации алгоритма, при которой сохраняются свойства неформального определения. Не вызывает трудностей формализация исходных данных и промежуточных
величин алгоритма. Свойства дискретности, направленности и конечности числа шагов алгоритма обеспечиваются последовательной
декомпозицией алгоритма, нумерацией его шагов и определением
следующего выполняемого шага, что также можно формализовать.
Но требование элементарности шага алгоритма вызывает трудность.
Неясно, какие действия следует считать элементарными. Если зафиксировать формально действия, которые мы считаем элементарными, то возникает опасность, не сузили ли мы такой формализацией
возможность описывать любые алгоритмы. В этом состоит основная
проблема формализации понятия «алгоритм».
Начиная с 30-х годов XX в. было разработано много подходов
11
формализации понятия «алгоритм»: частично-рекурсивные функции
(С.К. Клини, К. Гёдель, А.Чёрч), машины Тьюринга (А.М. Тьюринг), нормальные алгоритмы Маркова (А.А. Марков), алгоритмы
Поста (Э.Л. Пост), лямбда-исчисления (А.Чёрч). Каждый из подходов по-своему определял элементарность шага алгоритма, но замечателен тот факт, что все эти определения оказались эквивалентными,
т. е. описывающими одну и ту же совокупность объектов, называемых алгоритмами. В следующих трех разделах этой главы описаны три первых из перечисленных подходов, а в четвертом разделе
обосновывается эквивалентность формализации аппаратом частичнорекурсивных функций и формализации аппаратом машин Тьюринга.
1.2 Машины Тьюринга
1.2.1
Описание машины Тьюринга
В 1936 г. английский математик Алан Тьюринг описал схему абстрактной машины и предложил назвать алгоритмом то, что умеет
делать эта машина. Машины Тьюринга – это не реальные вычислительные машины, а способ описания алгоритма, при помощи которого формализуется понятие алгоритма и его можно исследовать.
От реальных машин они отличаются бесконечной памятью. Устройство этих машин намеренно выбрано «бедным», лишь бы оно было
универсальным и позволяло доказывать, что для определенных задач
не существует такой машины (т. е. нельзя построить алгоритм). Реальные вычислительные машины (компьютеры), наоборот, должны
быть богатыми по устройству и уметь хорошо (эффективно) решать
задачи.
В основе описания устройства машины Тьюринга (МТ) лежат три
ее составные части:
1. Память в виде бесконечной в обе стороны ленты, разделенной
на ячейки, в каждую из которых может быть записана либо одна
из букв внешнего алфавита {a1 , a2 , ..., ak }, либо пустой символ
(будем для единообразия обозначать его греческой буквой Λ
и добавим его к алфавиту как символ a0 ). Внешний алфавит
A = {a0 , a1 , ..., ak } может быть своим для каждой МТ, но всегда
12
конечным. На любом шаге работы МТ только конечный участок
ленты может содержать буквы внешнего алфавита.
2. Автомат, состоящий из устройства управления и считывающей головки, который на каждом шаге работы МТ передвигается вдоль ленты памяти на 1 ячейку влево, вправо или остается на месте. Движение автомата будем обозначать элементами
множества движений D = {L, R, N }.
3. Считывающая головка, которая на любом шаге работы МТ
обозревает какую-либо ячейку памяти.
4. Устройство управления, которое на каждом шаге работы МТ
может находиться в одном из конечного числа своих состояний, множество {q1 , q2 , ..., qn } которых называется внутренним
алфавитом.
Работа МТ может быть описана следующим образом:
1. В начальный момент работы МТ лента памяти находится в начальном состоянии (конечное непустое слово на ленте называется входным словом), автомат находится в начальном состоянии
q1 и считывающая головка обозревает самый левый непустой
символ входного слова.
2. На каждом шаге работы МТ головка считывает символ ai ∈ A
из обозреваемой ячейки и в зависимости от него и от состояния
qj ∈ Q устройства управления автомат:
a) записывает в обозреваемую ячейку символ ai0 ∈ A (может,
и не изменяет его),
b) изменяет состояние устройства управления на qj 0 ∈ Q (может, и не изменяет его),
c) осуществляет движение в направлении d ∈ D (может, остается на месте).
3. Если в результате предыдущих шагов автомат переходит в такое
состояние, при котором:
a) состояние автомата не изменяется,
13
b) символ в обозреваемой ячейке не изменяется,
c) движение автомата вдоль ленты памяти не происходит,
то машина переходит в заключительное состояние останова.
Для упрощения мы такое состояние обозначим через q0 и переход к нему будем обозначать как переход к состоянию q0 .
Это состояние мы также включим в множество состояний Q =
{q0 , q1 , ..., qn }. Слово, которое получается при переходе МТ к состоянию останова, будем называть выходным словом.
Действия автомата на каждом шаге работы МТ могут быть описаны
тремя функциями:
ai0 = a(ai , qj ), возвращающая значение записываемого символа ai0 ,
если в состоянии qj был прочитан символ ai ;
qj 0 = q(ai , qj ), возвращающая состояние qj 0 , в которое переходит
МТ после того, как в состоянии qj был прочитан символ ai ;
d = d(ai , qj ), возвращающая направление движения d после того,
как в состоянии qj был прочитан символ ai .
Все 3 функции должны быть определены на множестве A×(Q−{q0 }).
Табличное задание этих функций называется функциональной схемой МТ. В этой прямоугольной таблице (см. рис. 1) размера n × k
на пересечении j-й строки (j = 1, n), отвечающей состоянию qj , и
i-го столбца (i = 0, k), отвечающего символу ai , находится тройка
0
ai0 , qj 0 , d , определяющая изменение символа в обозреваемой ячейке,
изменение состояния МТ и движение МТ на шаге работы МТ.
Λ a1 ...
ai
... ak
q1
... ... ... ...
...
... ...
qj
ai0 qj 0 d
... ... ... ...
...
... ...
qn
Рис. 1
С каждым шагом работы МТ свяжем конфигурацию МТ, которая состоит из последовательности символов слова, записанного на
14
ленте МТ перед выполнением шага, и состояния МТ, записанного
перед обозреваемым символом на этом шаге. Например, конфигурация abq2 aba означает, что в момент выполнения шага на ленте
находится слово ababa, МТ обозревает второй символ a и находится в состоянии q2 , а конфигурация ababaq3 означает, что на ленте
находится такое же слово, но МТ в состоянии q3 обозревает пустой
символ справа от слова на ленте.
МТ называется применимой к входному слову, если, начав работу
в начальном состоянии, она через конечное число шагов перейдет в
состояние останова, и неприменимой, если при ее работе она не переходит в состояние останова ни за какое конечное число шагов. МТ
может быть неприменима ни к одному слову. Например, МТ, определенная функциональной таблицей на рис. 2, все время движется
вдоль ленты вправо, ничего не меняя.
q1
Λ
0
1
Λ q1 R 0 q1 R 1 q1 R
Рис. 2
МТ может быть применима к любому слову. Например, МТ, заданная функциональной таблицей на рис. 3, стирает все символы входного слова и останавливается.
q1
Λ
0
1
Λ q0 N Λ q1 R Λ q1 R
Рис. 3
В дальнейших примерах мы для большей наглядности не будем в
клетке таблицы определять
– заменяемый символ, если он не меняется,
– новое состояние, если оно не меняется,
– движение, если оно не происходит.
Введем также столбец для комментариев действий МТ в том или
ином состоянии. Рассмотрим в качестве примера разработку МТ, которая увеличивает произвольное десятичное число на 1. Неформальный пошаговый алгоритм можно определить следующим образом:
1. Определить последнюю цифру числа (для этого двигаться впра15
во до пустого символа и, перейдя к следующему состоянию, вернуться к предыдущей цифре).
2. Увеличить цифру (последнюю) на 1, если она меньше (не равна) 9, и закончить работу МТ; если же цифра равна 9, то заменить
ее 0 и перейти к увеличению предыдущей цифры (единица переноса), для чего повторить действия этого шага.
Функциональная таблица МТ представлена на рис. 4.
q1
q2
Λ
0
1 ... 8
9
q2 L R
R ... R
R поиск последней цифры
1 q0 1 q0 2 q0 ... 9 q0 0 L увеличение на 1 и перенос
Рис. 4
Каждая МТ на множестве входных слов задает частичную функцию f , которая любому применимому входному слову P ставит в
соответствие выходное слово f (P ). Эта функция называется вычислимой по Тьюрингу. Ясно, что каждая вычислимая по Тьюрингу
функция является интуитивно вычислимой, т. е. каждая МТ определяет алгоритм. Но, может быть, существуют алгоритмы, для которых
построение МТ невозможно? В следующем разделе мы исследуем
возможности МТ и покажем, что не видно ограничений для описания алгоритмов при помощи аппарата МТ.
1.2.2
Тезис Тьюринга
Рассмотрим пример МТ, которая для входного слова, образованного
любым натуральным числом палочек, стирает самую правую палочку. Функциональная таблица этой МТ представлена на рис. 5.
q1
q2
Λ
|
q2 L R
q0 Λ q0
поиск последней палочки
стирание палочки и останов
Рис. 5
Теперь постараемся объединить 2 последние МТ в одну, которая
подсчитывает число палочек во входном слове. Для этого можно
число считаемых палочек располагать левее самой левой палочки
16
входного слова и соединить 2 алгоритма стирания правой палочки и
увеличения левого числа (палочек) на 1 следующим образом:
1. Выполняем 1-й алгоритм стирания правой палочки, но не останавливаемся, а переходим к следующему шагу.
2. Ищем самую последнюю цифру числа (если ее нет, то 1-й пустой символ справа) и увеличиваем число на 1 при помощи 2-го
алгоритма. На этом не останавливаемся, а переходим к следующему
шагу.
3. Ищем палочку справа от числа и, если ее нет, заканчиваем работу; иначе переходим к шагу 1.
На рис. 6 представлена функциональная таблица МТ подсчета палочек.
q1
q2
q3
q4
Λ
0
...
8
9
|
q2 L
R
...
R
R
R
q0
q0
...
q0
q0 Λ q3 L
1 q4 R 1 q4 R ... 9 q4 R 0 L
L
q0
R
...
R
R
q1 R
Рис. 6
к последней палоч.
стир.пал.или стоп
к посл.ц.и увел.ч.
нет палоч. – стоп
Для проверки алгоритма построенной МТ нужно задать достаточный
набор тестов и проверить на нем выполнение алгоритма. Например,
работу построенной МТ подсчета палочек демонстрирует следующий
пример:
q1 ||| → |q1 || → ... → |||q1 → ||q2 |→ |q3 | → q3 || →1|q4 | → 1||q1 → 1|q2 |
→ 1q3 | → q3 1| → 2q4 | → 2|q1 → 2q2 | → q3 2 → 3q4 → 3q0
Указанный пример построения МТ показывает, что из уже имеющихся МТ можно построить новые МТ, соединяя их различным образом. Так, в примере применено последовательное соединение МТ,
стирающей палочку и увеличивающей число на 1, которое затем повторяется в цикле. Неформальное пошаговое определение алгоритма
показывает 4 способа соединения алгоритмов:
1. Последовательное соединение, или суперпозиция, когда за
выполнением шагов первого алгоритма следует выполнение шагов второго алгоритма и входной информацией для второго алгоритма служит выходная информация первого алгоритма.
17
2. Композиция, когда 2 алгоритма параллельно и независимо выполняют работу над входной информацией и результатом их работы является выходная информация обоих алгоритмов.
3. Ветвление, когда в зависимости от выполнения условия для
входной информации, проверяемого первым алгоритмом, выполняется или не выполняется второй алгоритм.
4. Цикл, когда при проверке первым алгоритмом выполнения условия для входной информации соединения алгоритмов или выходной информации предыдущего выполнения второго алгоритма
этот второй алгоритм снова выполняется в цикле или происходит выход из цикла с выходной информацией второго алгоритма.
Мы покажем, что МТ также можно соединить подобным образом,
приведя лишь идеи построения таких соединений 4 .
Суперпозиция МТ. Пусть M1 и M2 – две машины Тьюринга,
вычисляющие функции f1 (P ) и f2 (P ). Тогда можно построить машину Тьюринга M = M2 (M1 ), вычисляющую суперпозицию функций f2 (f1 (P )). Она определена тогда и только тогда, когда определены f1 (P ) и f2 на множестве значений f1 (P ).
Доказательство. Не ограничивая общность рассуждений, можно считать, что M1 заканчивает работу, когда ее головка позиционирует
самый левый символ выходного слова, так как в противном случае
можно заменить эту МТ на эквивалентную (вычисляющую ту же
функцию), удовлетворяющую этому требованию. Сделать это можно, добавив в конце работы переход к дополнительному состоянию,
в котором отыскивается пустой символ слева от левого непустого
символа выходного слова и производится сдвиг вправо с остановкой.
Пусть M1 имеет множество состояний {q0 , q1 , ..., qn1 }, а M2 имеет
множество состояний {q0 , q1 , ..., qn2 }. Для построения суперпозиции
M:
1) переименуем состояния M2 , заменяя q1 на qn1 +1 ,..., qn2 на qn1 +n2 ;
2) напишем вторую функциональную таблицу под первой;
Полные описания построений соединений МТ, использующие другие способы,
можно найти в [3, 12].
4
18
3) изменим значения тех ячеек первой функциональной таблицы,
в которых осуществляется переход к заключительному состоянию
q0 , изменив этот переход на переход к начальному состоянию второй
таблицы qn1 +1 .
В результате мы получим требующуюся машину M .
Композиция МТ. Пусть M1 и M2 – две машины Тьюринга, вычисляющие функции f1 (P ) и f2 (P ). Тогда можно построить машину Тьюринга M = M1 ∗ M2 , которая выполняет работу обеих
МТ и вычисляет функцию f (P ) = f1 (P ) ∗ f2 (P ), где символ * не
встречается в алфавите M1 и M2 (f (P ) не определена, если не
определены f1 (P ) или f2 (P )).
Доказательство. Рассмотрим сначала следующую идею образования
M:
1) построим МТ M3 , которая копирует входное слово P в виде
P ∗ P (упражнение 1.2.3.1);
2) напишем таблицу M2 под таблицей M1 ;
3) изменим действия M1 на пустом правом символе на такие же
действия на символе *;
4) изменим переход к заключительному состоянию M1 на переход
к начальному состоянию M2 с движением влево от символа *;
5) изменим действия M2 на пустом левом символе на такие же
действия на символе *.
Описанная идея некорректна в пункте 3, если действия M1 связаны с одним из следующих:
1) с заменой пустого символа на некоторый непустой (при этом
исчезнет символ *, разделяющий левую часть слова, обрабатываемую
M1 , и правую часть слова, обрабатываемую M2 , и соединение станет
неверным);
2) со стиранием символа слева от пустого (замена на символ *
приведет к тому, что обе части слова для обработки M1 и M2 будут
разделены более чем одним символом *).
Для того чтобы исправить некорректность в первом случае, введем для каждого состояния qj МТ M1 , где этот случай имеет место,
r
МТ Mjc
с множеством состояний {qj1 , . . . , qjm , qj0 } (qj1 – начальное,
а qj0 – конечное состояния), которая сдвигает вправо на 1 ячейку
19
правую часть слова, начинающуюся с символа *, записывая в освободившуюся ячейку символ λ (который не встречается в алфавите
МТ M1 и M2 ), и останавливается, позиционируя этот символ (упражнение 1.2.3.2). Пусть теперь функциональная таблица МТ M1 в состоянии qj при чтении пустого символа справа определяется тройкой
(ai , qj 0 , d). В преобразованной M1 эта тройка в строке qj и столбце,
отвечающем символу *, заменим на (λ, qj1 , N ), где qj1 – начальное
r
состояние МТ Mjc
. Добавим в функциональную таблицу M1 строку
r
qj0 , отвечающую конечному состоянию МТ Mjc
, и для дополнительно введенного столбца символа λ этой строки определим тройку
(ai , qj 0 , d). Тогда действия, отвечающие пункту 3 в этом случае, станут корректны.
Для того чтобы исправить некорректность пункта 3 во втором
случае (стирание символа слева от символа *, заменившего пустой
символ), введем для каждой пары (j, i), где qj – состояние, в котором
r
стирается символ ai , МТ Mjid
с множеством состояний {q1ji , . . . , q0ji }
(q1ji – начальное, а q0ji – конечное состояния), которая сдвигает
часть слова справа от символа * вместе с ним на 1 ячейку влево,
стирая символ слева (упражнение 1.2.3.3). Пусть теперь в некотором
состоянии M1 при чтении пустого символа переходит в состояние qj ,
сдвигая головку влево, и в состоянии qj стирает символ ai , сдвигая
после этого головку в направлении dji и переходя в состояние qji .
В преобразованной МТ M1 тройку (Λ, qji , dji ) заменим на (λ, q1ji , R),
где λ – символ, не встречающийся в алфавите МТ M1 и M2 , который
r
МТ Mjid
заменит на символ *. Добавим в функциональную таблиr
цу M1 строку q0ji , отвечающую конечному состоянию МТ Mjid
, и
для столбца символа * этой строки определим тройку (∗, qji , dji ). Тогда действия, отвечающие пункту 3 во втором случае, также станут
корректными.
Так же как для пункта 3 конструирования МТ M , требуются изменения, связанные с действиями МТ M2 при выполнении пункта 5.
Для этого аналогичным образом для каждого состояния qj МТ M2 ,
l
где необходимо добавлять символ слева, вводится МТ Mjc
, которая
сдвигает влево на 1 ячейку левую часть слова, заканчивающуюся
символом *, записывается в освободившуюся ячейку символ λ и производятся аналогичные изменения функциональной таблицы МТ M2 .
20
Аналогичным образом для каждой пары (j, i), где qj – состояние M2 ,
l
в котором стирается символ ai , строится МТ Mjid
, которая сдвигает
часть слова справа от удаляемого символа на 1 ячейку влево, стирая
этот символ. Соответствующие изменения функциональной таблицы
МТ M2 приводят к корректным действиям при выполнении пункта 5.
В результате мы получим машину M , которая сначала перерабатывает в f1 (P ) первую копию слова P , а затем перерабатывает в
f2 (P ) вторую копию слова P , что и требовалось.
Ветвление МТ. Пусть M1 и M2 – две машины Тьюринга, вычисляющие функции f1 (P ) и f2 (P ), причем множеством значений
функции f1 (P ) является множество {F, T } (F – ложь, T – истина). Тогда можно построить машину Тьюринга M = M1 →M2 , которая перерабатывает слово P в f2 (P ), если f1 (P ) = T , и оставляет входное слово без изменений, если f1 (P ) = F .
Доказательство. Машину M можно реализовать следующим образом:
1) построим композицию M1 ∗ M2 ;
2) изменим в этой композиции переход от работы машины M1 к
работе машины M2 так, что если после работы M1 образуется слово
F ∗ P , то M1 стирает символы F ∗ и переходит к заключительному
состоянию; а если после работы M1 образуется слово T ∗ P , то M1
стирает символы T ∗ и переходит к начальному состоянию M2 и продолжает работу, перерабатывая слово P в слово f2 (P ).
Цикл МТ. Пусть M1 и M2 – две машины Тьюринга, вычисляющие функции f1 (P ) и f2 (P ), причем множеством значений функции f1 (P ) является множество {F, T } (F – ложь, T – истина).
Тогда можно построить машину Тьюринга M = M1 ◦M2 , которая
выполняет следующую последовательность действий:
- вычисляет f1 (P ), и если f1 (P ) = T , то вычисляет новое значение P := f2 (P ), а если f1 (P ) = F , то переходит к заключительному состоянию c выходным словом P (возможно новое значение);
- повторяет действия предыдущего пункта до тех пор, пока
для очередного значения слова P не будет выполнено f1 (P ) = F .
21
Доказательство. Машину M можно реализовать следующим образом:
1) образуем ветвление машин M1 →M2 ;
2) изменим заключительное состояние M2 , заменив его переходом
к копированию получившегося слова f2 (P ) и добавив после этого переход к начальному состоянию M1 .
В результате мы получим машину, которая реализует цикл МТ.
Описанные операции над машинами Тьюринга показывают, что
можно строить все более сложные МТ. Поскольку можно реализовать арифметические и логические операции (см. упражнения раздела 1.2.3), то возникает уверенность в том, что любая интуитивно
вычислимая функция является вычислимой по Тьюрингу. Именно в этом и состоит тезис Тьюринга. Его не следует рассматривать
как некоторое утверждение, требующее доказательства, а следует
понимать как определение алгоритма. Для того чтобы убедиться в
корректности такого уточнения понятия «алгоритм», мы рассмотрим
другие уточнения этого понятия и вопрос об эквивалентности уточнений.
1.2.3
Упражнения
1. Определите функциональную таблицу МТ, которая копирует
любой двоичный код, т. е. любое входное слово P из нулей
и единиц перерабатывает в слово P ∗ P . Указание: сначала в
конце входного слова поставить символ *, а затем перед копированием каждого символа исходного слова слева направо
временно заменять символ 0 на A, а символ 1 – на B и переходить к различным последовательностям состояний для
копирования такого символа в конце слова; после завершения копирования всех символов, когда левее символа * нет
символов 0 или 1, произвести обратную замену символов A
на 0 и B на 1.
2. Определите функциональную таблицу МТ Mcr , которая в начальном состоянии при чтении символа * сдвигает вправо на
1 ячейку правую часть слова от этого символа (вместе с ним),
22
записывая в освободившуюся ячейку символ λ, не встречающийся в алфавите входного слова.
3. Определите функциональную таблицу МТ Mdr , которая в начальном состоянии при чтении символа слева от символа * сдвигает влево на 1 ячейку правую часть слова от этого символа *
(вместе с ним), стирая указанный символ слева.
4. Определите функциональную таблицу МТ, которая заменяет
любой двоичный код на реверсивный, т. е. идущий в обратном
направлении. Указание: подобно алгоритму копирования осуществлять копирование символов, но справа (от символа *)
налево и со стиранием копируемого символа; затем стереть
символ *.
5. Определите функциональную таблицу МТ, которая инвертирует
любой двоичный код (0 заменяет на 1, а 1 – на 0).
6. Определите функциональную таблицу МТ, которая уменьшает
десятичное число на 1.
7. Определите функциональную таблицу МТ, которая осуществляет сложение двух двоичных чисел, разделенных символом *.
Указание: поочередно одно из чисел уменьшать на 1, а другое увеличивать до тех пор, пока первое число не исчезнет;
затем стереть символ *.
8. Определите функциональную таблицу МТ, которая осуществляет сложение столбиком двух двоичных чисел, разделенных
символом +. Указание: для запоминания единицы переноса
переходить к другой последовательности состояний МТ.
9. Определите функциональную таблицу МТ, которая осуществляет вычитание столбиком двух двоичных чисел, разделенных
символом −. Указание: см. предыдущее упражнение.
10. Определите функциональную таблицу МТ, которая осуществляет умножение двоичного числа на степень числа 2, разделенных
символом *. Указание: использовать сдвиг кода первого числа
с добавлением 0 в конце и вычитанием 1 из второго числа.
23
11. Определите функциональную таблицу МТ, которая осуществляет умножение столбиком двух двоичных чисел, разделенных
символом *. Указание: см. предыдущее упражнение и сложение столбиком.
12. Определите функциональную таблицу МТ, которая осуществляет деление двоичного числа на степень числа 2, разделенных
символом >, и получает частное и остаток, разделенные символом «,». Указание: использовать сдвиг кода первого числа с
отделением последней цифры символом «,» и вычитанием 1
из второго числа.
13. Определите функциональную таблицу МТ, которая осуществляет деление столбиком двух двоичных чисел, разделенных символом /.
14. Определите функциональную таблицу МТ, которая осуществляет логическое сложение двух двоичных кодов, разделенных
символом |.
15. Определите функциональную таблицу МТ, которая осуществляет логическое умножение двух двоичных кодов, разделенных
символом ×.
16. Определите функциональную таблицу МТ, которая осуществляет сложение по модулю 2 двух двоичных кодов, разделенных
символом ⊕.
1.3
1.3.1
Частично-рекурсивные функции
Исходные функции
Мы будем рассматривать совокупность функций от целочисленных
неотрицательных аргументов с целыми неотрицательными значениями. Причем функции совокупности могут быть определены не для
всех целочисленных неотрицательных значений аргументов, т. е.
быть частичными. В этой совокупности выделим класс частичнорекурсивных функций, которые можно получать из трех исходных
24
функций совокупности при помощи трех операций. При этом, если
такая функция всюду определена, то мы будем называть ее рекурсивной. Исходными для определения частично-рекурсивных функций являются следующие рекурсивные функции:
1. O(x) = 0 — функция нуля, которая всюду определена и ставит
любому значению аргумента x значение 0.
2. s(x) = x + 1 — функция следования, которая всюду определена и ставит любому значению аргумента следующее значение в
расширенном натуральном ряде.
n
(x1 , . . . , xn ) = xm — функция выбора, которая всюду опре3. Im
делена для n (n > 0) своих аргументов и ставит любому набору значений этих аргументов значение аргумента с номером
m (m ∈ 1, n).
Операции, при помощи которых из исходных функций нуля, следования и выбора получаются любые другие частично-рекурсивные
функции, носят названия суперпозиции C, примитивной рекурсии
P r и минимизации µ. Дадим их определения в указанном порядке.
1.3.2
Операция суперпозиции C
Рассмотрим суперпозицию функций
f (x1 , . . . , xm ) = g(f1 (x1 , . . . , xn ), . . . , fm (x1 , . . . , xn )).
Если функции g(x1 , . . . , xm ), f1 (x1 , . . . , xn ), . . . , fm (x1 , . . . , xn ) частичнорекурсивны, то суперпозиция этих функций также является частичной функцией целочисленных неотрицательных аргументов и, по
данному определению, частично-рекурсивна. Получение функции f
из функций g, f1 , . . . , fm мы будем обозначать схемой:
f = C (g, f1 , . . . , fm ).
Заметим, что если функции g, f1 , . . . , fm являются полными (всюду определены), то функция f также является полной. Но если одна
из функций g, f1 , . . . , fm является частичной, то частичной может
25
быть и функция f их суперпозиции. Отметим также, что функции f1 , . . . , fm могут иметь разные аргументы и их число, так как
при помощи функции выбора мы можем ввести фиктивные аргументы. Например, пусть даны функции g(u, v), f1 (x, y), f2 (x, z), и мы
желаем определить функцию h(x, y, z) как их суперпозицию. Так как
x = I13 (x, y, z), y = I23 (x, y, z), z = I33 (x, y, z), то можно определить
h(x, y, z) = g(f1 (I13 (x, y, z), I23 (x, y, z)), f2 (I13 (x, y, z), I33 (x, y, z)).
Пример 1. Помимо уже включенных трех исходных функций в
определяемый класс частично-рекурсивных функций можно включить множество всех целых неотрицательных констант. Действительно, 0 = O(x),
1 = s(O(x)), . . . , k = s(s(. . . s( O(x)) . . . )). Последняя функция опре| {z }
k
деляется схемой: C(s, C(s, C(. . . C(s, O(x)) . . . ))).
Пример 2. Функция тождества y(x) = x является рекурсивной,
так как y(x) = I11 (x), т. е. представима через исходную функцию.
1.3.3
Операция примитивной рекурсии Pr
Пусть функции g(x1 , . . . , xn ), h(x1 , . . . , xn , y, z) являются частичнорекурсивными. Тогда примитивной рекурсией этих функций называется функция f = Pr (g, h), определяемая следующим образом:

 f (x1 , . . . , xn , 0) = g(x1 , . . . , xn ),
f (x1 , . . . , xn , y) =
f (x1 , . . . , xn , y + 1) =

= h(x1 , . . . , xn , y, f (x1 , . . . , xn , y)).
При этом при вычислении значения функции для некоторого значения аргумента y = p производятся последовательные вычисления
значений функции для y = 0, 1, 2, . . . , p. Но если какое-либо значение в этой последовательности окажется неопределенным (в силу
частичности функций g и h), то значение функции f также становится неопределенным для этого значения аргумента y.
Пример 3. Функцию сложения x+y можно определить следующим
образом:
½
x + 0 = x = I12 (x, y),
x + (y + 1) = (x + y) + 1 = s(x + y).
26
Поэтому можно взять g(x, y) = I12 (x, y), h(x, y, z) = s(x+y), где такие
функции уже определены как рекурсивные. И тогда схема функции
суммы x + y = Pr (I12 (x, y), s(x + y)).
Пример 4. Функцию умножения x · y можно определить следующим образом:
½
x · 0 = 0 = O(I12 (x, y)),
x · (y + 1) = x · y + x.
Поэтому можно взять g(x, y) = O(I12 (x, y)), h(x, y, z) = x+z = x+x·y,
где такие функции уже определены как рекурсивные, и тогда схема
функции умножения x · y = Pr (O(I12 (x, y)), x + x · y).
Пример 5. Функцию факториал n! можно определить следующим
образом:
½
0! = 1,
(n + 1)! = (n + 1) · n! .
Поэтому можно взять g(n) = s(O(n)), h(n, z) = s(n) · z = s(n) · n!,
где такие функции уже определены как рекурсивные. И тогда схема
функции факториал n! = Pr (s(O(n)), s(n) · n!).
Пример 6. Показательную функцию xy можно определить следующим образом:
½ 0
x = 1 = s(O(x)),
xy+1 = x · xy .
Поэтому можно взять g(x, y) = s(O(x)), h(x, y, z) = x · z = x · xy ,
где такие функции уже определены как рекурсивные. И тогда схема
показательной функции xy = Pr (s(O(x)), x · xy ).
Пример 7. Функцию сигнум (знака) sg(x) можно определить следующим образом:
½
sg(0) = 0 = O(x),
sg(x) =
sg(x) = 1 = s(O(x)), x > 0.
Поэтому можно взять g(x) = O(x), h(x, z) = h(x, sg(x)) = s(O(x)),
где такие функции уже определены как рекурсивные. И тогда схема
функции сигнум sg(x) = Pr (O(x), s(O(x)) ).
Пример 8. Функцию антисигнум sg(x) можно определить следующим образом:
½
sg(0) = 1 = s(O(x)),
sg(x) =
sg(x) = 0 = O(x), x > 0.
27
Поэтому можно взять g(x) = s(O(x)), h(x, z) = h(x, sg(x)) = O(x),
где такие функции уже определены как рекурсивные. И тогда схема
функции антисигнума sg(x) = Pr (s(O(x)), O(x) ).
Pk
Пример 9. Функцию суммы
i=1 xi = sum(x1 , . . . , xk , k) можно
определить с помощью примитивной рекурсии следующим образом:
sum(x1 , . . . , xk , k) =
½
sum(x1 , . . . , xk , 0) = 0 = O(x)
=
k
sum(x1 , . . . , xk , y + 1) = sum(x1 , . . . , xk , y) + Im+1
(x1 , . . . , xk ).
Пример 10. Функция одного переменного, отличная от 0 в конечном числе точек x1 , . . . xk , является рекурсивной, так как ее можно
определить следующим образом:
f (x) = sum(f (x1 ) · sg|x − x1 |, . . . , f (xk ) · sg|x − x1 |, k).
1.3.4
Операция минимизации µ
Если при помощи операции примитивной рекурсии можно накапливать результат, что соответствует циклическому алгоритму, то при
помощи операции минимизации можно получать обратные функции.
Эта операция определяется следующим образом.
Пусть имеется частично-рекурсивная функция g(x1 , . . . , xn−1 , y).
Фиксируем набор (x1 , . . . , xn−1 ) всех аргументов, кроме последнего,
и рассмотрим уравнение относительно y:
g(x1 , . . . , xn−1 , y) = xn ,
с помощью которого мы определим новую функцию f (x1 , . . . , xn ).
Для этого будем искать минимальный корень уравнения, вычисляя
последовательно g(x1 , . . . , xn−1 , 0), g(x1 , . . . , xn−1 , 1), . . . и т.д. до тех
пор, пока
– либо не найдем решение,
– либо не обнаружится, что очередное значение функции g не вычисляется для указанных аргументов (в силу частичности этой функции),
– либо мы не убедимся, что решения нет.
Решение, если оно есть, обозначим через µy (g(x1 , . . . , xn−1 , y) = xn )
28
и определим его как значение функции f (x1 , . . . , xn ). Если же решения не найдено (решения нет или для некоторого значения y из
поледовательности 0, 1, 2,. . . не определена функция g), то значение
функции f при этих значениях аргументов является неопределенным
(частичность функции f ).
Операция минимизации µ(g) определяет для частично-рекурсивной функции g(x1 , . . . , xn−1 , y) другую частично-рекурсивную
функцию
f (x1 , . . . , xn ) = µy (g(x1 , . . . , xn−1 , y) = xn ).
√
Пример 11. Функция x является обратной для функции x2 . Определим эту функцию через операцию минимизации для функции
x2 :
√
x = µy (y 2 = x).
Эта функция определена для значений x = 0, 1, 4, 9, . . . и не определена, например, для значения x = 2, так как вычисляемые значения
y 2 = 0, 1, 4, . . . растут монотонно и, следовательно, никогда не примут значения 2.
Пример 12. Функция xy является обратной для функции x·y. Определим эту функцию через операцию минимизации для функции x · y:
x
= µz (y · z = x).
y
Эта функция определена для любых значений x при y = 1 и для любых значений y при x = 0 (деление 0 на 0 дает 0), но не определена
уже для всех значений x, если y 6= 1.
Пример 13. Функция x − y является обратной для функции x + y.
Определим эту функцию через операцию минимизации для функции
x + y:
x − y = µz (y + z = x).
Эта функция определена для любых значений x ≥ y.
1.3.5
Определение и примеры
Частично-рекурсивной функцией называется целочисленная неотрицательная функция с целочисленными неотрицательными аргу29
ментами, которая может быть получена из исходных функций
n
O(x), s(x), Im
(x1 , . . . , xn ) при помощи операций C, P r, µ.
Приведем еще ряд примеров.
Пример 14. Функцию усеченной разности x . y (обычная разность не определена для x < y) можно определить следующим образом:
½
x − y, x ≥ y
.
x y=
0,
x < y.
Ее рекурсивность следует из рекурсивности
½ .
0 1 = 0 = O(x),
.
x 1=
(x + 1) . 1 = x,
и тогда следующая примитивная рекурсия определяет x
½
x . 0 = x,
.
x y=
x . (y + 1) = (x . y) . 1.
.
y
Пример 15. Функцию симметричной разности |x − y| можно определить следующим образом:
|x − y| = (x
.
y) + (y
.
x).
Эта функция рекурсивна как сумма рекурсивных функций (рекурсивность суммы обоснована в примере 2).
С определением симметричной разности можно дать другое определение операции минимизации µ:
f (x1 , . . . , xn ) = µy (|g(x1 , . . . , xn−1 , y) − xn | = 0).
Пример 16. Функция целой части частного [ xy ], которая в области определения функции xy всюду совпадает с ней и, по определению [ x0 ] = x, является частично-рекурсивной. Покажем это. Прежде
всего [ xy ] определяется как наименьшее значение z, при котором
y · (z + 1) > x, что эквивалентно y · (z + 1) ≥ x + 1, и, следовательно,
(x+1) . y·(z+1) = 0. Поэтому при y 6= 0 [ xy ] = µz ((x+1) . y·(z+1) =
h i
0). Чтобы учесть, что при x = y = 0 значение xy = 0, введем множитель x . z. Тогда
hx i
.
.
= µz ((x z)((x + 1) y · (z + 1)) = 0).
y
30
Пример 17. Функция остатка от деления mod(x, y) определяется
при y 6= 0 обычным образом, а при y = 0 mod(x, 0) = x. Ее рекурсивность следует из определения:
hx i
.
mod(x, y) = x y ·
.
y
Пример 18. Функцию делимости x на y (1, если делится, и 0 –
иначе) можно определить следующим образом:
½
1, mod(x, y) = 0,
div(x, y) =
0, mod(x, y) 6= 0.
Ее рекурсивность следует из определения:
div(x, y) = sg(mod(x, y)).
Пример 19. Функция двоичной степени x : st2 (x) – максимальный
показатель, с которым степень 2 входит множителем в x, т. е. x =
2st2 (x) (2s + 1). Ее можно определить следующим образом:
st2 (x) = µt (div(x, 2t+1 ) = 0),
откуда следует рекурсивность этой функции.
1.3.6
Тезис Чёрча
Мы установили, что при помощи частично-рекурсивных функций
можно описать все арифметические операции и организовать вычисление более сложных функций из более простых (суперпозиция), организовать циклические вычисления (примитивная рекурсия) и ветвление вычислений (функция сигнум sg(x)). А.Чёрч высказал тезис,
который следует понимать как определение понятия алгоритм.
Тезис А.Чёрча. Любая интуитивно вычислимая функция является частично-рекурсивной.
Напомним, что тезис А.Тьюринга утверждает, что любая интуитивно вычислимая функция является вычислимой по Тьюрингу. Мы
покажем, что эти 2 определения понятия алгоритма являются эквивалентными. Но для этого нам придется рассмотреть еще 2 типа
частично-рекурсивных функций: нумерацию наборов и совместную
рекурсию.
31
1.3.7
Нумерация наборов
Частично-рекурсивная функция называется нумерацией, если она
всюду определена и разным наборам ставит в соответствие разные
значения. Таким образом, по значению функции набор находится
однозначно.
Рассмотрим функцию p (x, y) нумерации пар. Каждой паре (x, y)
ставится в соответствие n = p (x, y). Рассмотрим также 2 обратные
функции l(n) и r(n), ставящие в соответствие n левый (x) и правый
(y) элементы пары. Тогда
p (l(n), r(n)) = n,
l(p (x, y)) = x,
r(p (x, y)) = y.
Если есть нумерация пар p, то можно получить нумерации троек,
четверок и т. д.:
p3 (x, y, z) = p (p (x, y), z),
p4 (x, y, z, u) = p (p (p (x, y), z), u).
По номеру набора компоненты набора определяются однозначно. Так,
из n = p3 (p (x, y), z) следует:
z = r(n),
p (x, y) = l(n),
x = l (l (n)),
y = r (l (n)).
Если функции p, r, l являются рекурсивными, то построенные функции нумерации для троек, четверок и т.д. также будут рекурсивны.
Опишем одну из нумераций пар так, чтобы показать ее рекурсивность:
p (x, y) = 2x (2y + 1) − 1.
Во-первых, она рекурсивна, так как из 2x (2y + 1) ≥ 0 следует, что
p (x, y) = 2x (2y +1) . 1 и в таком определении все входящие функции
и операции рекурсивны. Во-вторых, из n = 2x (2y + 1) − 1 следует
l (n) = x = st2 (n + 1), и эта функция также рекурсивна. В-третьих,
из
n+1
n+1
2y + 1 =
=
2x
2st2 (n+1)
следует
n+1
1
y + = st (n+1)+1
2 2 2
и, следовательно,
h n+1 i
r (n) = y = st (n+1)+1 ,
2 2
32
что также дает рекурсивность функции r (n).
Рассмотрим пример использования нумерации.
Пример 20. Функция f (x, y), отличная от 0 на конечном числе
пар, рекурсивна. Действительно, на парах (x1 , y1 ), . . . , (xk , yk ) она
принимает значения z1 , . . . , zk , а на остальных равна 0. Положим
n1 = p (x1 , y1 ), . . . , nk = p (xk , yk ) и рассмотрим функцию g (n), принимающую в точках n1 , . . . , nk значения, равные соответственно z1 , . . . ,
zk , а в остальных точках равную 0. Из примера 10 следует ее рекурсивность. Но тогда
f (x, y) = g (p (x, y))
является суперпозицией рекурсивных функций и, следовательно, рекурсивна.
Аналогично, верен результат для функций любого числа аргументов: функция, отличная от 0 в конечном числе точек, рекурсивна.
1.3.8
Совместная рекурсия
Под совместной рекурсией двух функций мы будем понимать следующее обобщение примитивной рекурсии. Обозначим через x̃ вектор
всех аргументов, кроме выделенного последнего. Тогда совместная
рекурсия функций f1 (x̃, y) и f2 (x̃, y) выражается следующим обобщением примитивной рекурсии:

f1 (x̃, 0) = g1 (x̃),



f2 (x̃, 0) = g2 (x̃),
f (x̃, y + 1) = h1 (x̃, y, f1 (x̃, y), f2 (x̃, y)),


 1
f2 (x̃, y + 1) = h2 (x̃, y, f1 (x̃, y), f2 (x̃, y)).
Теорема 1.1. Если функции g1 , g2 , h1 , h2 частично-рекурсивны,
то функции f1 и f2 также частично-рекурсивны.
Доказательство. Определим функцию
u(x̃, y) = p (f1 (x̃, y), f2 (x̃, y)),
где p – нумерация. Тогда f1 (x̃, y) = l (u (x̃, y)) и f2 (x̃, y) = r (u (x̃, y)).
Поэтому функцию u (x̃, y) можно определить с помощью примитив-
33
ной рекурсии:

u (x̃, 0) = p (f1 (x̃, 0), f2 (x̃, 0)) = p (g1 (x̃), g2 (x̃)) ≡ g(x̃),



u (x̃, y + 1) =
= p (h1 (x̃, y, l (u (x̃, y), r (x̃, y))), h2 (x̃, y, l (u (x̃, y), r (x̃, y)))) ≡



≡ h (x̃, y, u (x̃, y)).
Так как частичная рекурсивность функций g и h следует из частичной рекурсивности функции нумерации p, то функция u также частично-рекурсивна. Отсюда следует частичная рекурсивность
функций f1 (x̃, y) = l (u (x̃, y)) и f2 (x̃, y) = r (u (x̃, y)), что и требовалось доказать.
Аналогично доказывается частичная рекурсивность совместной
рекурсии трех, четырех и т.д. функций.
1.3.9
Упражнения
1. Определите схему частично-рекурсивной функции (последовательность выполнения операциий C, P r, µ для исходных функn
ций O(x), s(x), Im
(x1 , . . . , xn ) ), которая подсчитывает число
двоичных разрядов аргумента. Указание: если число двоичных
разрядов x равно k, то 2k−1 ≤ x < 2k .
2. Определите схему частично-рекурсивной функции целой части
двоичного логарифма аргумента [log2 x]. Указание: сравнить с
результатом предыдущего упражнения.
3. Определите схему частично-рекурсивной функции, которая любому аргументу ставит в соответствие целое неотрицательное
число, двоичный код которого равен дважды подряд скопированному двоичному коду аргумента.
4. Определите схему частично-рекурсивной функции, которая любому аргументу ставит в соответствие целое неотрицательное
число, двоичный код которого образуется из кода аргумента удалением последнего разряда.
5. Определите схему частично-рекурсивной функции, которая любому аргументу ставит в соответствие целое неотрицательное
34
число, двоичный код которого образуется из кода аргумента дописыванием 1.
6. Определите схему частично-рекурсивной функции, которая любому аргументу ставит в соответствие целое неотрицательное
число, двоичный код которого равен инвертированному (дополнительному) двоичному коду аргумента. Указание: выделять в
цикле примитивной рекурсии разряды числа как остатки от
деления на степень 2 и накопить в этом цикле сумму дополнительных разрядов как антисигнумов исходных разрядов.
7. Определите схему частично-рекурсивной функции, которая любому аргументу ставит в соответствие целое неотрицательное
число, двоичный код которого равен реверсированному (идущему в обратном порядке) двоичному коду аргумента.
8. Определите схему частично-рекурсивной функции, которая осуществляет логическое сложение двух двоичных кодов аргументов.
9. Определите схему частично-рекурсивной функции, которая осуществляет логическое умножение двух двоичных кодов аргументов.
10. Определите схему частично-рекурсивной функции, которая осуществляет логическое сложение по модулю 2 двух двоичных
кодов аргументов.
11. Определите схему частично-рекурсивной функции
prod(k, x1 , . . . , xn ),
которая задает произведение своих аргументов, начиная со второго, в количестве, определяемом первым аргументом (при k = 0
или k > n значение функции не определено).
12. Определите схему частично-рекурсивной функции, которая осуществляет логическое сложение двоичного кода своего аргумента и реверсированного кода аргумента.
35
13. Определите схему частично-рекурсивной функции
lsum(k, x1 , . . . , xn ),
которая задает логическое сложение двоичных кодов своих аргументов, начиная со второго, в количестве, определяемом первым
аргументом (при k = 0 или k > n значение функции не определено).
1.4
1.4.1
Нормальные алгоритмы Маркова
Определение нормального алгоритма и его выполнение
Для определения нормального алгоритма Маркова вводится произвольный алфавит – конечное непустое множество символов, при помощи которых описывается алгоритм и данные. В алфавит также
включается пустой символ, который мы будем обозначать греческой
буквой λ. Под словом понимается любая последовательность непустых символов алфавита либо пустой символ, который обозначает
пустое слово.
Всякий нормальный алгоритм Маркова определяется конечным
упорядоченным множеством пар слов алфавита, называемых подстановками. В паре слов подстановки левое (первое) слово – непустое, а правое (второе) слово может быть пустым символом. Для
наглядности левое и правое слово разделяются стрелкой. Например,
1. ab → bd
2. db → ba
3. bba → abb
4. c → λ
В качестве данных алгоритма берется любая непустая строка символов. Работа нормального алгоритма Маркова состоит из последовательности совершенно однотипных шагов. Шаг работы алгоритма
может быть описан следующим образом:
36
1. В упорядоченной последовательности подстановок ищем самую
первую подстановку, левое слово которой входит в строку данных.
2. В строке данных ищем самое первое (левое) вхождение левого
слова найденной подстановки.
3. Это вхождение в строке данных заменяем на правое слово найденной подстановки (преобразование данных).
Шаг работы алгоритма повторяется до тех пор, пока
– либо не возникнет ситуация, когда шаг не сможет быть выполнен
из-за того, что ни одна подстановка не подходит (левое слово любой
подстановки уже не входит в строку данных) – правило остановки;
– либо не будет установлено, что процесс подстановок не может
остановиться.
В первом случае строка данных, получившаяся при остановке алгоритма, является выходной (результатом) и алгоритм применим к
входным данным, а во втором случае алгоритм неприменим к входным данным.
Так, определенный выше в примере нормальный алгоритм Маркова преобразует слово cdbacab в слово bbddd следующим образом
(над стрелкой преобразования мы пишем номер применяемой подстановки, а в преобразуемой строке подчеркиваем левое слово применяемой подстановки):
1
2
4
4
1
1
cdbacab → cdbacbd → cbaacbd → baacbd → baabd → babdd → bbddd,
а при преобразовании слова abbc этот же алгоритм будет неограниченно работать, так как имеет место цикличное повторение данных:
1
2
3
1
abbc → bdbc → bbac → abbc → . . .
Таким образом, всякий нормальный алгоритм Маркова определяет функцию, которую мы назовем нормальной (или вычислимой по
Маркову), которая может быть частичной и которая в области определения входному слову ставит в соответствие выходное слово.
37
1.4.2
Возможности нормальных алгоритмов и тезис Маркова
Прежде всего рассмотрим возможности реализации арифметических
операций с помощью нормальных алгоритмов Маркова. Сначала обратим внимание на одно обстоятельство, связанное с работой любого
нормального алгоритма Маркова: нужно либо вводить дополнительное правило остановки работы нормального алгоритма (иначе в примере увеличения числа на 1 алгоритм продолжит работу и снова
будет увеличивать полученный результат еще на 1 и т.д. неограниченное число раз), либо перед началом работы нормального алгоритма добавлять к входной строке специальные символы, отличные от
других символов строки, которые учитываются подстановками алгоритма в начале его работы и которые удаляются в конце работы
алгоритма. Мы будем придерживаться второго способа, как и одна
из наиболее успешных реализаций нормальных алгоритмов Маркова в виде языка программирования Рефал (рекурсивных функций
алгоритмический язык). В качестве добавляемого символа возьмем
символ "@".
Пример 1. Рассмотрим простейшую операцию увеличения десятичного числа на 1. В этом случае почти всегда необходимо увеличить последнюю цифру на 1, а последняя цифра отличается тем,
что после нее идет символ "@". Поэтому первыми подстановками
должны быть подстановки типа
< цифра > @ → < цифра + 1 > .
Но если это цифра 9, то ее нужно заменить 0 и увеличение на 1
перенести в предыдущий разряд. Этому отвечает подстановка
9@ → @0.
Наконец, если число начинается с 9 и перед этой цифрой нужно
поставить 1, то этому будет отвечать подстановка
@@ → 1,
а если это не так, то в конце работы алгоритма символы @ надо
стереть, что выполнит подстановка
@ → λ.
38
Таким образом, мы получаем следующий нормальный алгоритм Маркова увеличения десятичного числа на 1:
1. 0@ → 1
9. 8@ → 9
2. 1@ → 2
10. 9@ → @0
3. 2@ → 3
11. @@ → 1
...............
12. @ → λ
Приведем работу построенного алгоритма для чисел 79 и 99:
10
8
12
@79@ → @7@0 → @80 → 80,
10
10
11
@99@ → @9@0 → @@00 → 100.
Аналогичным образом разрабатывается нормальный алгоритм Маркова для уменьшения числа на 1 (см. упражнение 1.4.3.1).
Пример 2. Прежде, чем перейти к другим арифметическим операциям, рассмотрим как довольно типичный пример, используемый
в других алгоритмах, алгоритм копирования двоичного числа. В
этом случае прежде всего исходное и скопированное числа разделим
символом "*". В разрабатываемом алгоритме мы будем копировать
разряды числа по очереди, начиная с младшего, но нужно решить 2
проблемы: как запоминать значение символа, который мы копируем,
и как запоминать место копируемого символа. Для решения второй
проблемы используем символ "!", которым мы будем определять еще
не скопированный разряд числа, после которого и стоит этот символ.
Для запоминания значения копируемого разряда мы будем образовывать для значения 0 символ "a", а для значения 1 – символ "b".
Меняя путем подстановок эти символы "a" или "b" с последующими, мы будем передвигать разряды "a" или "b" в начало копируемого
числа (после "*"), но для того, чтобы пока не происходило копирование следующего разряда справа, мы перед передвижением разряда
временно символ "!" заменим на символ "?", а после передвижения
сделаем обратную замену. После того как все число окажется скопированным в виде символов "a" и "b", мы заменим эти символы на 0
и 1 соответственно. В результате нормальный алгоритм копирования
двоичного числа можно определить следующей последовательностью
39
подстановок:
1. 0@ → 0!∗
2. 1@ → 1!∗
3. 0! → ?0a
4. 1! → ?1b
5.
6.
7.
8.
a0
a1
b0
b1
¾
начальные пометки копир. разряда и копии числа
¾
копирование разряда с заменой пометки разряда
→
→
→
→
0a )
1a
передвижение скопированного разряда
0b
1b
¾
9. a∗ → ∗a
остановка передвижения скопированного разряда
10. b∗ → ∗b
11. ? → !
обратная замена пометки разряда
¾
12. a → 0
обратная замена скопированного разряда
13. b → 1
14. @! → λ
Продемонстрируем работу алгоритма для числа 10:
1
3
9
11
4
7
@10@ → @10!∗ → @1?0a∗ → @1?0 ∗ a → @1!0 ∗ a → @?1b0 ∗ a →
10
11
13
12
14
@?10b∗a → @?10 ∗ ba → @!10 ∗ ba → @!10 ∗ 10 → @!10 ∗ 10 → 10 ∗ 10
Для построения алгоритма сложения двух чисел можно использовать идею уменьшения одного числа на 1 с последующим увеличением другого числа на 1 и повторением этого до тех пор, пока
уменьшаемое число не исчезнет после того, как станет равным 0.
Но можно использовать и более сложную идею поразрядного сложения с переносом 1 в разряд слева. Построения этих алгоритмов, а
также алгоритмов вычитания, умножения и деления выделены для
самостоятельной работы в упражнениях 2 – 6 (см. раздел 1.4.3).
Приведенные примеры показывают также возможности аппарата
нормальных алгоритмов Маркова по организации ветвления и цикличных процессов вычисления. Это показывает, что
всякий алгоритм может быть нормализован,
т. е. задан нормальным алгоритмом Маркова. В этом и состоит тезис
Маркова, который также следует понимать как определение алгоритма.
40
1.4.3
Упражнения
1. Определите нормальный алгоритм, который уменьшает число на
единицу.
2. Определите нормальный алгоритм сложения двух двоичных чисел методом уменьшения одного числа на 1 и увеличением другого числа на 1 до тех пор, пока уменьшаемое число не станет
равным 0.
3. Определите нормальный алгоритм логического сложения двух
двоичных кодов.
4. Определите нормальный алгоритм логического умножения двух
двоичных кодов.
5. Определите нормальный алгоритм сложения по модулю 2 двух
двоичных кодов.
6. Определите нормальный алгоритм поразрядного сложения двух
двоичных чисел.
7. Определите нормальный алгоритм вычитания двоичных чисел.
8. Определите нормальный алгоритм умножения двух двоичных
чисел столбиком.
9. Определите нормальный алгоритм деления двух двоичных чисел
с определением частного и остатка.
10. Определите нормальный алгоритм вычисления наибольшего общего делителя двух двоичных чисел.
11. Определите нормальный алгоритм вычисления наименьшего общего кратного двух двоичных чисел.
1.5 Эквивалентность моделей алгоритма
1.5.1
Общие черты моделей алгоритмов
Анализ трех приведенных моделей алгоритмов (машины Тьюринга,
частично-рекурсивные функции, нормальные алгоритмы Маркова)
41
показывает, что они обладают следующими общими чертами:
1. Указывается единый способ представления входной и выходной информации (для МТ и нормального алгоритма Маркова – конечное слово некоторого фиксированного алфавита, для
частично-рекурсивных функций – целое неотрицательное число).
2. Вычисляемая функция (по Тьюрингу, по Маркову или частичнорекурсивная) задается конечным набором инструкций (3 функции МТ для изменения символа, состояния и движения; 3 операции над 3 исходными функциями, при помощи которых определяются любые частично-рекурсивные функции; единое правило
подстановки в нормальном алгоритме Маркова).
3. Определяется конечный набор правил вычисления значения
функции (конечное число элементов функциональной таблицы
МТ; конечное число операций в схеме частично-рекурсивной
функции; конечное число правил подстановки нормального алгоритма Маркова).
Заметим, что этим требованиям в определенном смысле удовлетворяет и интуитивно-вычислимая функция:
– каждый алгоритм предполагает конечный алфавит для входной
промежуточной и выходной информации;
– хотя элементарность действия не определяется, но число элементарных действий конечно в описании каждого алгоритма, и они детерминированы;
– выполнение алгоритма связано с конечным числом дискретных шагов.
Оказывается, что понятие вычислимой функции устойчиво в смысле требований 1 – 3:
Любые мощные описания вычислимых функций, удовлетворяющие требованиям 1 – 3, оказываются эквивалентными.
Мы в последующих разделах докажем эквивалентность двух моделей: машин Тьюринга и частично-рекурсивных функций. Для этого
42
мы сначала покажем, что для любой частично-рекурсивной функции
можно построить машину Тьюринга, вычислимая функция которой
является этой частично-рекурсивной функцией, а затем мы покажем, что вычислимая по Тьюрингу функция может быть задана как
частично-рекурсивная.
1.5.2
Вычисление частично-рекурсивной функции
на машине Тьюринга
Каждая частично-рекурсивная функция определяется с помощью 3
n
исходных функций O(x), s(x), Im
(x1 , . . . , xn ) и 3 операций C, P r, µ.
Поэтому достаточно показать выполнимость этих операций и вычислимость этих функций.
10 . Реализация суперпозиции C
Пусть имеется суперпозиция
h(x1 , . . . , xm ) = g(f1 (x1 , . . . , xm ), . . . , fn (x1 , . . . , xm ))
и функции g, f1 , . . . , fn вычисляются МТ Mg , M1 , . . . , Mn . Тогда функция h может быть вычислена МТ Mg (M1 ∗ · · · ∗ Mn ).
Для этого изобразим аргумент x при помощи x + 1 палочки. Учитывая операции композиции и суперпозиции над МТ, получим отображение вычислимых по Тьюрингу функций:
x1 ∗ · · · ∗ xm
M1 ∗···∗Mn
−→
Mg
f1 (x1 , . . . , xm ) ∗ · · · ∗ fn (x1 , . . . , xm ) −→
Mg
−→ g(f1 (x1 , . . . , xm ), . . . , fn (x1 , . . . , xm )).
20 . Реализация примитивной рекурсии P r
Для простоты покажем, как реализовать простейшую примитивную рекурсию
½
f (0) = a,
f (x) =
f (x + 1) = h(x, f (x)).
Соединение МТ M4 ((B ◦ (M1 ∗ M2 ∗ M3 ))(A)), реализующее цикл вычислений примитивной рекурсии, состоит из вспомогательных машин Тьюринга, выполняющих следующие действия:
A – по x вырабатывает x ∗ 0 ∗ a (дописывает ∗0 ∗ a).
43
B – по x1 ∗ x2 ∗ x3 выдает T , если x1 6= 0, а иначе F .
M1 – стирает ∗x2 ∗ x3 и в x1 стирает 1 палочку.
M2 – стирает x1 ∗ и ∗x3 , а к x2 дописывает 1 палочку.
M3 – стирает x1 ∗ и вычисляет h(x2 , x3 ).
M4 – стирает x1 ∗ x2 ∗.
Заметим, что вычисление идет циклами и после i-го цикла на ленте
записана тройка x − i ∗ i ∗ h(i, f (i)):
A
x → x ∗ 0 ∗ a = x1 ∗ x2 ∗ x3
B◦(M1 ∗M2 ∗M3 )
−→
M
x1 − 1 ∗ x2 + 1 ∗ h(x2 , x3 ) →4 x3 .
30 . Реализация минимизации µ
Для простоты покажем, как реализовать простейшую операцию
минимизации:
f (x) = µy (g(x, y) = 0).
Соединение МТ M4 ((B ◦ (M1 ∗ M2 ∗ M3 ))((M1 ∗ M0 )(A))), реализующее
цикл вычислений примитивной рекурсии, состоит из вспомогательных машин Тьюринга, выполняющих следующие действия:
A – по x вырабатывает x ∗ 0 (дописывает ∗0).
M0 – оставляет x ∗ i без изменений.
M1 – вычисляет g(x, i) по x ∗ i.
B – по g(x, i) ∗ x ∗ i выдает T , если g(x, i) 6= 0, а иначе F .
M2 – стирает g(x, i)∗.
M3 – дописывает 1 палочку (увеличивает i на 1).
M4 – стирает x∗.
Заметим, что вычисление идет циклами и после i-го цикла на ленте
записана тройка g(x, i) ∗ x ∗ i:
A
M ∗M
1
0
x → x∗0 = x∗i →
g(x, i) ∗ x ∗ i
B◦(M1 ∗M2 ∗M3 )
→
44
M
g(x, i + 1) ∗ x ∗ i + 1 →4 i.
40 . Реализация исходных функций
МТ, реализующая функцию нуля O(x), стирает все палочки, кроме одной.
МТ, реализующая функцию следования s(x), дописывает 1 палочку.
n
МТ, реализующая функцию выбора Im
(x1 , . . . , xn ), стирает палочки всех групп, кроме m-й.
Таким образом, исходные функции вычислимы по Тьюрингу, и
любая частично-рекурсивная функция также вычислима по Тьюрингу.
Теперь нам необходимо показать, что для любой МТ ее вычислимая по Тьюрингу функция является частично-рекурсивной, т. е.
может быть задана частично-рекурсивной схемой. Сначала покажем,
что работа МТ описывается частично-рекурсивными функциями.
1.5.3
Арифметизация машин Тьюринга
Изменим нумерацию внешнего алфавита {a0 , a1 , . . . , ak−1 } и будем
предполагать, что в алфавит входит пустой символ a0 = Λ и символ
a1 = 1 (иначе алфавит можно расширить). Букве si сопоставим число
i и далее не будем различать числа (коды букв) и буквы.
Пусть обозревается символ a, символы b0 , b1 , b2 , . . . находятся слева от обозреваемого символа, а символы c0 , c1 , c2 , . . . находятся справа от обозреваемого символа (см. рис. 7).
...
b2 b1 b0 a c0 c1 c2 . . .
4
Рис. 7
Конфигурация на ленте однозначно определяется тройкой целых неотрицательных
чисел
< a, m, n >, где a – обозреваемый символ,
P∞
P
∞
m = i=o bi k i , n = i=o ci k i . По m и n однозначно восстанавливаются коэффициенты bi и ci . Конфигурацию МТ опишем четверкой
<j, a, m, n>, где j – номер состояния qj (j = 0, 1, . . . , r − 1), r –
число состояний, q1 – начальное состояние, q0 – заключительное состояние. Нам необходимо определить, как преобразуется четверка в
результате 1 шага работы МТ.
45
Пусть команда qj при считывании символа ai ведет к движению
головки вправо, т. е. (ai , qj ) → (ai0 , R, qj 0 ) (см. рис. 8).
...
b2 b1 b0 ai0 c0 c1 c2 . . .
4
Рис. 8
0
0
0
Новые значения a, m, n обозначим через a , m , n . Они вычисляются
по формулам:
0
m = m · k + ai0 ,
0
n = [n/k],
0
a = mod(n, k).
Поэтому преобразование четверки конфигурации МТ идет по формуле:
0
< j, a, m, n > → < j , mod(n, k), m · k + ai0 , [n/k] > .
Пусть теперь команда qj при считывании символа ai ведет к движению головки влево, т. е. (ai , qj ) → (ai0 , L, qj 0 ) (см. рис. 9).
...
0
b2 b1 b0 ai0 c0 c1 c2 . . .
4
Рис. 9
0
0
Новые значения a , m , n вычисляются по формулам:
0
n = n · k + ai0 ,
0
m = [m/k],
0
a = mod(m, k).
Поэтому преобразование четверки конфигурации МТ в этом случае
идет по формуле:
0
< j, a, m, n > → < j , mod(m, k), [m/k], n · k + ai0 > .
Наконец, в случае команды, которая оставляет неподвижной головку, т. е. (ai , qj ) → (ai0 , N, qj 0 ), вычисления идут по формулам:
0
n = n,
46
0
m = m,
0
a = ai0
и преобразование четверки конфигурации выглядит следующим образом:
0
< j, a, m, n > → < j , ai0 , m, n > .
Введем функцию D(j, ai ), характеризующую движение головки:

 0, если D = R,
1, если D = L,
D(j, ai ) =

2, если D = N,
определив ее на всех остальных парах как D(j, ai ) = 0. Определим
также функцию Q(j, ai ), указывающую номер нового состояния, и
функцию A(j, ai ), указывающую записываемый символ:
½ 0
j , если j ∈ {1, . . . , r − 1}, ai ∈ {0, . . . , k − 1},
Q(j, ai ) =
0, если иначе,
½
ai0 , если j ∈ {1, . . . , r − 1}, ai ∈ {0, . . . , k − 1},
A(j, ai ) =
0, если иначе.
Тогда
0
j = Q(j, a),
0
a = mod(n, k) · sg(D(j, a)) + mod(m, k) · sg(|D(j, a) − 1|)+
+A(j, a) · sg(|D(j, a) − 2|),
0
m = (m · k + A(j, a)) · sg(D(j, a)) + [m/k] · sg(|D(j, a) − 1|)+
+m · sg(|D(j, a) − 2|),
0
n = [n/k] · sg(D(j, a)) + (n · k + A(j, a)) · sg(|D(j, a) − 1|)+
+n · sg(|D(j, a) − 2|).
0
0
0
0
Функция j рекурсивна, а функции a , m , n тоже рекурсивны, как
суперпозиции рекурсивных функций.
Введем функцию Q̃(t, j, a, m, n) номера состояния МТ через t шагов из конфигурации < j, a, m, n >. Аналогично введем функции
Ã(t, j, a, m, n), M̃ (t, j, a, m, n), Ñ (t, j, a, m, n),
47
определяющие записываемый символ, символы слева от него и символы справа от него. Эти функции удовлетворяют соотношениям:


Q̃(0, j, a, m, n) = j,




Ã(0, j, a, m, n) = a,




M̃ (0, j, a, m, n) = m,





Ñ (0, j, a, m, n) = n,




 Q̃(t + 1, j, a, m, n) = Q(Q̃(t, j, a, m, n), Ã(t, j, a, m, n)),
0
Ã(t + 1, j, a, m, n) = a (Q̃(t, j, a, m, n), Ã(t, j, a, m, n),



M̃ (t, j, a, m, n), Ñ (t, j, a, m, n)),


0


M̃ (t + 1, j, a, m, n) = m (Q̃(t, j, a, m, n), Ã(t, j, a, m, n),




M̃ (t, j, a, m, n), Ñ (t, j, a, m, n)),



0


Ñ (t + 1, j, a, m, n) = n (Q̃(t, j, a, m, n), Ã(t, j, a, m, n),



M̃ (t, j, a, m, n), Ñ (t, j, a, m, n)).
Это совместная рекурсия функций Q̃, Ã, M̃ , Ñ , и так как функции
0
0
0
Q, a , m , n частично-рекурсивны, то функции Q̃, Ã, M̃ , Ñ также частичнорекурсивны, что и требовалось.
Теперь остается показать, что вычислимая по Тьюрингу функция
является частично-рекурсивной.
1.5.4
Частичная рекурсивность вычислимой по Тьюрингу функции
Для простоты мы рассмотрим функцию одного аргумента f (x). Для
арифметизации аргумента x опишем его с помощью слова из x + 1
единицы (см. рис. 10).
...
Λ 1 1 ... 1 1 Λ ...
4
Рис. 10
Начальная конфигурация описывается четверкой < 1, 1, 0, n(x) >, где
x
−1
n(x) = 1 + k + k 2 + · · · + k x−1 = kk−1
. Так как k > 1 и деление нацело,
то
.
.
n(x) = [(k x
1)/(k
1)],
т. е. функция n(x) частично-рекурсивна.
48
Пусть q(t, x) состояние МТ после t тактов. Тогда
q(t, x) = Q̃(t, 1, 1, 0, n(x)).
Аналогично определим функции a(t, x), m(t, x), n(t, x). Все эти функции являются суперпозициями частично-рекурсивных функций и потому частично-рекурсивны.
Определим момент остановки МТ:
t0 (x) = µt (q(t, x) = 0).
Если f (x) не определена, МТ не останавливается, и, следовательно,
t0 (x) не определена. Поэтому t0 (x) – частично-рекурсивная функция,
что следует из рекурсивности функции q(t, x) и операции минимизации µ. В момент останова МТ:
q0 (x) = q(t0 (x), x), a0 (x) = a(t0 (x), x),
m0 (x) = m(t0 (x), x), n0 (x) = n(t0 (x), x).
Поэтому функции q0 , a0 , m0 , n0 являются частично-рекурсивными. В
заключительной конфигурации (см. рис. 11)
z
...
f (x)+1
}|
{
Λ 1 1 ... 1 1 Λ ...
4
q0
Рис. 11
n0 (x) = 1 + k + · · · + k
Поэтому
f (x)−1
h ks
=
h k f (x)
k
.
.
1
1i
.
1i
f (x) = µs (|n0 (x) −
| = 0)
k . 1
получается из частично-рекурсивной функции и ее минимизации.
.
Таким образом, мы доказали, что класс вычислимых по Тьюрингу
функций совпадает с классом частично-рекурсивных функций.
49
1.6 Алгоритмическая неразрешимость
1.6.1
Массовые алгоритмические проблемы
Под массовой алгоритмической проблемой понимают требование построить алгоритм решения для некоторой бесконечной совокупности
задач. Для многих таких проблем разработаны алгоритмы решения
задач, но в начале XX в. попытки построения алгоритмов для некоторых проблем не увенчались успехом. Возникло сомнение в том, что
это возможно. Среди этих неподдающихся проблем особенно простыми выглядят те, решение задач которых требует ответа "да" или
"нет". В качестве примера отметим указанную в разделе 1.1 десятую проблему Гильберта: построить алгоритм, определяющий существование рациональных корней для любого диофантова уравнения.
В общем случае такой простой проблемы индивидуальная задача zi состоит в том, обладает ли свойством Q вычислимая машиной
Тьюринга Mi функция φMi (x). А массовая алгоритмическая проблема требует построить алгоритм распознавания свойства Q, т. е. алгоритм, который бы для каждой функции φM (x) определял, обладает
ли она свойством Q.
Согласно тезису Тьюринга, если существует такой алгоритм, то
существует МТ M 0 , которая решает данную проблему, т. е. индивидуальная задача zi для МТ M 0 кодируется в виде слова Pi и запускается на выполнение МТ M 0 в конфигурации < q1 , Pi >. Она должна
остановиться либо в конфигурации < q0 , 1 >, что будет означать ответ "да", либо в конфигурации < q0 , 0 >, что будет означать ответ
"нет". Если показать, что такой МТ M 0 не может существовать, то
это будет означать алгоритмическую неразрешимость проблемы:
нельзя построить алгоритм, проверяющий для любой функции φM (x), обладает ли она свойством Q.
1.6.2
Метод сводимости
При доказательстве алгоритмической неразрешимости проблем часто используют метод сводимости, который позволяет по алгорит50
мической неразрешимости одних проблем устанавливать алгоритмическую неразрешимость других проблем.
0
0
Пусть существуют 2 массовые проблемы Z = {zi } и Z = {zi }
и пусть существует алгоритмический способ построить по задаче
0
0
0
zi ∈ Z задачу zi ∈ Z такой, что zi имеет ответ "да" тогда и только
тогда, когда zi имеет положительный ответ. В этом случае говорят,
0
0
что проблема Z сводится к проблеме Z , т. е. Z → Z . Если проблема
Z алгоритмически неразрешима, то алгоритмически неразрешима и
0
проблема Z .
При использовании метода сводимости для доказательства алгоритмической неразрешимости некоторой проблемы Z обычно путь
такой:
1) доказывается алгоритмическая неразрешимость простой проблемы Z 0 ;
2) затем строится цепочка сведения Z 0 → Z1 → Z 2 → · · · → Z.
В этом и состоит метод сводимости.
Для примера доказательства простой алгоритмически неразрешимой проблемы рассмотрим проблему самоприменимости.
1.6.3
Проблема самоприменимости
Для каждой МТ внешний алфавит и внутренний алфавит конечны.
Рассмотрим объединение всех внешних алфавитов и объединение
всех внутренних алфавитов. Они, конечно же, бесконечны, но из
них для каждой МТ мы выбираем конечные множества символов.
Будем считать, что символ a0 принадлежит всем внешним алфавитам и интерпретируется как пустой символ Λ, а q0 и q1 содержатся
во внутренних состояниях всех МТ и интерпретируются как заключительное и начальное состояния.
Каждому символу s ∈ {R, L, N, a0 , a1 , . . . , ai , . . . , q0 , q1 , . . . , qj , . . . }
сопоставим шифр H(s) следующим образом:
H(R) = 10, H(L) = 100, H(N ) = 1000, H(a0 ) = 10000,
H(a1 ) = 1000000 = 106 , . . . , H(ai ) = 102i+4 (i = 0, 1, . . . ),
51
H(q0 ) = 100000 = 105 . . . , H(qj ) = 102j+5 (j = 0, 1, . . . ),
т. е. шифр каждого символа начинается с единицы и имеет отличительное от других шифров число нулей. Такое кодирование символов
позволяет по закодированной последовательности символов в виде
единиц и нулей однозначно восстановить исходную последовательность символов.
0
0
Любой команде МТ C = (a, q) → (a , d, q ) сопоставим шифр
0
0
H(C) = H(a)H(q)H(a )H(d)H(q ).
Упорядочим лексикографически пары (ai , qj ) (сначала изменяется j,
а затем i) и в соответствии с этим упорядочим команды МТ:
C1 , C2 , . . . , C(r−1)k .
Теперь МТ M поставим в соответствие шифр:
H(M ) = H(C1 )H(C2 ) . . . H(C(r−1)k ).
Каждой конфигурации МТ K =< a1 . . . ai−1 q j ai . . . ap > сопоставим
шифр:
H(K) = H(a1 ) . . . H(ai−1 )H(q j )H(ai ) . . . H(ap ).
По шифру конфигурации можно найти, какой шифр соответствует
состоянию МТ, какой символ обозревается и каково расположение
всех символов на ленте.
Шифр МТ позволяет построить универсальную машину Тьюринга (во входном слове такой универсальной машины сначала располагается шифр МТ, а затем начальная конфигурация с ее входным
словом): при работе одного шага такой универсальной машины состояние текущей конфигурации отыскивается по последнему шифру
состояния (нечетное число нулей, большее 3) и определяется шифр
обозреваемого символа (следует за шифром состояния), затем отыскивается шифр команды, соответствующий этой паре шифров, и, наконец, преобразуется конфигурация в соответствии с выполнением
команды. Этим показывается принципиальная возможность построения универсальных машин. Но универсальная машина не является
нашей целью.
52
Каждая МТ может быть зашифрована. В отношении своего шифра H(M ) МТ M может быть применима (и в этом случае называется
самоприменимой) или нет (и в этом случае называется несамоприменимой). Индивидуальная задача для M заключается в определении свойства самоприменимости. Проанализировав МТ, можно определить выполнение свойства самоприменимости для нее. Проблема
самоприменимости формулируется следующим образом:
построить общий алгоритм распознавания самоприменимости машины Тьюринга.
Теорема 1.2. Проблема распознавания самоприменимости алгоритмически неразрешима.
Доказательство. Допустим, что утверждение теоремы неверно.
Тогда существует МТ M 0 , которая конфигурацию < q1 , H(M ) >
переводит для самоприменимой МТ M в < q0 , 1 >, а для несамо0
применимой МТ – в < q0 , 0 >. Построим по ней МТ M следующим
образом: заключительное состояние q0 для M0 заменим на незаклю0
0
чительное состояние q0 для M и добавим 3 новые команды:
0
0
(1, q0 ) → (Λ, N, q0 ),
0
0
(Λ, q0 ) → (1, N, q0 ),
0
(0, q0 ) → (0, N, q0 ).
0
Машина M применима к шифрам несамоприменимых МТ, так как
0
последняя из добавленных команд приведет M к останову, и неприменима к шифрам самоприменимых МТ, так как первые две из трех
добавленных команд приведут к бесконечному циклу стирания и
восстановления единицы.
0
Какой же является сама M – самоприменимой или нет? Если она
самоприменима, то, переработав свой шифр, она не должна остановиться, но тогда она несамоприменима. Если же она несамоприменима, то при переработке своего шифра должна остановиться, но
тогда она самоприменима. Это противоречие показывает, что маши0
на M не может существовать. Значит, не существует и машина M 0 .
Теорема доказана.
53
1.6.4
Упражнения
1. Является ли алгоритмически неразрешимой проблема остановки
МТ: построить алгоритм, который позволяет для любой МТ
определять ее применимость к любому входному слову? Ответ
обосновать (указание: использовать идеи доказательства алгоритмической неразрешимости проблемы самоприменимости).
2. Является ли алгоритмически неразрешимой следующая проблема: построить алгоритм, который для любого алгебраического уравнения одного неизвестного с целочисленными коэффициентами и коэффициентом 1 при старшей степени определяет, имеет ли это уравнение целые корни. Ответ обосновать
(указание: попытаться построить алгоритм или применить метод
сводимости для доказательства алгоритмической неразрешимости).
3. Можно ли построить частично-рекурсивную функцию, которая
для любого целого неотрицательного аргумента определяет, является ли его код шифром какой-либо МТ? Ответ обосновать.
4. Можно ли построить нормальный алгоритм Маркова, который
по любой последовательности из бесконечного числа единиц и
натурального числа нулей определяет наибольшее число нулей,
идущих в последовательности подряд? Ответ обосновать.
1.7 Литература
1. Адян С.И., Дурнев В.Г. Алгоритмические проблемы для групп
и полугрупп // УМН. 2000. Т. 55, вып. 2(332). С. 3 – 94.
2. Инютин С.А. Основы теории алгоритмов. Сургут, 2003.
3. Кузнецов О.П., Адельсон-Вельский Г.М. Дискретная математика для инженера. М., 1988.
4. Мальцев А.И. Алгоритмы и рекурсивные функции. М., 1965.
5. Мальцев А.И. Алгоритмы и рекурсивные функции. М., 1986.
54
6. Марков А.А. Теория алгорифмов. М., 1954. (Труды МИАН.
Т. 42.)
7. Марков А.А., Нагорный Н.И. Теория алгоритмов. М., 1984.
8. Мендельсон Э. Введение в математическую логику и теорию
алгоритмов. М., 1985.
9. Новиков Н.С. Элементы теории алгоритмов. М., 1987.
10. Роджерс Х. Теория вычислимых функций и эффективная вычислимость. М., 1972.
11. Шапорев С.Д. Математическая логика и теория алгоритмов.
СПб., 2004.
12. Яблонский С.В. Введение в дискретную математику. М., 1979.
13. Churh A. An unresolvable problem of elementary number theory
// Amer. J. Math. 1936. V. 58. № 2. P. 345 – 363.
14. Post E.L. Recursive unsolvability of a problem of Thue // J.
Symbolic Logic. 1947. V. 12. № 1. P. 1 – 11.
15. Turing A.M. On computable numbers, with an application to the
Entscheidungsproblem // Proc. London Math. Soc. (2). 1936. V.42.
№ 3, 4. P. 230 – 265.
55
Глава 2
Сложность алгоритмов
2.1 Характеристики сложности алгоритмов
В большинстве практических случаев рассматриваемые проблемы не
являются алгоритмически неразрешимыми. Но при создании алгоритма решения задачи можно часто использовать разные идеи, которые ведут к различным алгоритмам. Как сравнить такие алгоритмы,
чтобы выбрать лучший? Что значит лучший? Всегда ли существует
лучший алгоритм или это зависит от каких-то условий его использования? Нам необходимо научиться сравнивать алгоритмы по их
применимости.
Характеристики алгоритма, которые влияют на его применимость,
принято называть характеристиками сложности алгоритма. Среди
характеристик сложности наиболее важными являются две, характеризующие ресурсы исполнителя: время и память. Нам необходимо
знать, как долго будет выполняться алгоритм и хватит ли ресурса
памяти для этого.
Время зависит от того, кто является исполнителем (человек, вычислительное устройство, компьютер), и от того, насколько быстро
он делает операции (разные компьютеры обладают разной производительностью). Так как нам нужна объективная характеристика
алгоритма, не зависящая от исполнителя, то мы вместо времени исполнения алгоритма будем рассматривать число шагов t выполнения
алгоритма. Если τ̄ – среднее время одного шага исполнителя, то
фактическое время работы алгоритма для этого исполнителя
Tφ = τ̄ · t.
56
Таким образом, t есть характеристика алгоритма, не зависящая от
особенностей исполнителя, и потому математическая характеристика
сложности алгоритма.
Память S, используемая алгоритмом, также зависит от особенностей исполнителя. Если на каждом шаге алгоритма используется не
более µ единиц памяти, то S ≤ µ · t. Эта оценка очень грубая, так
как t может существенно превосходить S.
В большинстве случаев практического использования память не
является таким существенным ограничением, как время. И по этой
причине в качестве характеристики сложности алгоритма применяется характеристика t – число шагов выполнения алгоритма. Однако в
отдельных случаях мы укажем на особенности организации данных
алгоритма, когда используемая память будет существенно ограничивающим ресурсом при наиболее эффективном числе шагов.
2.2 Трудоемкость алгоритмов
2.2.1
Определение трудоемкости алгоритма
Итак, в качестве основной характеристики сложности алгоритма мы
будем рассматривать число шагов t исполнения алгоритма. Однако t
зависит от исходных данных задачи. Зависимость эта в большинстве
случаев является весьма сложной, и не всегда ее возможно анализировать непосредственно. В то же время, если мы непосредственно не
можем предсказать, сколько будет выполняться алгоритм, то разумно
потребовать меньшее: каковы будут временные рамки выполнения
алгоритма (максимальное и минимальное время), сколько в среднем будет выполняться алгоритм (среднее время). Но для любых
вариантов задачи время (число шагов) ничем не ограничено. Так, при
сортировке массива время, как правило, зависит от длины массива и
растет с ростом числа элементов массива.
Принято входные данные V алгоритма характеризовать одним параметром или несколькими параметрами. Одной из таких характеристик является объем входных данных – число элементов входных
данных. Эта характеристика объективно характеризует входные данные так же, как и число шагов объективно характеризует исполне57
ние алгоритма. В свою очередь, устанавливают зависимость объема
входных данных от одного или нескольких параметров, характеризующих задачу. Так, в задаче сортировки массива таким параметром
является длина n массива, в задаче нахождения кратчайшего пути
в графе объем входных данных связан с числом n вершин графа, а
в задаче нахождения кратчайшего остовного дерева объем данных
зависит от числа m ребер графа. В других задачах оптимизации на
графах в качестве параметров выбирается и число n вершин, и число
m ребер.
Так как число шагов алгоритма зависит не только от выбранных
в задаче параметров P = (n, m, . . . ), характеризующих объем входных данных V = (P, X), но и от других характеристик входных
данных X = (x1 , x2 , . . . ), то мы введем оценку по всем этим характеристикам. Оценка наибольшего числа шагов, необходимых для
выполнения алгоритма, в зависимости от параметров P :
t = t(P, X) ≤ max t(P, X) ≡ T (P ),
X
называется максимальной трудоемкостью алгоритма или просто
трудоемкостью алгоритма. Максимальная трудоемкость дает нам
возможность оценить максимальное время, необходимое для исполнения алгоритма. Эта оценка может быть очень завышенной в некоторых случаях. Поэтому важно иметь оценку наименьшего числа
шагов, которую мы назовем минимальной трудоемкостью:
t = t(P, X) ≥ min t(P, X) ≡ Tmin (P ),
X
и оценку среднего числа шагов, которую мы назовем средней трудоемкостью:
1X
t(P, X) ≡ Tcp (P ),
t = t(P, X) ≈
k
X
где k – число вариантов других характеристик входных данных.
2.2.2
Оценка трудоемкости алгоритма
Для простоты мы будем предполагать, что задача имеет 1 параметр
n, и обращать внимание на отличия, когда это не так. Трудоемкость
58
алгоритма позволяет оценить время выполнения алгоритма при решении той или иной задачи:
Tφ (X) ≤ τ̄ · T (n).
При этом коэффициент τ̄ статистически определяется для исполнителя или оценивается (что чаще бывает) некоторой константой.
Однако точный вид зависимости T (n) от аргумента n часто очень
трудно (практически невозможно) установить. Поэтому вместо установления вида функции для трудоемкости оценивается быстрота роста этой функции при помощи некоторой простой функции f (n).
Говорят, что
T (n) = O(f (n)),
если |T (n)| ≤ C|f (n)| для всех значений n > n0 . Такая оценка роста
функции T (n) является односторонней, так как функция f (n) может
расти быстрее. Лучше оценивать рост трудоемкости функцией f (n),
имеющей тот же порядок роста, т. е. также |T (n)| ≥ C1 |f (n)|. В этом
случае пишут
T (n) = Θ(f (n))
и говорят, что рост T (n) оценивается ростом f (n). Наиболее простыми функциями, оценивающими рост трудоемкости, являются полиномы p(n) = nk +c1 nk−1 +· · ·+ck . В случае T (n) = Θ(p(n)), учитывая,
что p(n) = Θ(nk ), получаем T (n) = Θ(nk ). Говорят, что в этом случае
трудоемкость полиномиальна или алгоритм имеет полиномиальную
трудоемкость. При k = 1 T (n) = Θ(n) и алгоритмы принято называть
алгоритмами с линейной трудоемкостью.
Если есть 2 алгоритма A1 и A2 решения некоторой задачи и оба
имеют полиномиальную трудоемкость, причем k1 < k2 , то говорят,
что первый алгоритм имеет меньшую трудоемкость. Но меньшая
трудоемкость не означает, что время решения задачи первым алгоритмом будет меньше, чем вторым. Так, пусть
9n2 ≤ T2 (n) ≤ 10n2 .
90n ≤ T1 (n) ≤ 100n,
Тогда при n < 10 оказывается, что время решения задачи для первого
алгоритма больше, чем для второго. Однако из определения ясно, что
найдется такое n0 (в примере n0 = 10), что время решения задачи
при n > n0 будет всегда меньше для первого алгоритма.
59
Трудоемкость алгоритма может иметь скорость роста меньшую,
√
чем линейная. Например, T (n) = Θ( n) или T (n) = log2 n. Но и
в этом случае принято говорить о полиномиальной трудоемкости.
Алгоритмы, трудоемкость которых растет быстрее любого полинома,
принято называть алгоритмами экспоненциальной трудоемкости, даже если скорость роста трудоемкости оценивается более медленной
функцией, чем экспонента. Например, экспоненциальными являются
все алгоритмы со следующими трудоемкостями:
n
T (n) = Θ(2 ),
T (n) = Θ(2
√
n
),
2
T (n) = Θ(nn ).
Причина, по которой используются только эти 2 названия трудоемкости (полиномиальная и экспоненциальная), состоит в том, что
алгоритмы полиномиальной трудоемкости, как правило, эффективны, если показатель степени у полинома не слишком большой. А
алгоритмы экспоненциальной трудоемкости не являются эффективными, так как время вычисления по этим алгоритмам растет «космически» быстро. В следующей таблице показана скорость нарастания
времени работы алгоритмов различной трудоемкости в секундах на
компьютере с быстродействием 106 оп/сек.
n
n
n2
n3
n5
2n
3n
10
0.00001
0.0001
0.001
0.1
0.001
0.059
20
0.00002
0.0004
0.008
3.2
1.0
58 мин.
30
0.00003
0.0009
0.027
24.3
17.9 мин.
6.5 лет
40
0.00004
0.0016
0.064
1.7 мин.
12.7 дней
385500 лет
50
0.00005
0.0025
0.125
5.2 мин.
35.7 лет
200 · 108 лет
Таблица показывает так называемый «комбинаторный» взрыв, когда
при переходе от полиномиальных алгоритмов к экспоненциальным
алгоритмам время нарастает так быстро, что уже при n = 30 задачу
решать очень трудно, а при больших значениях параметра – совсем
невозможно. Для решения некоторых дискретных задач неизвестны
алгоритмы полиномиальной трудоемкости и их приходится решать
перебором всех комбинаций, а так как число перестановок n! и чисn/2
ло выборок Cn
растет так же быстро, как nn , то трудоемкость таких
60
задач экспоненциальна. Отсюда следует и название – «комбинаторный» взрыв.
Заметим теперь, что при нескольких параметрах входных данных трудоемкость полиномиального алгоритма растет как полином
от нескольких аргументов. Например,
T (n, m) = Θ(n · m2 ),
2.2.3
T (n, m, k) = Θ(n2 k log2 m).
Анализ алгоритмов и методика оценивания трудоемкости
Процесс получения оценки трудоемкости называется оцениванием
трудоемкости. Для этого следует анализировать алгоритм с точки
зрения быстроты роста числа его шагов при изменении параметров
задачи (параметров входных данных).
Прежде всего в алгоритме следует выделить циклы. Если циклов нет, то число шагов линейной структуры алгоритма не зависит
от параметров задачи и, следовательно, трудоемкость является константной, т. е. оценивается как Θ(1).
Циклическая структура алгоритма ведет к повторению выполнения его частей, что влияет на общее число шагов выполнения, т. е.
на трудоемкость. Следует оценить для каждого цикла, от каких параметров задачи зависит число повторений цикла и как оно растет с
ростом этих параметров.
Если цикл B с числом повторений n(B) вложен в цикл A с числом
повторений n(A) и циклы независимы (число повторений цикла B не
зависит от выполнения цикла A), то общее число повторений цикла
B с учетом повторений цикла A составляет n(A) · n(B). Отсюда
правило:
для вложенных независимых циклов их трудоемкости перемножаются Θ(AB) = Θ(A) · Θ(B).
Если вложенные циклы не являются независимыми, т. е. число повторений внутреннего цикла ni (B) зависит от номера i повторения
при выполнении внешнего цикла, то нужно проанализировать, как
Pn(A)
зависит общее число повторений i=1 ni (B) внутреннего цикла от
параметров задачи.
61
Если циклы не являются вложенными, то трудоемкость определяется наибольшей из трудоемкостей циклов
Θ(A + B) = Θ(A) + Θ(B) = max{Θ(A), Θ(B)}.
Заметим теперь, что при оценке максимальной трудоемкости следует подбирать такие примеры входных данных для тех или иных
параметров задачи, на которых реализуется максимальное число шагов алгоритма. При оценке минимальной трудоемкости следует подбирать примеры, на которых реализуется минимальное число шагов
алгоритма. Ввиду сложности некоторых алгоритмов такие примеры
не всегда удается построить, но в таких случаях для оценки трудоемкости бывает достаточно примеров и близких по числу операций
к максимальному или соответственно к минимальному числу.
Рассмотрим ряд примеров оценивания трудоемкости.
Пример 1. Алгоритм Дейкстры нахождения кратчайшего пути между вершинами a и b связного графа G(X, U ). Пусть n = |X| – число
вершин графа и l(xi , xj ) – расстояние от xi до xj . Можно привести
следующее пошаговое описание алгоритма:
1. Определить начальный список S помеченных вершин, включив
в него a(0) (в скобках – пометка), начальное значение последней
помеченной вершины last = a и начальный список непомеченных вершин N = {x1 (λ1 ), . . . }, записав в скобках расстояние
от вершины до a : λi = l(a, xi ), если (a, xi ) ∈ U, или λi =
∞, если иначе.
2. Найти минимальное значение λj = mini λi .
3. Переместить вершину xj из списка N в список S с пометкой
значением last и определить новое значение last = xj .
4. Пересчитать значения λi для списка N , положив λi = min{λi , λj +
l(xj , xi )}.
5. Если xj = b, закончить выполнение алгоритма, иначе перейти к
шагу 2.
В качестве параметра задачи естественно выбрать число n вершин
графа. Анализ алгоритма показывает, что он содержит несколько
циклов:
62
1) цикл шага 1, который выполняется n − 1 раз, т. е. его трудоемкость Θ(n);
2) выполнение шагов 2 - 5 повторяется, т. е. идет циклически.
Назовем этот цикл внешним. Его повторение зависит от входных
данных и максимально, когда вершина b включается в список S последней, и минимально, когда она включается сразу же за вершиной a. Поэтому при определении максимальной трудоемкости нужно
брать для этого цикла оценку Θ(n), а при определении минимальной
трудоемкости – оценку Θ(1);
3) внутри внешнего цикла выполняются 4 шага, при этом шаги
3 и 5 не содержат циклов, и потому их трудоемкости оцениваются
как Θ(1). Шаги 2 и 4 содержат внутренние циклы, среднее число
повторений которых равно n/2. Поэтому трудоемкости каждого из
этих циклов можно оценить как Θ(n). Суммарная трудоемкость тела
внешнего цикла равна Θ(n) + Θ(1) + Θ(n) + Θ(1) = Θ(n).
Учитывая полученные трудоемкости циклов, можно оценить максимальную трудоемкость как
Θ(n) + Θ(n) · Θ(n) = Θ(n2 ),
а минимальную трудоемкость – как
Θ(n) + Θ(1) · Θ(n) = Θ(n).
Замечание. Внешний и внутренние циклы не являются независимыми – мы обошли эту трудность, взяв среднее число повторений
внутренних циклов. Более аккуратно при оценивании максимальной
трудоемкости следует брать число повторений внутренних циклов
равным n − 1, n − 2, . . . , 1, и тогда повторение их тела во внешнем
цикле составит (n − 1) + (n − 2) + · · · + 1 = n(n − 1)/2, что также
дает Θ(n2 ). При оценивании минимальной трудоемкости внешний
цикл выполняется 1 раз, а внутренний – (n − 1) раз, поэтому оценка
трудоемкости будет Θ(n).
Пример 2. Сортировка массива методом пузырька задана процедурой на C++:
63
void SortP (int n, f loat X[])
{ Bool b = true;
f or (int k = n − 1; b && k > 0; k − −)
f or (int i = 0, b = f alse; i < k; i + +)
if (X[i] > X[i + 1])
{ f loat r = X[i];
X[i] = X[i + 1];
X[i + 1] = r;
b = true;
}
}
Процедура содержит вложенные циклы. Внешний цикл в случае массива входных данных, упорядоченного по убыванию, будет выполняться максимальное число раз: n − 1, а в случае входного массива,
упорядоченного по возрастанию, будет выполняться только 1 раз.
Внутренний цикл во втором случае выполняется n − 1 раз, а в первом случае циклы зависимы, но, как и в предыдущем примере, внутренний цикл в среднем выполняется n/2 раз. Поэтому максимальная трудоемкость (входные данные первого случая) оценивается как
Θ(n) · Θ(n) = Θ(n2 ), а минимальная трудоемкость (входные данные
второго случая) – как Θ(1) · Θ(n) = Θ(n).
Пример 3. Оценить трудоемкость следующей процедуры на С++
при n > 0:
long f (int n)
{ long j = 1;
while (n > 0) j <<= n − −;
while (j > 0) j >>= 1;
return j;
}
Процедура имеет 2 независимых цикла (не вложенных один в другой). Вернее, начальное значение параметра j второго цикла определяется в первом цикле, так что некоторая зависимость циклов есть.
Первый цикл будет выполняться ровно n раз, если n > 0. Поэтому
его трудоемкость оценивается как Θ(n). В результате выполнения
первого цикла параметр j примет значение 2n+(n−1)+···+1 = 2n(n+1)/2 .
64
Второй цикл будет выполняться log2 j = n(n + 1)/2 раз, так как в
теле цикла параметр j уменьшается ровно в 2 раза, и, значит, трудоемкость этого цикла оценивается как Θ(n2 ). Поэтому трудоемкость
процедуры оценивается как Θ(n) + Θ(n2 ) = Θ(n2 ).
Пример 4. Оценить трудоемкость алгоритма Краскала нахождения
минимального остовного дерева связного графа G(X, U ) с заданной
неотрицательной функцией длин ребер l(u), u ∈ U . Алгоритм Краскала на первом шаге упорядочивает все ребра по возрастанию длины, а на втором шаге, который выполняется циклически, в строящееся остовное дерево выбираются минимальные по длине ребра,
не приводящие к циклу с уже выбранными в дерево. Для оценки
трудоемкости первого шага мы должны взять трудоемкость сортировки (m ребер по длине). Но эта трудоемкость будет зависеть от
выбранного метода сортировки. Для получения объективной оценки
трудоемкости алгоритма Краскала мы должны взять «лучший» по
трудоемкости алгоритм сортировки. Но что значит «лучший»? Этот
вопрос мы рассмотрим в разделе 2.3 и там продолжим рассмотрение
этого примера.
2.2.4
Характеристики временной сложности программы
Оценка трудоемкости еще не позволяет ничего сказать о времени
выполнения программы, реализующей алгоритм, для тех или иных
данных. Мы можем судить только о быстроте роста этого времени.
Скажем, если трудоемкость оценивается как Θ(n2 ), то с увеличением
параметра n в 2 раза требующееся время будет увеличиваться примерно в 4 раза. Такая качественная оценка не всегда удовлетворяет
нас. Допустим, что мы запустили программу и она уже выполняется
10 минут. Может, программа зациклилась и ее следует снять? А если
нет, то сколько она еще будет выполняться? Таким образом, оценка
времени выполнения программы может стать актуальной. Если программа в начале своей работы выдает, что интервал времени ожидания ее выполнения такой-то и в среднем придется ждать столько-то,
то на такие данные можно опираться при принятии решения, следует
ли продолжить счет.
Введем 3 статистические функции tmin (n), tmax (n), tcp (n), оцени65
вающие время работы программы в зависимости от значения параметра n (при большем числе параметров задачи эти функции имеют
большее число аргументов). Их вид зависит от оценки соответствующей трудоемкости.
Рассмотрим вначале общий случай оценки трудоемкости полиномом T (n) = Θ(nk ). В этом случае выбирается соответствующий вид
функции t(n) = ck nk + ck−1 nk−1 + · · · + c1 n + c0 . Здесь ci (i ∈ 1, k)
– неизвестные коэффициенты, и мы рассматриваем не только коэффициент ck , который определяет время при довольно больших значениях параметра n, но и коэффициенты при меньших степенях n,
которые могут влиять на время при малых значениях параметра n.
Для получения значения коэффициентов ci (i ∈ 1, k) следует выбрать
ряд различных значений параметра n = n1 , n2 , . . . , np , где p > k, и
для каждого значения параметра nj выбрать данные, соответствующие этому параметру, и провести вычислительный эксперимент с
программой, замерив время ее выполнения τj . Затем по методу наименьших квадратов можно найти коэффициенты ci , которые минимизируют сумму
Pp квадратов 2отклонений полинома в точках nj от значений τj :
j=1 (t(nj ) − τj ) . Для более грубой оценки можно взять
p = k + 1 и решить систему уравнений
k
X
ci nij = τj
(j ∈ 1, k + 1)
i=0
относительно неизвестных ci .
При оценке трудоемкости логарифмической функцией или произведением полинома на логарифмическую функцию следует и функцию времени t(n) строить в подобном виде. Так, при оценке трудоемкости T (n) = Θ(n · log2 n) следует выбрать вид t(n) = c1 n log2 n +
c2 n + c3 log2 n + c4 , а при T (n) = Θ(n2 log2 n) следует выбрать вид
t(n) = c1 n2 log2 n + c2 n2 + c3 n log2 n + c4 n + c5 log2 n + c6 . Проведя
достаточное количество вычислительных экспериментов, построив и
решив соответствующую систему уравнений, можно найти коэффициенты функции времени t(n).
√
При оценке трудоемкости T (n) = Θ( n) следует выбрать вид
√
t(n) = c1 n + c2 , а затем провести вычислительные эксперименты и
66
найти неизвестные коэффициенты, решив соответствующую систему
уравнений.
При экспоненциальной оценке трудоемкости T (n) = Θ(2n ) можно
взять соответствующий вид функции t(n) = c1 ·cn2 и, проведя 2 вычислительных эксперимента для n1 , n2 , найти τ1 , τ2 . Так как ττ21 = cn2 1 −n2 ,
1
1
2
то c2 = ( ττ21 ) n1 −n2 и c1 = τ1 c−n
= τ2 c−n
2
2 . Аналогичным образом можно
поступать при другом виде экспоненциальной оценки трудоемкости.
В тех случаях, когда оценка трудоемкости алгоритма включает
в себя несколько параметров задачи, вид функций времени следует
выбирать соответствующим такому виду оценки трудоемкости. Например, при T (n, m) = Θ(nm2 ) следует функцию времени искать в
виде t(n, m) = c1 nm2 + c2 nm + c3 n + c4 m + c5 . И в других случаях
следует поступать подобным образом.
Теперь сделаем 3 замечания относительно выбора данных, выбора
ряда параметров задачи для вычислительного эксперимента и организации проведения эксперимента:
1. При построении функции максимального времени следует выбирать данные, приводящие к максимальному числу шагов алгоритма. Если не представляется возможным построить такие данные, то следует для каждого значения параметра nj провести не
1, а много экспериментов (например, 10) и взять максимальное
значение времени. Аналогичным образом следует поступать при
построении функций минимального времени и среднего времени.
2. Значения параметров для вычислительного эксперимента следует выбирать такими, чтобы время эксперимента не было слишком малым, так как точность измерения времени имеет порядок
0.02 сек. Поэтому разумно выбирать значения параметров, дающих время эксперимента не менее 10 сек. Однако в ряде случаев данные эксперимента могут составлять значительный объем
и возникает проблема ввода таких данных и проблема памяти
для них. Первую из них можно решить программной генерацией
данных, возможно, с использованием датчика случайных чисел,
но вторую проблему так просто одолеть не удается. Поэтому
вместо параметров, приводящих к значительным объемам дан67
ных, можно выбрать меньшие значения параметров, но вызов
процедуры повторять k раз (k = 102 , . . . , 106 ) в цикле. Для исключения времени этого цикла следует замерить время пустого
цикла с таким же числом повторений и вычесть его из времени
цикла вызова процедуры. Полученное значение времени следует
разделить на k.
3. Ряд значений параметра (параметров) также нужно выбирать
разумным способом. Если трудоемкость оценивается как Θ(log n),
то выбор значений параметра n, отличающихся друг от друга на
несколько единиц, мало скажется на изменении времени эксперимента и потому приведет к большим погрешностям (система
уравнений получится близкой к вырожденной). Поэтому в таком случае каждое следующее значение параметра можно выбирать в 2 раза больше предыдущего. Если же трудоемкость
имеет экспоненциальную оценку, то, наоборот, не следует выбирать слишком далекие друг от друга значения параметра: так,
при T (n) = Θ(2n ) увеличение параметра на 10 ведет к увеличению времени в 1 000 раз. Будет разумным в этом случае, чтобы
следующее значение параметра было на 1-2 больше значения
предыдущего.
Помимо полученных функций максимального, минимального и
среднего времени выполнения параметров следует также указать
производительность процессора, на котором производились вычислительные эксперименты. Для использования этих функций на компьютере с другой производительностью следует всегда вычисленное значение функций умножать на отношение производительностей процессоров, для чего макросом задавать производительность
используемого процессора.
В заключение сделаем следующее замечание. При вычислении коэффициентов функций времени может оказаться, что коэффициент
при самой старшей степени полинома ничтожно мал, в особенности
по сравнению с коэффициентами других степеней. Это может говорить о том, что соответствующая трудоемкость оценена неверно и на
самом деле ее оценка ниже. В таком случае следует повторить расчеты с более низкой оценкой трудоемкости и проконтролировать эти
68
расчеты дополнительными экспериментами с более высокими значениями параметров, для чего сравнить время эксперимента и значение
функции. Такой контроль необходим и в случаях, когда такой ситуации нет. Если при контроле оказывается, что время эксперимента
значительно превышает значение функции, то это говорит о том, что
оценка соответствующей трудоемкости занижена. Необходимо и в
этом случае пересмотреть оценку трудоемкости и пересчитать коэффициенты функции.
2.2.5
Упражнения
1. Определите оценки минимальной и максимальной трудоемкости
сложения чисел с последовательным уменьшением одного на 1
и увеличением другого на 1 на МТ из упражнения 1.2.3.5.
2. Определите оценки минимальной и максимальной трудоемкости
сложения чисел столбиком на МТ из упражнения 1.2.3.6.
3. Определите трудоемкость обычного возведения натурального числа в целую степень и трудоемкость возведения в степень следующим алгоритмом:
long pow (int m, int n)
{ long k = 1;
while (n)
{ if (n & 1) k∗ = m;
m∗ = m;
n >>= 1; }
return k;
}
4. Опишите методику получения функции времени программы с
трудоемкостью Θ(nn ).
5. Опишите методику получения функции времени программы с
√
трудоемкостью Θ( n ln n).
6. Опишите методику √получения функции времени программы с
трудоемкостью Θ(2n n ).
69
7. Определите трудоемкость следующего алгоритма:
void f (int n)
{ unsigned int x[n]; int i, k;
x[0] = 1;
f or (i = 1; i < n; i + +) x[i] = 0;
while (x[n − 1] < 65535)
{ i = 0; k = 1;
while (k)
if (x[i] < 65535) {x[i] + +; k = 0; }
else x[i + +] = 0;
}
}
2.3 Трудоемкость задач
2.3.1
Определение трудоемкости задачи
Для решения какой-либо задачи может существовать несколько алгоритмов. Естественно для практического применения использовать
алгоритм более быстрый. И хотя мы видели, что быстродействие
алгоритма не всегда связано с наименьшей трудоемкостью, а может зависеть также от данных, но во многих случаях использования
меньшая трудоемкость означает и большее быстродействие.
Иногда удается найти точную нижнюю грань inf A TA (n) для оценки максимальной трудоемкости снизу, и если среди алгоритмов есть
такой алгоритм A0 , для которого эта оценка достигается TA0 (n) =
inf A TA (n), то его трудоемкость называется трудоемкостью задачи,
а алгоритм A0 называется оптимальным по трудоемкости алгоритмом.
Мы рассмотрим 2 важных примера: задачу поиска информации в
упорядоченном массиве и задачу сортировки массива. Этим задачам
посвящена следующая глава 3.
70
2.3.2
Задача поиска в упорядоченном массиве
Задача поиска информации является одной из наиболее широко используемых в различных алгоритмах. Алгоритмы поиска информации и их трудоемкость существенно зависят от организации самой
информации. Впоследствии мы увидим, что можно применить такую
организацию, при которой поиск нужной информации будет происходить почти мгновенно или трудоемкость поиска оценивается как
Θ(1). Но можно и так организовать информацию (вернее, никак не
организовывать), что для поиска нужной информации в худшем случае придется всю ее перебрать. Эти 2 случая являются крайними.
Чаще всего информацию организуют следующим образом: ее делят на части, и с каждой из них связывают специальную характеристику для поиска, называемую ключом. Чаще всего ключи целочисленные, реже они числовые (не обязательно целочисленные) или
представляются строками символов. Ключи, как правило, имеют различные значения, хотя не исключается случай, когда некоторые из
ключей могут иметь одинаковые значения. Ключи организуются в
массив ключей, где с каждым ключом связан указатель на информацию. Для поиска информации задается поисковый ключ. В массиве
ключей ищется значение поискового ключа, и либо он будет найден
(и тогда найдена необходимая информация), либо будет определено,
что среди ключей нет поискового (и тогда нужной информации нет).
Если о ключах неизвестно никакой информации, то для ее поиска не остается ничего другого, как сравнивать в каком-то порядке
все ключи массива с поисковым. В худшем случае, если количество
ключей n, то придется сравнить с каждым из n ключей, и потому
трудоемкость такого поиска Θ(n).
Если ключи упорядочены по возрастанию (такой массив называется отсортированным по возрастанию: k1 < k2 < · · · < kn ), то
для отыскания значения поискового ключа k мы также будем сравнивать его с ключами массива, т. е. мы предполагаем, что никакой
дополнительной информации нам неизвестно (мы увидим в следующей главе, что возможна такая организация информации, которая
не требует сравнения ключей) и для поиска информации необходимо
найти ее ключ в массиве ключей.
71
При сравнении ключей k > ki может быть получен либо положительный ответ (и тогда для дальнейшего поиска мы можем отбросить часть ключей с индексами от 1 до i), либо отрицательный
ответ (и тогда дальнейший поиск мы можем производить только в
части ключей с индексами от 1 до i). Так как трудоемкость поиска
очевидным образом монотонно растет с числом элементов, среди которых нам нужно найти элемент со значением поискового ключа, то
в худшем случае мы должны взять бóльшую часть, длина которой
max{i, n−i}. Так как max{i, n−i} ≥ n2 , то число операций сравнения
не может быть меньше, чем log2 n. Поэтому трудоемкость поиска в
упорядоченном массиве может быть оценена снизу TA ≥ Θ(log2 n).
Покажем теперь, что алгоритм с такой трудоемкостью поиска существует. Определим алгоритм бинарного поиска тем, что мы будем сравнивать со средним по индексу элементом массива (такой
элемент массива называется его медианой) i = [(n + 1)/2]. Пусть
n = 2k . Тогда после каждого сравнения с медианой оставшейся части массива число элементов остающейся части не превосходит n/2,
и потому после k сравнений у нас останется 1 элемент. Следовательно, в этом случае число операций сравнения k + 1 = Θ(log2 n).
Если же n > 2[log2 n] , то после выполнения [log2 n] + 1 сравнений у нас
останется 1 элемент, и, следовательно, в этом случае число операций сравнения [log2 n] + 2 = Θ(log2 n). Таким образом, трудоемкость
бинарного поиска Tbin (n) = Θ(log2 n), алгоритм бинарного поиска является лучшим по трудоемкости, а задача поиска в упорядоченном
массиве имеет трудоемкость Θ(log2 n).
2.3.3
Задача сортировки массива сравнением его элементов
Рассмотренная задача поиска информации показывает важность такой организации массива, как его сортировка. Существует много алгоритмов сортировки, различных по своим идеям, и мы их проанализируем в следующей главе. Трудоемкость алгоритма может зависеть
от дополнительной информации об элементах массива, но мы сейчас будем предполагать, что такая информация отсутствует. Многие
из алгоритмов сортировки, например рассмотренный ранее метод пузырька, основаны на сравнении элементов и изменении порядка в
72
зависимости от результатов сравнения. Трудоемкость таких алгоритмов пропорциональна числу операций сравнения, и потому оценка
трудоемкости может быть получена через оценку числа операций
сравнения.
Мы будем рассматривать только алгоритмы, основанные на сравнении элементов и изменении их порядка. Трудоемкость метода пузырька Θ(n2 ), но есть алгоритмы с меньшей трудоемкостью. Установим нижнюю границу трудоемкости таких алгоритмов. Для этого
мы обратим внимание на то, что задачу сортировки можно интерпретировать как задачу распознавания порядка всех элементов массива.
Если мы знаем порядок элементов, то расставить их в порядке возрастания можно за линейное число операций: зная порядковый номер
каждого элемента, мы можем сразу ставить его на место и так делать по очереди элементов один за другим. Массив X = (x1 , . . . , xn )
из n элементов может иметь n! различных порядков элементов, соответствующих перестановкам из n элементов. Обозначим множество
всех перестановок через P . Каждая операция сравнения элементов
xi > xj делит множество P на 2 части: P1y , где это высказывание
истинно, и P1n , где оно ложно. Максимальная часть содержит не
менее половины перестановок: max{|P1y |, |P1n |} ≥ n!/2. Продолжая
сравнивать другие элементы, мы снова будем разбивать полученную
часть на 2 и оценивать снизу число перестановок большей части через половину полученной оценки: max{|P2y |, |P2n |} ≥ n!/22 , и т.д. до
тех пор, пока мы не достигнем такого числа сравнений k, при котором n!/2k = 1. Тогда, когда большая часть будет состоять из одной
перестановки, мы найдем порядок элементов массива. Но для этого нужно совершить не менее чем k операций сравнения. Так как
n! = Θ(nn ), то k = log2 n! = Θ(n log2 n), и, следовательно, оценка
снизу для трудоемкости алгоритма Θ(n log2 n).
Теперь мы покажем существование такого алгоритма, трудоемкость которого имеет именно эту оценку. Таких алгоритмов известно
несколько, но первый из них был построен Джоном фон Нейманом
и носит название алгоритма фон Неймана.
Алгоритм фон Неймана основан на многократном процессе слияния двух упорядоченных отрезков массива. Если длины этих отрезков равны l1 и l2 , то для получения общей упорядоченной последова73
тельности из l1 + l2 элементов потребуется самое большее l1 + l2 − 1
операция сравнения: мы выбираем для сравнения наименьшие элементы обеих последовательностей, записываем меньший из них в
выходную последовательность и заменяем его для последующего
сравнения выбором следующего элемента из той последовательности, из которой он был выбран; так поступаем до тех пор, пока одна
из последовательностей не станет пустой, и тогда остаток другой
последовательности переписываем в выходную последовательность.
Идея алгоритма фон Неймана может быть описана следующей
последовательностью шагов:
1) массив X разбивается на пары рядом стоящих элементов (n/2
пар), и каждая пара упорядочивается ( n2 · 1 операций сравнения);
2) массив пар разбивается на рядом стоящие упорядоченные пары
(n/4 пары по 2 элемента), и каждые 2 пары сливаются в одну
упорядоченную четверку (≤ 3 операций сравнения для четверки,
а всего n4 · 3 операции сравнения);
.................................................................
i) сливаются в упорядоченные последовательности каждые 2 рядом стоящие последовательности из 2i−1 элементов (требуется
не более 2i − 1 операция сравнения по 2ni раз) и т.д., пока мы
не получим упорядоченный массив (на каждом шаге последняя
в процессе слияния в массиве последовательность может быть
неполной, что только уменьшит число операций сравнения).
Оценим число операций сравнения для случая n = 2k :
k
k
2k−1 ·1+2k−2 ·3+· · ·+20 ·(2k −1) ≤ 2| k + 2k +
{z· · · + 2} = k2 = n log2 n.
k
При 2k < n < 2k+1 число операций сравнения будет меньше, чем
(k + 1)2k+1 < 2n(log2 n + 1).
Поэтому трудоемкость алгоритма фон Неймана TN (n) = Θ(n log2 n).
Таким образом, алгоритм фон Неймана является оптимальным по
трудоемкости для задачи сортировки массива, а эта задача имеет
74
трудоемкость Θ(n log n) (обосновано только для алгоритмов, использующих операции сравнения).
В заключение вернемся к примеру 4 из раздела 2.2.3, в котором
требовалось оценить трудоемкость алгоритма Краскала. Поскольку
трудоемкость сортировки ребер Θ(m log m), а второй шаг алгоритма Краскала (проверка возникновения цикла) можно реализовать
линейным по трудоемкости алгоритмом, то трудоемкость алгоритма
Краскала, учитывая, что m = Θ(n2 ), можно оценить как Θ(n2 log n).
Заметим, что для построения остовного дерева есть более сложный
алгоритм Прима, имеющий меньшую трудоемкость Θ(n2 ). А в тех
случаях, когда граф плоский, существует алгоритм трудоемкости
Θ(n log n).
2.3.4
Упражнения
1. Определите трудоемкость (с обоснованием) сортировки слиянием двух упорядоченных массивов. Указание: обоснуйте оптимальность по трудоемкости алгоритма слияния, приведенного в описании алгоритма фон Неймана.
2. Разработайте программу процедуры бинарного поиска.
3. Разработайте программу процедуры алгоритма фон Неймана сортировки.
4. Даны 32 монеты, различные между собой по весу. Весы без
гирь позволяют сравнить за 1 взвешивание вес двух групп монет. Как за 35 взвешиваний найти 2 самые легкие монеты? Разработайте алгоритм и программу. Указание: используйте идеи
алгоритма фон Неймана для отыскания самой легкой монеты и использования полученной информации для отыскания
следующей по легкости монеты.
75
2.4 Литература
1. Ахо А., Хопкрофт Дж., Ульман Дж. Построение и анализ вычислительных алгоритмов. М., 1979.
2. Кнут Д. Искусство программирования для ЭВМ. Т. 1: Основные
алгоритмы. М., 1977.
3. Кормен Т., Лейзерсон Л., Ривест Р. Алгоритмы: построение и
анализ. М., 2001.
4. Кузнецов О.П., Адельсон-Вельский Г.М. Дискретная математика для инженера. М., 1988.
5. Пападимитриу Х., Стайглиц К. Комбинаторная оптимизация.
Алгоритмы и сложность. М., 1985.
76
Глава 3
Сортировка и поиск
3.1 Способы организации информации для поиска
Поиск информации является одной из самых распространенных задач многих алгоритмов, и потому быстрый поиск и организация с
этой целью информации – одна из наиболее интересных задач информатики. В предыдущей главе мы рассмотрели бинарный поиск
информации, дающий очень быстрый алгоритм поиска логарифмической трудоемкости. Для такого поиска информационные ключи организуют в массив и сортируют этот массив в порядке возрастания
или убывания ключей. Поэтому задача сортировки массива является
важной и для ее решения разработаны многочисленные алгоритмы.
Другой способ работы с информационными ключами состоит в
их организации в виде поискового дерева, и хотя скорость поиска с
помощью деревьев не всегда возрастает по сравнению с поиском в отсортированном массиве, но операции включения новых ключей являются менее трудоемкими по сравнению с включением новых ключей
в отсортированный массив, что дает сильное преимущество использованию в таких случаях организации деревьев.
Наиболее быстрым в поиске информации является, однако, способ, при котором вместо организации ключей в отсортированный
массив или поисковое дерево можно использовать специальную функцию, позволяющую по значению ключа сразу вычислить расположение нужной информации. Такие функции, называемые функциями
расстановки, не всегда возможны, но и в случае возможности требуют решения ряда проблем.
77
В следующих разделах мы рассмотрим эти 3 способа организации
информации.
3.2 Сортировка массива
3.2.1
Виды сортировки
Различают внутреннюю сортировку, когда весь массив ключей располагается в оперативной памяти компьютера, и внешнюю сортировку, когда массив или его часть находятся во внешней памяти.
При внешней сортировке необходимо решить ряд проблем, оказывающих влияние на скорость сортировки.
Различают также 3 вида сортировки:
1. Сортировка самих ключей, когда при изменении порядка ключей
они перемещаются. В этом случае использование ключа k[i] в
алгоритме бинарного поиска информации идет непосредственно,
но при «длинных» ключах тратится дополнительное время на
перемещение ключей при сортировке.
2. Сортировка индексов, при которой вместо ключей перемещаются их индексы, что может быть намного быстрее, но обращение
к ключу k[Ind[i]] идет через его индекс Ind[i].
3. Сортировка списка ключей, требующая меньших затрат для коррекции списка, но при которой бинарный поиск становится невозможным.
Конечно, одним и тем же методом сортировки можно осуществлять
любой из этих видов сортировки, но алгоритмы (программы) должны
быть различными (или настраиваемыми).
3.2.2
Основные методы внутренней сортировки
Мы уже рассмотрели в предыдущей главе 2 метода сортировки массива: метод пузырька и метод фон Неймана. Эти методы имеют разную трудоемкость и сложность организации, а потому и область их
применения различна. Так, если число элементов поискового массива меньше 10, то вряд ли имеет смысл организовывать сортировку,
78
а затем использовать бинарный поиск, так как число операций бинарного поиска будет не меньше, чем число операций при просмотре
всего массива подряд со сравнением с поисковым ключом. Если число элементов массива находится в пределах от 10 до 200, то метод
пузырька по времени будет работать быстрее метода фон Неймана.
А вот при больших размерностях массива имеет смысл использовать метод фон Неймана или аналогичный ему по трудоемкости.
При дополнительной информации об элементах массива возможна
сортировка более быстрая по трудоемкости, чем метод фон Неймана. Кроме того, методы, имеющие одну и ту же оценку трудоемкости,
могут иметь все же разную трудоемкость, если учитывать и коэффициент. Поэтому имеет смысл рассмотреть разные идеи, на которых
основаны методы сортировки.
Сортировка подсчетом
Идея этого метода состоит в подсчете для каждого ключа числа
ключей с меньшими значениями. В предположении, что все ключи
различны, если для ключа kj имеется i ключей с меньшими значениями, то ключ kj следует поставить на (i + 1)-е место. Для сравнения
всех ключей необходимо произвести число операций сравнения порядка Θ(n2 ). Поэтому в таком виде этот метод не стоит применять.
Однако если диапазон ключей невелик и есть очень много повторяющихся, то метод подсчета можно изменить следующим образом:
пусть возможны всего p различных целочисленных значений ключей в диапазоне от 1 до p, где p < n. Организуем массив счетчиков
C1 , . . . , Cp с начальными нулевыми значениями и, просматривая массив ключей, для ключа kj будем увеличивать счетчик Ckj на 1. После
этого, просматривая счетчики, начиная от 2-го, увеличим значение
каждого счетчика Cj на значение предыдущего счетчика Cj−1 . В результате каждый счетчик Cj будет равен числу ключей со значениями, не превосходящими j. Это дает возможность, еще раз просмотрев счетчики, выписать всю отсортированную последовательность
ключей. Например, исходная последовательность содержит 20 ключей
(2, 5, 3, 2, 7, 5, 3, 2, 5, 7, 5, 2, 3, 3, 2, 5, 7, 5, 2, 7)
79
в диапазоне от 1 до 7. Организуем 7 счетчиков, обнулив их. После
просмотра последовательности получим следующие значения счетчиков (0, 6, 4, 0, 6, 0, 4). После суммирования значений счетчиков с
предыдущими счетчиками получим: (0, 6, 10, 10, 16, 20). Поэтому в
отсортированную последовательность ключей выпишем на места с
1 по 6 двойки (единиц нет, так как счетчик равен 0), на следующие
места с 7 по 10 выпишем тройки, далее четверок нет, так как места
с 11 по 10 отсутствуют, и т.д. В результате получим последовательность ключей:
(2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7).
Нетрудно видеть, что трудоемкость метода линейная Θ(n).
Сортировка вставками
Идея метода состоит в том, что элементы по очереди вставляются в
уже упорядоченную часть массива.
При простых вставках сначала отыскивается место для вставляемого элемента, а затем хвост упорядоченной части массива, идущей
после этого места, перемещается на одну позицию вперед, освобождая место для вставляемого элемента. Поскольку трудоемкость поиска места для вставляемого элемента оценивается как Θ(n), трудоемкость раздвижки упорядоченной части массива также оценивается
как Θ(n), и мы эти действия определяем для каждого элемента массива (трудоемкость внешнего цикла оценивается как Θ(n)), то оценка трудоемкости метода простых вставок определяется как Θ(n) ·
(Θ(n) + Θ(n)) = Θ(n2 ).
Число операций простого метода вставок можно уменьшить, если
элементы упорядоченной части начать помещать в середине массива,
раздвигая его затем то вправо, то влево. Но этот прием не приводит к
снижению оценки трудоемкости. Метод бинарных вставок предлагает искать место для вставляемого элемента при помощи бинарного
поиска, но трудоемкость раздвижки массива при этом не изменится,
и потому оценка трудоемкости метода останется прежней Θ(n2 ).
Сортировка вставками является удобной для сортировки списка,
так как сама вставка в список оценивается как Θ(1), но из-за невозможности бинарного поиска в списке оценка трудоемкости – Θ(n2 ).
80
Обменная сортировка
Суть обменной сортировки состоит в том, что ищется пара элементов массива, для которой нарушается упорядоченность, и эти элементы обмениваются местами. К наиболее простым алгоритмам обменной сортировки относится рассмотренный в предыдущей главе
метод пузырька. В этом методе обмениваются местами рядом стоящие элементы. Так как в среднем каждый элемент перемещается
на n/3 позиций, то естественны методы обменной сортировки, которые рассматривают пары из элементов, отстоящих друг от друга на
определенный шаг. К таким алгоритмам относится метод Шелла,
или метод с убывающим шагом, в котором сначала все элементы
делятся на группы по 2 записи, шаг между которыми n/2, и они упорядочиваются. Затем шаг уменьшается в 2 раза, и снова происходят
упорядочивания в каждой группе, и т.д. до тех пор, пока шаг станет
равным 1 и будет упорядочен весь массив. Анализ метода Шелла
(см.: Кнут Д. Искусство программирования для ЭВМ) показывает,
√
что его трудоемкость оценивается как Θ(n n).
Метод Бэтчера похож на метод Шелла, но группы не перекрываются, и, по существу, каждый раз происходит слияние группы из
двух групп, отсортированных на предыдущем проходе. Этот метод
имеет много общего с рассмотренным в предыдущей главе методом
фон Неймана, но в отличие от метода фон Неймана группы элементов не стоят рядом, и потому среднее число обменов снижается.
Трудоемкость этого метода оценивается так же, как и трудоемкость
метода фон Неймана, – Θ(n log n).
В рассмотренных методах обменной сортировки последовательность сравнений предопределена: сравниваются одни и те же пары
ключей. Стратегия, при которой положение сравниваемых ключей
зависит от предыдущих сравнений, была воплощена Ч.Э.Р. Хоаром
в его методе быстрой сортировки. Этот метод состоит в том, что
сначала для сравнения выбирается первый элемент i = 1 и последний элемент j = n. Если обмен после сравнения не требуется, то j
уменьшается и снова производится сравнение. Как только произойдет обмен, увеличивается i до тех пор, пока сравнение не потребует
обмен. Тогда вновь уменьшается j, пока сравнения не приведут к
81
необходимости обмена. Этот процесс продолжается до тех пор, пока
не станет i = j. В этом случае ключ ki займет свою окончательную
позицию на этом месте, и описанная процедура повторяется для последовательности элементов слева от него и для последовательности
элементов справа от него. Этот процесс повторяется снова и снова
до тех пор, пока длина каждой получающейся последовательности
не станет равной 1. Анализ этого метода (там же) показывает, что
средняя трудоемкость метода оценивается как Θ(n log n), но коэффициент при n log2 n меньше, чем у других оптимальных по трудоемкости алгоритмов. Поэтому, несмотря на сложность, метод быстрой
сортировки рекомендуется использовать при больших значениях n.
Однако максимальная трудоемкость для быстрой сортировки оценивается как Θ(n2 ).
Еще один оригинальный метод обменной поразрядной сортировки, в котором отсутствуют операции сравнения элементов, основан
на двоичном представлении ключей. В этом методе столько проходов массива, какова максимальная двоичная разрядность m ключей
и все ключи представляются этим максимальным количеством разрядов. На первом проходе ищется слева направо самый первый номер i
ключа со значением самого старшего двоичного разряда 1 и одновременно справа налево ищется номер j самого правого ключа со значением самого старшего двоичного разряда 0. Если i < j, то эти ключи
обмениваются местами и проход продолжается с i = i + 1, j = j − 1.
Таким образом, после первого прохода массив отсортирован по старшему двоичному разряду ключа и устанавливается граница разбиения массива на 2 части. На втором проходе для каждой из этих частей процесс повторяется для следующего по старшинству двоичного разряда. После m проходов мы получим отсортированный массив.
Трудоемкость метода оценивается как Θ(n · m), и если m < log2 n,
то его трудоемкость меньше, чем Θ(n log n). В этом случае имеется много повторяющихся элементов, и за счет этой информации и
происходит снижение трудоемкости. В общем случае трудоемкость
может даже возрасти.
82
Сортировка выбором
Методы сортировки выбором основаны на идее последовательного
выбора ключей из массива, начиная с наименьшего. Но можно выбирать и в обратном порядке, начиная с наибольшего. Метод простого выбора состоит в выборе сначала максимального элемента,
что можно сделать за n − 1 операцию сравнения элементов, затем
в выборе максимального из оставшихся, для чего потребуется уже
n − 2 операций сравнения, и т.д., пока не будут выбраны все элементы. Трудоемкость этого метода оценивается как Θ(n2 ), и он не
представляет большого интереса.
Однако выбор последующих элементов после выбора максимального можно ускорить, если использовать информацию, которую мы
можем получить при выборе максимального элемента. Для простоты рассмотрим случай n = 2p . Для поиска максимального элемента
мы можем использовать алгоритм выбывания, когда все элементы
массива делятся на n/2 пар и для каждой из них находится максимум (n/2 операций сравнения), затем эти максимумы разбиваются
на n/4 пар и опять для каждой пары находится максимум (n/4 операций сравнения), и т.д. до тех пор, пока не останется 1 пара и
не будет найден ее максимум, который является максимумом всего
массива. Общее число потребовавшихся операций сравнения соста= n − 1. Пусть в последнем
вит n/2 + n/22 + · · · + n/2p = n/2−1/2
1/2
сравнении участвовала пара (ki , kj ) и максимальным оказался ключ
ki . Тогда среди претендентов на второй по максимуму ключ могут
претендовать только те p ключей, с которыми сравнивался ключ ki .
Следовательно, число операций сравнения для получения второго по
максимуму ключа составит p − 1 = log2 n − 1. Подобно этому, для нахождения каждого из следующих ключей потребуется не более чем
log2 n − 1 операция сравнения. Поэтому трудоемкость такого способа
можно оценить как Θ(n) + (n − 1)Θ(log2 n) = Θ(n log2 n).
Описанная идея положена в основу метода, названного пирамидальной сортировкой. В этом методе под пирамидой понимается
такое расположение элементов массива, при котором выполняется
условие:
k[i/2] ≥ ki (∀i : 1 ≤ [i/2] < i ≤ n).
83
Пирамида описывает бинарное дерево ключей, у которого максимальное значение ключа находится в вершине пирамиды, имеющей
номер 1 в массиве; отцом узла дерева с номером i является узел с
номером [i/2], а сыновьями – узлы с номерами 2i и 2i + 1. С другой
стороны, ключи в этом порядке дают отсортированный по убыванию массив, который и требуется построить. Метод пирамидальной
сортировки последовательно преобразует исходный массив ключей в
пирамиду. Его описание можно найти в томе 3 монографии Д. Кнута
«Искусство программирования для ЭВМ: сортировка и поиск».
Отметим, что пирамидальная сортировка в среднем уступает быстрой сортировке, но для худших случаев быстрая сортировка имеет
трудоемкость Θ(n2 ) и потому для очень больших значений n она
может выполняться дольше пирамидальной сортировки.
Сортировка слиянием
Идея метода слияния указана при описании алгоритма фон Неймана. Да и сам алгоритм фон Неймана относится к этой группе методов сортировки. Однако в алгоритме фон Неймана сливаемые вместе
части массивов, как правило, имеют одинаковую длину (все, кроме последней) на каждом проходе алгоритма. Поэтому даже в тех
случаях, когда последовательность уже отсортирована, алгоритм все
равно продолжает работать.
Метод сортировки естественным двухпутевым слиянием ликвидирует этот недостаток. В этом методе границы частей массива, которые сливаются, получаются естественным образом. В начале массива выделяется его максимальная часть, где ключи идут в порядке
возрастания, и отмечается ее граница l1 , начиная с которой ключ
меньше предыдущего, и в конце массива выделяется его максимальная часть, где ключи идут в порядке убывания, и отмечается граница
r1 , начиная с которой ключ больше последующего. Затем этот процесс повторяется до тех пор, пока не наступит условие lp > rp . В этом
случае ключ между границами rp и lp относят к левой части, т. е. rp
увеличивают на 1. Если l1 > n, то массив отсортирован и алгоритм
прекращает свою работу. Если же это не так, то начинают слияние
первого прохода, при котором первые части слева и справа сливают84
ся и записываются слева, а вторые части слева и справа сливаются и
записываются справа и т.д. При записи отмечаются новые границы
частей. После прохода проверяется условие окончания сортировки
и, если оно не выполнено, повторяются слияния частей на втором
проходе. Не более чем через log2 n проходов алгоритм закончит свою
работу.
Трудоемкость метода двухпутевого слияния также оценивается
как Θ(n log n). При больших n он работает быстрее пирамидальной
сортировки, но требует дополнительного расхода памяти.
Распределяющая сортировка
Этот метод не требует операций сравнения и в этом похож на метод поразрядной обменной сортировки. Отличие состоит в том, что,
во-первых, разряды ключа не обязательно бинарные, так как любые
участки ключа, идущие подряд, могут быть интерпретированы как
разряды ключа, и, во-вторых, распределение значений ключа начинается не от старших разрядов, а от младших. Метод часто называют поразрядной сортировкой. Он требует 2n элементов памяти
для исходного и вспомогательного массивов и 2m элементов памяти
для счетчиков, где m максимальное число значений для каждого из
разрядов. Метод состоит в следующем:
1. На первом просмотре исходного массива (назовем его первым)
подсчитываются в первом массиве счетчиков число ключей с
одинаковым значением младшего разряда.
2. На втором проходе массива ключи упорядочиваются по младшему разряду, для чего они переписываются в другой (второй)
массив. При этом для каждого ключа значение счетчика, соответствующего младшему разряду, дает номер этого ключа в новом массиве. Одновременно во втором массиве счетчиков идет
подсчет числа ключей с каждым значением второго разряда.
3. На третьем проходе ключи из второго массива упорядочиваются
по второму разряду с переписью в первый массив и одновременным подсчетом счетчиков третьего разряда справа (используется
первый массив счетчиков).
85
4. Этот процесс продолжается далее по разрядам справа налево,
пока они не будут исчерпаны. Последнее упорядочивание по
старшему разряду даст окончательно отсортированный массив
ключей.
Если p – число разрядов ключа, то трудоемкость метода оценивается как Θ((p + 1)n). На первый взгляд, этот метод по трудоемкости
линеен по n, но p связано с n оценкой p = Θ(log n), и это дает все
ту же оценку Θ(n log n). Но при p = 1 метод превращается в сортировку подсчетом с повторяющимися элементами и его трудоемкость
становится линейной.
Распределяющая сортировка имеет экономное число операций, но
требует дополнительной памяти в 2m + n элементов. Приведем пример. Пусть у нас имеется 106 ключей, каждый из которых определяется 6-разрядным десятичным натуральным числом. Так как m = 10
и p = 6 потребуется дополнительная память в объеме 106 + 20 элементов. Всего потребуется 7 · 106 операций по просмотру ключей
(одновременно такое же количество операций по выделению разряда
и почти такое же по переписи). Если же мы укрупним разряды (по
2 десятичных разряда в 1 стоичный), то потребуется почти такая же
дополнительная память в объеме 106 + 200 элементов, но число просмотров элементов сократится почти в 2 раза (4 · 106 ). Наименьшее
число просмотров ключей будет, если все разряды числа объединить
в 1 разряд. В этом случае потребуется только 106 дополнительных
элементов памяти (под счетчики) и число просмотров элементов массива сократится еще в 2 раза (2 · 106 ). Этот пример показывает, что
трудоемкость может быть сделана линейной за счет введения огромной дополнительной памяти.
3.2.3
Внешняя сортировка
Проблемы внешней сортировки
Если данные сортируемого массива не размещаются целиком в оперативной памяти, то мы говорим о сортировке файла, находящегося во внешней памяти. Такая сортировка называется внешней. При
внешней сортировке приходится разделить внешний файл на порции
86
или более мелкие файлы, которые уже целиком размещаются в оперативной памяти и могут быть там отсортированы одним из алгоритмов внутренней сортировки. Затем эти части в оперативной памяти
сливаются постепенно в более длинные отсортированные массивы,
которые снова делятся на части и записываются во внешнюю память. Этот процесс продолжается до тех пор, пока все части массива в определенном порядке их нумерации не станут образовывать
единого отсортированного массива или все мелкие файлы не будут
слиты в один отсортированный файл.
Время внешней сортировки можно разделить на 2 части:
1) время всех действий в оперативной памяти, связанных с внутренней начальной сортировкой частей массива и последующей сортировкой слиянием этих частей;
2) время обращения ко внешней памяти, которое зависит от числа
обращений ко внешней памяти.
Учитывая, что каждое обращение ко внешней памяти по времени на
порядок больше обращения к оперативной памяти, мы должны стремиться не только к оптимальным алгоритмам используемой внутренней сортировки, но и к минимальному числу обращений ко внешней
памяти. Нас будет интересовать, возможен ли алгоритм, оптимальный по трудоемкости операций в оперативной памяти и оптимальный
по трудоемкости обращения ко внешней памяти.
Оценка снизу трудоемкости внешней сортировки
Пусть m – число элементов сортируемого массива во внешней памяти. Тогда трудоемкость операций в оперативной памяти можно
оценить снизу как Θ(m log2 m), поскольку такова уже оценка внутренней сортировки.
Найдем теперь оценку снизу для числа обращений ко внешней памяти. Мы для минимизации обращений ко внешней памяти должны,
во-первых, разделить исходный массив на как можно большие части
и, во-вторых, использовать стратегию, дающую возможно меньшее
число вызовов и записей этих частей. Пусть n – максимальный размер части для этого. Он определяется исходя из того, что в оперативной памяти должно размещаться 3n элементов: по n элементов для
87
каждой из сливаемых частей и n элементов для половины слитого
объединения этих частей.
Рассмотрим случай, когда число частей разбиения исходного массива определяется степенью 2, т. е. m = n · 2p . В этом случае оценка
снизу числа обращений к 2p частям может быть получена из оценки
трудоемкости сортировки 2p элементов и определяется как Θ(p · 2p ).
Оптимизация по трудоемкости внешней сортировки
Покажем теперь, как построить алгоритм внешней сортировки, трудоемкость которого по числу операций в оперативной памяти определяется полученной оценкой снизу Θ(m log2 m) и трудоемкость по
числу обращений ко внешней памяти определяется как Θ(p · 2p ).
1. Разделим исходный массив на 2p частей и каждую часть отсортируем в оперативной памяти. Для этого потребуется Θ(2p ·
n log2 n) операций в оперативной памяти и 2p вызовов из внешней памяти и столько же записей во внешнюю память.
2. Воспользуемся алгоритмом фон Неймана для слияния частей.
Сначала мы осуществляем попарное слияние рядом стоящих частей с нечетным и следующим четным номерами и для этого
используем исходный файл, каждая часть которого отсортирована, и вспомогательный файл, в котором после записи слитых
частей каждая часть с четным номером в паре имеет большие
значения ключей, чем в части с предыдущим нечетным номером
в паре. В этом случае для слияния частей, например, с номерами 1 и 2 мы вызовем их в оперативную память, где начнем
выполнять алгоритм слияния. После n операций сравнения мы
получим младшую часть слитого объединения частей и запишем ее во внешнюю память в часть с номером 1 вспомогательного файла. Затем мы продолжим слияние остатков частей с
номерами 1 и 2 в оперативной памяти и полученную старшую
часть слитого объединения частей запишем в часть 2 вспомогательного массива. На этом первом этапе слияния потребуется
Θ(2n2p−1 ) = Θ(n2p ) операций в оперативной памяти и 2p вызовов из внешней памяти и столько же записей во внешнюю
память.
88
3. На втором этапе слияния частей мы поменяем местами исходный и вспомогательный файл, после чего образуем четверки частей. Теперь сначала для слияния мы вызовем первую и третью часть четверки и после получения первой части сливаемого
объединения мы запишем ее в первую часть четверки для вспомогательного файла, а после завершения слияния первой и третьей части исходной четверки и получения части остатка (так
мы ее назовем) мы должны добавить к ней для слияния либо
вторую часть, либо четвертую часть исходной четверки. Но какую из них? Ранее в алгоритме слияния внутренней сортировки
мы определяли номер (второй или четвертый) ключа, с которым производили следующее сравнение исходя из того, ключ с
каким номером записывался во вспомогательный массив (если
первый, то брали второй, а если третий, то брали четвертый).
Теперь же это труднее определить, так как записываемые части
отличаются от считанных и содержат, как правило, ключи обеих считанных частей. Для разрешения этой трудности обратим
внимание на следующее обстоятельство: если последний ключ в
части остатка принадлежал первой из считанных частей четверки, то все ключи второй части исходной четверки будут иметь
значения большие, чем ключи части остатка, и, следовательно, нужно добавлять для дальнейшего слияния четвертую часть
исходной четверки; если же последний ключ в части остатка
принадлежал третьей части исходной четверки, то нужно для
дальнейшего слияния добавлять вторую часть исходной четверки. При слиянии части остатка с этой вызванной частью мы
сначала образуем новую вторую часть четверки и запишем ее
во вспомогательный файл, а затем после получения новой части
остатка от этого слияния вызовем последнюю часть для слияния. В результате на этом этапе потребуется Θ(4n2p−2 ) = Θ(n2p )
операций в оперативной памяти и 2p вызовов из внешней памяти
и столько же записей во внешнюю память.
4. Этот процесс продолжается, и на каждом этапе слияния частей
удваивается число частей, содержащих отсортированную информацию массива. На каждом из этапов требуется Θ(n2p ) опера89
ций в оперативной памяти и 2p вызовов из внешней памяти и
столько же записей во внешнюю память.
Так как на p-м этапе весь массив будет отсортирован (если части
описывались как отдельные файлы, они могут быть слиты в один
отсортированный файл), то общая трудоемкость операций в оперативной памяти оценивается как Θ(2p ·n log2 n+pn2p ) = Θ(n2p (log2 n+
p)) = Θ(m log2 m), а число обращений ко внешней памяти оценивается как Θ(p · 2p ), что соответствует полученным ранее нижним
оценкам трудоемкости. Таким образом, описанный алгоритм внешней сортировки является оптимальным по трудоемкости.
3.2.4
Упражнения
1. Разработайте процедуру метода пузырька для сортировки индексов массива ключей.
2. Разработайте процедуру сортировки подсчетом массива целочисленных ключей с повторяющимися значениями трудоемкости
Θ(n). От чего зависит необходимая для алгоритма память и как
можно оценить ее рост?
3. Разработайте процедуру обменной поразрядной сортировки. При
какой организации алгоритма трудоемкость процедуры будет расти как Θ(n3/2 )?
4. Разработайте процедуру сортировки естественным двухпутевым
слиянием.
5. Разработайте процедуру внешней распределяющей (поразрядной) сортировки, где ключами являются автомобильные номера
и оперативная память позволяет использовать 106 байт. Каково
минимальное число требующихся для этого проходов массива и
каково минимальное число обращений ко внешней памяти?
6. Разработайте процедуру внешней сортировки с использованием
внутренней сортировки алгоритмом фон Неймана. Каково минимальное число проходов массива и минимальное число обращений ко внешней памяти в условиях предыдущего упражнения?
90
3.3 Поисковые деревья
3.3.1
Бинарные деревья и поиск
Дерево, каждая вершина которого имеет не более двух сыновей,
называется бинарным деревом. Если с каждой вершиной бинарного
дерева связан ключ, а бинарное дерево организовано таким образом,
что для любой его вершины все вершины левой ветви, если она не
пустая, имеют ключи с меньшими значениями, а вершины правой
ветви, если она не пустая, имеют ключи с большими значениями,
то такое дерево называется бинарным поисковым деревом. Пример
такого дерева представлен на рис. 12.
Рис. 12
Поиск происходит следующим образом:
1. В качестве текущей вершины выбирается корень дерева.
2. Поисковый ключ сравнивается с ключом текущей вершины. Если они совпадают, то поиск закончен и он удачен (доступ к
отыскиваемой информации может быть обеспечен указателем
текущей вершины); иначе выполняется следующий шаг.
3. Если поисковый ключ меньше ключа текущей вершины, то текущей вершиной становится левый ее сын; иначе текущей вершиной становится правый ее сын.
91
4. Если текущая вершина не определена (нет соответственно левого или правого сына предыдущей текущей вершины), то поиск
завершен и он неудачен; иначе переходим к шагу 2.
Так, для примера на рис. 12 при поиске ключа 18 происходит сравнение с ключами 20, 15, 18 и поиск оказывается удачным. При поиске
ключа 23 происходит сравнение с ключами 20, 25, и так как вершина
с ключом 25 не имеет левого сына, то поиск завершается неудачно.
Заметим, что для вставки (добавления) в дерево вершины, ключ
которой отсутствует, можно воспользоваться тем же алгоритмом, но
с тем изменением, что вместо завершения при неудачном поиске происходит добавление новой вершины с поисковым ключом, которая
прикрепляется к предыдущей текущей вершине слева или справа в
зависимости от предыдущего сравнения ключа предыдущей текущей
вершины с поисковым ключом. Поэтому алгоритм в таком виде (найти, если есть, или присоединить, если нет) называется алгоритмом
поиска-вставки. В примере вершина с ключом 23 становится левым
сыном вершины с ключом 25 (см. рис. 13).
Рис. 13
При помощи алгоритма поиска-вставки бинарное дерево можно
формировать. Если последовательность добавляемых ключей случайная, то такое дерево назовем случайным. Трудоемкость (максимальная) поиска определяется длиной самой длинной ветви, идущей из
корня. Так, в примере на рис. 12 она равна 3. Более неудачным для
92
данного примера явилось бы дерево, которое бы образовалось, если
бы ключи добавлялись в порядке возрастания или убывания. Поэтому трудоемкость поиска для случайного дерева оценивается как
Θ(n).
Если же дерево таково, что любой его уровень заполнен вершинами и самая длинная ветвь имеет длину p, то число вершин дерева
n = 1 + 2 + · · · + 2p = 2p+1 − 1. Поэтому для него p = log2 ( n+1
2 ), и,
следовательно, трудоемкость поиска оценивается как Θ(log2 n). Таким образом, максимальная трудоемкость поиска зависит от вида
дерева, и для случайного дерева мы должны взять оценку Θ(n). Но
образование дерева, вырождающегося в цепь, имеет небольшую вероятность, и более верным было бы использовать среднюю трудоемкость. Использование методов теории вероятностей для равномерного распределения случайных деревьев (случайных деревьев столько,
сколько перестановок ключей, т. е. n!, и все деревья равновероятны)
дает оценку средней трудоемкости в числе сравнений с поисковым
ключом как примерно 1, 39 log2 n. Поэтому построение случайных
бинарных деревьев с целью использования для поиска имеет практическое применение.
Заметим также, что использование построения случайного дерева
дает еще один алгоритм сортировки массива. Надо после построения
дерева обойти его вершины в определенном порядке: слева направо и снизу вверх. Средняя трудоемкость при этом оценивается как
Θ(n log n).
Организация поиска информации с помощью бинарных поисковых
деревьев имеет преимущества перед организацией поиска с помощью
сортировки массивов. Средняя трудоемкость поиска в обоих случаях
оценивается как Θ(log n), но добавление ключа в отсортированный
массив требует порядка Θ(n) операций, а в дерево – Θ(log n) операций. При удалении вершины бинарного поискового дерева можно
применить следующий алгоритм:
– если удаляемая вершина является листом дерева, то она просто
удаляется;
– если она не лист, но имеет лишь одну ветвь, исходящую из нее,
то первая вершина этой ветви заменяет удаляемую;
93
– и если она имеет две ветви, исходящие из нее, то первая вершина одной из ветвей заменяет ее, а для первой вершины другой
ветви алгоритмом поиска-вставки ищется место включения, и она
присоединяется вместе со всей ветвью.
Средняя трудоемкость удаления вершины оценивается, как и средняя трудоемкость вставки, как Θ(log n). В случае использования массива средняя трудоемкость удаления вершины оценивается как Θ(n).
Для того чтобы максимальная трудоемкость поиска была минимальной, можно организовать оптимальное дерево, у которого все
висячие вершины либо находятся на нижнем уровне, либо на предыдущем. Для этого
1) в отсортированном массиве ключей находится медиана (индекс,
слева и справа от которого число индексов отличается не более чем
на 1) и ключ этого элемента помещается в корень;
2) затем находятся медианы для отрезков массива слева и справа
и ключи этих элементов помещаются в сыновья корня дерева;
3) процесс этот продолжается, пока для каждого элемента массива не будет определена вершина дерева.
Поиск в таком дереве имеет не более чем log2 n сравнения ключей. Но при добавлении нового ключа свойство оптимальности дерева (висячие вершины только на двух уровнях) пропадает и требуется много операций, чтобы ее восстановить (порядка Θ(n)). Поэтому
ставится задача найти такое сбалансированное дерево, чтобы трудоемкость поиска осталась Θ(log2 n), но перестройка дерева при добавлении нового ключа происходила бы быстрее. В следующем разделе
рассматриваются такие деревья.
3.3.2
Сбалансированные АВЛ-деревья
Задача построения "хорошего" поискового дерева должна отвечать
некоторым условиям:
1) поиск за Θ(log n) операций;
2) поиск k-го элемента за Θ(log n) операций;
3) вставка за Θ(log n) операций;
4) удаление за Θ(log n) операций.
94
Для массива выполняются только условия 1 и 2, а вставка и удаление займут Θ(n) операций.
Стремление получить поисковое бинарное дерево близким к оптимальному привело двух русских математиков – Г.М. Адельсон-Вельского и Е.М. Ландиса – к построению подравненных деревьев или
сбалансированных АВЛ-деревьев, как это принято теперь называть.
Они определяются следующим свойством:
Для каждой вершины АВЛ-дерева ее ветви отличаются
по длине не более чем на 1.
На рис. 13 приведен пример АВЛ-дерева. Около каждой вершины показана характеристика, значение которой равно разности длин
правой и левой ветвей вершины. Естественно, что для висячих вершин (листов дерева) эта характеристика равна 0.
Пусть p – длина самой длинной ветви АВЛ-дерева. Какое минимальное число n(p) вершин может иметь такое дерево? Это число
вершин состоит из корня и вершин ветвей корня. Длина, по крайней
мере, одной из ветвей на 1 меньше, и, чтобы число вершин было
минимальным, необходимо, чтобы эта ветвь содержала минимальное
число вершин, а другая ветвь была еще короче и также содержала
минимальное число вершин. Отсюда следует рекурентное соотношение:
n(p) = n(p − 1) + n(p − 2) + 1.
Так как n(0) = 1 (только корень) и n(1) = 2 (одна дуга), то, пользуясь этим отношением, вычисляем n(2) = 4, n(3) = 7 (пример на
рис. 13) и n(4) = 12. Надо оценить n(p), чтобы оценить трудоемкость
поиска p для АВЛ-дерева.
√
2
Уравнение x = x + 1 имеет корень α = 5+1
2 , который обладает
m
m−1
m−2
свойством α = α
+α
, т. е. степени корня ведут себя как
числа Фибоначчи. Покажем, что n(p) > αp+1 при p ≥ 3. Воспользуемся методом математической индукции. Для основания индукции
покажем, что α4 < n(3), α5 < n(4). Действительно,
³ 3 + √5 ´ 2 7 + 3 √5
α4 = (α2 )2 =
=
< 7 = n(3),
2
2
√ √
√
(7
+
3
11
+
5
5)(
5
+
1)
5 11 + 12
α5 = α4 · α =
=
<
< 12 = n(4).
4
2
2
95
Сделаем шаг индукции: предположим, что n(l) > αl+1 имеет место
для всех 3 ≤ l < p, и докажем, что тогда имеет место n(p) > αp+1 .
Из равенства αp+1 = αp + αp−1 следует неравенство:
αp+1 < αp + αp−1 + 1 < n(p − 1) + n(p − 2) + 1 = n(p),
что и требовалось показать.
Теперь получаем оценку максимального числа операций сравнения ключей при поиске:
T (n) = p + 1 < logα n =
log2 n
≈ 1, 44 · log2 n.
log2 α
Заметим, что среднее число операций сравнения ключей при поиске
≈ 1, 04 · log2 n. Худший поиск в АВЛ-дереве почти такой же, как
средний поиск в несбалансированном случайном дереве, а средний
поиск практически такой же, как худший в оптимальном дереве. Таким образом, максимальная трудоемкость и средняя трудоемкость
при поиске в АВЛ-дереве оцениваются как Θ(log n).
Перейдем теперь к оценке трудоемкости вставки. В тех случаях,
когда вставляемый ключ увеличивает более короткую ветвь, условие баланса АВЛ-дерева не нарушается. В случаях, когда имеет
место нарушение условия баланса, вставляемая вершина удлиняет
более длинную ветвь. Покажем, как преобразовать АВЛ-дерево в
этом случае, чтобы добиться восстановления условия баланса. Для
этого рассмотрим только те случаи, когда вставка производится в
правую ветвь некоторой вершины АВЛ-дерева (случаи, когда вставка происходит в левую ветвь, симметричны, и по аналогии можно
указать необходимые преобразования АВЛ-дерева для восстановления условий баланса). Пусть A – вершина с самым низким уровнем,
для которой условие баланса нарушается из-за того, что ее правая
более длинная ветвь становится еще на 1 длиннее, а B – первая
вершина этой ветви, для которой A является отцом. Рассмотрим два
случая:
1) вставка происходит в правую ветвь вершины B;
2) вставка происходит в левую ветвь вершины B.
Первый случай изображен на рис. 14.
96
Рис. 14
Здесь α – левая ветвь вершины A, которая имеет длину h, β – левая
ветвь вершины B, которая имеет такую же длину h, и γ – правая
ветвь вершины B, которая удлинилась до h + 1, что не нарушило
условие баланса для вершины B, но нарушило условие баланса для
вершины A.
Преобразование АВЛ-дерева в первом случае проведем следующим образом (см. рис. 15):
Рис. 15
1) вершину B подвесим на место вершины A к ее отцу (так как
обе вершины находятся в одной ветви отца вершины A, то это возможно сделать);
97
2) вершину A подвесим в левую ветвь вершины B на место прежней левой ветви β (это возможно сделать, так как ключ A меньше
ключа B);
3) ветвь β подвесим в качестве правой ветви вершины A (это возможно сделать, так как правая ветвь A освободилась и все ключи
ветви β больше ключа вершины A).
В результате для вершины A выполнены условия баланса (длины
ветвей α и β одинаковы), для вершины B выполнены условия баланса – длины ее ветвей также одинаковы, и, наконец, длина ветви,
которая была подвешена к отцу вершины A, не изменилась. Все
преобразование потребовало изменения трех указателей: для отца
вершины A, указателя левого сына вершины B и указателя правого
сына вершины A.
Второй случай изображен на рис. 16.
Рис. 16
Здесь α – левая ветвь вершины A, которая имеет длину h, δ – правая
ветвь вершины B, X – первая вершина левой ветви вершины B, в
которую была вставлена новая вершина, β и γ – левая и правая ветви
вершины X такие, что до вставки они были одинаковой длины, а
98
после вставки одна из них стала длиннее, что привело к нарушению
условия баланса для вершины A.
Преобразование АВЛ-дерева во втором случае проведем следующим образом (см. рис. 17):
Рис. 17
1) вершину X подвесим на место вершины A к отцу последней
(это можно сделать, так как A и X находятся в одной ветви для
отца вершины A);
2) вершину A подвесим в качестве левого сына вершины X, а
вершину B подвесим в качестве правого сына вершины X (это возможно сделать, так как ключ A меньше ключа X, а ключ B больше
ключа X);
3) ветвь β подвесим справа для вершины A (это возможно сделать, так как значения ключей этой ветви больше, чем значение
ключа A, а длина ветви такая же, как и длина левой ветви для A
или на 1 меньше);
4) ветвь γ подвесим слева для вершины B (это возможно сделать,
так как значения ключей этой ветви меньше, чем значения ключа B,
а длина ветви такая же, как и длина правой ветви для B или на 1
меньше).
В результате для вершин A и B выполнены условия баланса (длины ветвей одинаковы или одна больше другой на 1), для вершины
X выполнены условия баланса (длины ветвей одинаковы), и, наконец, длина ветви, которая была подвешена к отцу вершины A, не
изменилась. Все преобразование потребовало изменения пяти ука99
зателей: для отца вершины A, указателя левого сына вершины B,
указателя правого сына вершины A и обоих указателей вершины X.
Таким образом, во всех случаях вставки вершины требуется изменение не более 5 указателей и потому трудоемкость вставки оценивается как Θ(1).
При удалении вершины АВЛ-дерева не удается при помощи небольшого числа операций (как при вставке) перестроить дерево, чтобы
сохранить условие баланса. Однако можно показать, что трудоемкость удаления сохранением сбалансированности дерева оценивается
как Θ(log n).
3.3.3
Сбалансированные (3-2)-деревья
Если снять требование бинарности поискового дерева (допустить,
чтобы число сыновей любой вершины могло быть и более 2), то
можно решить проблему построения сбалансированного дерева иным
образом. Джон Хопкрофт предложил, чтобы каждая вершина поискового дерева имела 2 или 3 сыновей и все внешние узлы, содержащие информацию или указатели на нее, располагались бы на одном
самом нижнем уровне.
Пример такого дерева представлен на рис. 18. Здесь внешние узлы
обозначены квадратами, содержащими значение ключа информации,
а узлы поиска – овалами с одним или двумя значениями ключа. В
случае, когда узел поиска имеет 1 ключ, то при сравнении с поисковым ключом происходит переход к левому сыну, если значение
поискового ключа меньше значения ключа узла, или к правому сыну
в противном случае. Если узел имеет 2 ключа, то они интерпретируются как диапазон значений поискового ключа. При этом, если
поисковый ключ меньше нижней границы диапазона, то происходит
переход к левому сыну; если же он больше верхней границы диапазона, то происходит переход к правому сыну узла; если, наконец,
он принадлежит диапазону, то происходит переход к среднему сыну. Так, в этом примере при поиске ключа 41 произойдет сравнение
этого значения с ключом 20, затем с диапазоном ключей 30:50 и,
наконец, с диапазоном ключей 35:45, после чего внешний узел, содержащий необходимую информацию, будет найден.
100
Рис. 18
Нетрудно видеть, что число сравнений поискового ключа с ключами узлов дерева равно числу уровней дерева, которое находится
в промежутке [log3 n, log2 n], где n – число внешних узлов дерева.
Поэтому трудоемкость поиска оценивается как Θ(log n).
Рис. 19
101
При вставке нового внешнего узла S алгоритмом поиска ищется
последний поисковый узел P , со значением ключей которого сравнивается значение s ключа вставляемого узла. В зависимости от числа
ключей узла P происходит преобразование этого узла и, может быть,
и других узлов. Поэтому рассмотрим 2 случая.
1. Если узел P имеет одно значение p ключа, то к этому ключу добавляется еще 1 ключ со значением u (образуется диапазон
ключей в узле), определяемым следующим образом. Пусть P1 и P2
– внешние узлы с ключами p1 и p2 , связанные с поисковым узлом
P (p1 < p ≤ p2 ). Тогда,
если s < p1 , то u = p1 и диапазон ключей узла p1 : p;
если p1 < s < p, то u = s и диапазон ключей узла s : p;
если p < s < p2 , то u = s и диапазон ключей узла p : s;
если p2 < s, то u = p2 и диапазон ключей узла p : p2 .
На рис. 19 показано добавление нового внешнего узла 29 для примера рис. 18. При этом поисковый узел 25 заменяется на новый узел
с диапазоном 25:27.
0
00
2. Если узел P имеет диапазон ключей p : p , то узел P заменяется на 2 одноключевых: U1 с ключом u1 и U2 с ключом u2 , значения
которых определяются следующим образом. Пусть p1 , p2 , p3 – ключи
0
00
внешних узлов, связанных с узлом P (p1 < p ≤ p2 ≤ p < p3 ). Тогда,
00
если s < p1 , то u1 = p1 и u2 = p ;
0
00
если p1 < s < p , то u1 = s и u2 = p ;
0
0
00
если p ≤ s < p2 , то u1 = p и u2 = p ;
0
00
0
00
если p ≤ p2 < s < p , то u1 = p и u2 = p ;
00
0
если p ≤ s < p3 , то u1 = p и u2 = p3 ;
0
если p3 < s, то u1 = p и u2 = s.
После этого для прежнего узла P поиском в исходном дереве
ищется узел F , являющийся его отцом, и если он одноключевой,
то преобразуется в двухключевой аналогично рассмотренному случаю 1, а если он двухключевой, то преобразуется разбиением на 2
узла аналогично случаю 2. В последнем случае процесс преобразования дерева распространяется вверх по дереву и либо прекратится
преобразованием некоторого одноключевого узла в двухключевой,
либо прекратится разбиением корня на 2 одноключевых узла и созданием нового корня для них (в этом случае число уровней дерева
102
возрастет). На рис. 19 показано добавление внешнего узла 37 для
примера на рис. 18, которое приводит к разбиению двухключевого
узла 35:45 на одноключевые узлы 35 и 45, которые, в свою очередь,
разбивают двухключевой узел 30:50 на 2 одноключевых узла 30 и
50 и производят замену одноключевого корня 20 на двухключевой
корень 20:49.
Трудоемкость вставки узла, таким образом, не превосходит по
числу этапов числа уровней дерева и потому оценивается как Θ(log n).
При удалении внешнего узла из дерева имеет место простой случай, когда отец удаляемого узла является двухключевым – в этом
случае он становится одноключевым. Если же отец удаляемого узла
одноключевой, то он преобразуется, объединяясь со своим соседним
братом в один узел, если тот одноключевой, или разбиваясь совместно на 2 новых узла, если тот двухключевой. Процесс этот может
продолжиться и дойти до корня. Нетрудно видеть, что трудоемкость
удаления узла также оценивается как Θ(log n).
3.3.4
Внешний поиск по дереву и B-деревья
При внешнем поиске деревья удобны: можно сначала вызвать в оперативную память часть дерева, содержащую корень, а затем в зависимости от результатов поиска в этой корневой части вызвать только
необходимую часть для продолжения. Однако при организации бинарных поисковых деревьев во внешней памяти количество обращений ко внешней памяти растет как Θ(log2 N ), где N – число частей,
на которое разбито поисковое дерево во внешней памяти для вызовов в оперативную память. Поскольку такое количество обращений
ко внешней памяти может быть довольно велико, то ставится проблема такой организации деревьев во внешней памяти, которая бы
уменьшила это количество обращений ко внешней памяти.
Такой организации отвечают сильно ветвящиеся деревья, т. е. деревья, у которых каждая вершина имеет много сыновей. В этом случае дерево имеет намного большую ширину, чем бинарное дерево,
но намного меньшую высоту, что способствует уменьшению вызовов
частей при поиске. Обычно при такой организации корневая часть
103
дерева находится в оперативной памяти, а информация для каждого сына организуется в страничной памяти, и при необходимости
нужная страница переписывается в оперативную память.
В качестве сильно ветвящихся деревьев используют так называемые B-деревья порядка m, которые определяются следующим образом:
1. Каждый узел имеет не более m сыновей.
2. Каждый узел, кроме корня, имеет не менее m/2 сыновей.
3. Корень, если он не лист, имеет не менее 2 сыновей.
4. Листья находятся на одном уровне дерева.
5. Нелистовой узел с k сыновьями имеет k − 1 ключ.
При поиске ключи узла по очереди сравниваются с поисковым ключом, и, когда определяется диапазон, которому принадлежит поисковый ключ, происходит переход на следующий узел дерева.
B-дерево порядка 3 – это (3-2)-дерево. Для поиска во внешней памяти используют m = 7 и больше. Алгоритм преобразования B-дерева похож на алгоритм работы с (3-2)-деревом. Процесс
оптимизации B-деревьев ведет к построению B ∗ -деревьев, которые
отличаются другим минимумом и максимумом сыновей узла:
от (2m − 1)/3
3.3.5
до 2[(2m − 2)/3] + 1.
Упражнения
1. Разработайте процедуру поиска-вставки для бинарного дерева.
2. Разработайте алгоритм и процедуру построения оптимального
дерева для заданного упорядоченного массива ключей.
3. Разработайте алгоритм и процедуру обхода бинарного дерева в
порядке его ключей.
4. Разработайте алгоритм и процедуру определения ключа по номеру его позиции.
104
5. Разработайте алгоритм и процедуру удаления ключа в бинарном
дереве.
6. Разработайте для АВЛ-дерева алгоритм и процедуру поискавставки.
7. Разработайте для АВЛ-дерева алгоритм и процедуру удаления
ключа.
8. Разработайте для (3-2)-дерева алгоритм и процедуру поискавставки.
9. Разработайте для (3-2)-дерева алгоритм и процедуру удаления
ключа.
10. Разработайте алгоритм и процедуру определения ключа по номеру его позиции для (3-2)-дерева.
11. Разработайте для B-дерева алгоритм и процедуру внешнего поиска порядка 7.
3.4
3.4.1
Функции расстановки и хеширование
Функции расстановки
В тех случаях, когда нам известна информация о значениях ключей, эту информацию можно использовать для ускорения поиска.
Рассмотрим сначала тривиальный случай, когда n целочисленных
ключей имеют значения от 0 до n − 1. В этом случае можно организовать массив P указателей информации, который для ключа со
значением i содержит в качестве элемента Pi указатель на информацию, соответствующую ключу i. Таким образом, для поиска нужно
взять адрес (указатель).
В более сложном случае для взаимно однозначного отображения множества значений ключей на элементы массива может существовать функция такого отображения. Такая функция называется
функцией расстановки. Например, для последовательности значений ключей
(10, 12, 14, . . . , 2n + 10)
105
функция расстановки имеет вид: f (k) = (k − 10)/2. В этих случаях
вместо поиска ключа нужно вычислить значение функции расстановки. Тем самым трудоемкость поиска ключа не зависит от его
значения и, следовательно, оценивается как Θ(1).
Такой функции расстановки может не существовать, но если
предъявить к ней требования, чтобы она взаимно однозначно отображала множество ключей на элементы некоторого подмножества
элементов массива, то такая функция (мы также назовем ее функцией расстановки), возможно, существует. Она существует определенно, если массив P имеет m = kmax − kmin элементов, где kmax и
kmin – максимальное и минимальное значения ключей. Но если m
намного больше n, то в силу ограниченности такого ресурса памяти может не быть. Отношение α = n/m называют коэффициентом
заполнения массива. Естественно отыскивать функцию, которая бы
этот коэффициент сделала возможно более близким к 1.
3.4.2
Хеширование
В тех случаях, когда информация о всех значениях ключа неизвестна, а мы знаем только о диапазоне значений ключей, то можно
попытаться найти такую функцию, которая бы хорошо распределяла
значения ключей по элементам массива, на который она отображает
ключи. В этом случае существует уже опасность, что 2 ключа будут
иметь одинаковое значение функции, и такая ситуация называется
коллизией. Особенно это имеет место для близких значений ключа. Поэтому функция отображения должна "хорошо размешивать"
ключи. Отсюда ее название хеш-функция от английского глагола to
hash – размешивать.
В случае коллизии организуется список значений ключа, имеющих одно значение хеш-функции. Таким образом, использование
хеш-функции предполагает вычисление ее значения для ключа и
обращение к списку, начинающемуся с вычисленного адреса. Для
хорошей хеш-функции большинство списков состоят из 1 элемента
и лишь некоторые из них имеют большее значение элементов. Распределение значений хеш-функции называется хешированием. Если
каждый ключ может попасть с одинаковой вероятностью в любой
106
элемент массива независимо от попадания других элементов, то такое хеширование называется равномерным. Естественно стремиться
к выбору хеш-функции, которая дает хеширование, близкое к равномерному по следующим причинам. Оказывается, в этом случае
верны следующие теоретико-вероятностные результаты относительно коэффициента заполнения α (он может быть и больше 1):
1. При поиске отсутствующего ключа будет просмотрено в среднем α элементов списка.
2. Трудоемкость успешного поиска оценивается как Θ(1 + α).
Для выбора хорошей хеш-функции чаще всего на практике либо
используют деление значения ключа на простое число M , либо метод
умножения размерности массива на дробное число, зависящее от
ключа.
В первом случае
h(k) = k mod M,
и очень важен выбор простого числа M . Если p – "основание системы счисления" для множества значений ключа, то pl mod M не
должно иметь маленькое значение при небольших значениях l.
Во втором случае выбирают
некоторое число a ∈ (0, 1) (Д. Кнут
√
рекомендует брать a = ( 5 − 1)/2) и полагают
h(k) = [m((k · a) mod 1)],
где (k · a) mod 1 – дробная часть k · a. Такая хеш-функция мало
зависит от выбора m.
Если n ≥ m, то для любой хеш-функции можно подобрать такую последовательность ключей, чтобы значения хеш-функции были
одинаковы. Чтобы избежать такой неприятности (сделать ее маловероятной) применяют случайный выбор хеш-функции из некоторого
набора хеш-функций. Такой подход называется универсальным хешированием.
3.4.3
Хеширование с открытой адресацией
В отличие от рассмотренного вида хеширования, при котором для
разрешения коллизий организуются отдельные от массива списки, в
данном виде хеширования списки как таковые не организуются, а
107
элементы каждого списка помещаются в тот же массив на свободные места, следующие за первым элементом списка. Такой подход
возможен только при n ≤ m.
Но при таком подходе трудоемкость поиска оценивается как Θ(n).
Поэтому, чтобы ускорить поиск, используют функцию двойного хеширования
h(k, i) = (h1 (k) + i · h2 (k)) mod m,
где k – значение ключа, а h1 и h2 – функции хеширования, которые для различных значений i ∈ 0, m − 1 обеспечивают различные
значения h(k, i) ∈ 0, m − 1.
Формирование записей в массив для ключа k идет следующим
образом: поочередно делаются попытки с i = 0, 1, . . . , m − 1 до тех
пор, пока элемент массива с индексом h(k, i) не окажется свободным,
и тогда в этот элемент записывается значение ключа и указатель на
информацию.
При поиске попытки делаются в том же порядке до тех пор, пока
значение ключа не будет найдено или не будет встречено свободное
значение, что означает неудачный поиск – ключа нет.
Выбор функций h1 , h2 для образования функции двойного хеширования может быть сделан различным способом. Один из наиболее
простых способов следующий:
1) число m элементов массива выбирается простым числом m ≥ n;
2) выбираем предыдущее для m простое число M1 < m и полагаем
h1 (k) = k mod M1 ;
3) выбираем предыдущее для M1 простое число M2 < M1 и полагаем h2 (k) = 1 + k mod M2 .
В этом случае по производительности двойное хеширование приближается к равномерному хешированию:
– при добавлении нового элемента в массив с коэффициентом
заполнения α < 1 число попыток не превосходит 1/(1 − α);
– при поиске случайного ключа среднее число попыток не превос1
.
ходит α1 ln 1−α
Рассмотрим пример с m=17. Так как
h(k, i) = (k mod 13 + i(1 + k mod 11)) mod 17,
108
то при вводе следующей случайной последовательности ключей
79, 0, 50, 72, 69, 96, 14, 27, 1, 5, 16
получим следующее заполнение массива, где значением n отмечается свободный элемент (см. рис. 20):
0 79 n 1 69 96 5 72 n 14 n 50 n 27 n 16 n
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Рис. 20
Отметим теперь, что при удалении ключа из массива нельзя сделать этот элемент свободным, так как это может помешать поиску элементов, размещение которых по цепочке с одинаковым шагом h2 (k) проходило через него. В рассмотренном примере удаление
ключа 96 с отметкой свободного элемента приведет к невозможности найти ключи 14 и 5. Поэтому элемент надо не освобождать, а
помечать как удаленный, что позволяет изменить алгоритм поиска и
вставки. Этот элемент может быть вновь занят некоторым ключом,
если потребуется. Так, в примере пометка элемента 96 символом
удаления позволит находить элементы 14 и 5, а затем удаленный
элемент может быть заменен ключом 44.
3.4.4
Упражнения
1. Разработайте хеш-функцию списков указателей информации
Bool HashL (int m, List ∗ X[], int K, char ∗ p),
которая осуществляет поиск информации с ключом K в массиве
X размерности m указателей списка элементов структуры
struc inf {int k; char ∗ pInf },
содержащей поле ключа k и указатель на информацию pInf , и
в случае успеха возвращает значение указателя на информацию
p, а в случае неуспешного поиска заносит в соответствующий
список массива ключ k и указатель информации p.
109
2. Разработайте хеш-функцию с открытой адресацией
Bool HashA (int m, List ∗ X[], int K, char ∗ p),
которая определяется условиями предыдущего упражнения.
3.5 Литература
1. Ахо А., Хопкрофт Дж., Ульман Дж. Построение и анализ вычислительных алгоритмов. М., 1979.
2. Вирт Н. Алгоритмы и структуры данных. М., 1989.
3. Кнут Д., Искусство программирования для ЭВМ. Т. 1.: Основные алгоритмы. М., 1977.
4. Кормен Т., Лейзерсон Л., Ривест Р. Алгоритмы: построение и
анализ. М., 2001.
5. Лавров С.С., Гончарова Л.И. Автоматическая обработка данных:
хранение информации в памяти ЭВМ. М., 1971. С.70 – 141.
110
Глава 4
Класс NP трудоемкости задач
4.1 Труднорешаемые задачи
В тех случаях, когда задача допускает существование алгоритмов,
не всякий алгоритм ее решения может быть эффективным. Приведенная в разделе 2.2.2 таблица поясняет неэффективность экспоненциальных алгоритмов. Практически такими алгоритмами трудно
воспользоваться для решения многих практических задач, так как
время их выполнения может стать «космическим».
Рассмотрим в качестве примера известную задачу коммивояжера,
которая заключается в определении кратчайшего маршрута объезда
n городов с возвращением в исходный. Для этой задачи неизвестны алгоритмы, трудоемкость которых существенно отличалась бы
от трудоемкости перебора всех вариантов T (n) = Θ(n!). Так, для
нахождения кратчайшего маршрута объезда всех 50 столиц штатов
США имеется 49! вариантов. Можно оценить
√
¡это
¢ количество, восn n
пользовавшись формулой Стирлинга n! ≈ 2πn π :
√
49! ≈ 2π · 49 · (49/π)49 ≈ 1650 = 2200 = (210 )20 ≈ 1060 .
Быстродействие современного компьютера ограничено пределом скорости взаимодействий, который рассчитывается исходя из физического ограничения скорости света 300 000 км/сек и ограничения
минимального расстояния между физическими носителями информации, определяемого размером наименьшего атома (водорода) в 1Å.
Исходя из этого теоретический предел быстродействия современного
компьютера ограничен 1018 оп/сек. Поэтому время выполнения такого алгоритма оценивается снизу как 1042 сек ≈ 3 · 1034 лет, что
111
существенно больше возраста не только Солнечной системы, но и
нашей Галактики. Таким путем эта задача не может быть решена.
Задачи, для которых не удается построить полиномиальные алгоритмы, являются труднорешаемыми. Их решение возможно лишь
при сравнительно небольших значениях параметра. Так, в задаче
коммивояжера «комбинаторный взрыв» наступает уже при n = 30.
Другим примером труднорешаемой задачи является задача об отыскании максимальной клики (полного подграфа) произвольного графа
G(X, U ).
Труднорешаемыми являются и более простые задачи, где необходимо установить существование некоторого свойства – такие задачи мы будем называть задачами распознавания. Так, для задачи
коммивояжера соответствующая задача распознавания коммивояжера формулируется следующим образом: задано конечное множество
городов и расстояний между ними, а также граница B; существует ли маршрут объезда всех городов, длина которого не
превосходит B? Задаче о максимальной клике соответствует следующая задача распознавания клики: задан граф G(X, U ) и число
k ≤ |X|; существует ли клика графа с числом вершин k?
Анализ примеров труднорешаемых задач распознавания показывает, что для каждой задачи существует множество вариантов и
при положительном ответе надо угадать удачный вариант (выбрать
недетерминированно), а затем проверить выполнение свойства за полиномиальное число шагов. Поэтому класс таких задач называется
классом задач с недетерминированно-полиномиальными алгоритмами, или классом NP, в отличие от класса задач распознавания,
для которых существуют полиномиальные алгоритмы и который мы
назовем классом P. Смысл введения класса NP задач состоит в выявлении таких задач этого класса (мы назовем их NP-полными),
для которых существование полиномиального алгоритма для решения одной из них означает, что для любой такой задачи также можно построить полиномиальный алгоритм ее решения. Поэтому в тех
случаях, когда для некоторой задачи не удается построить полиномиальный алгоритм, пробуют доказать, что она является NP-полной.
Если это удается, то попытки построить полиномиальный алгоритм
ее решения следует прекратить, поскольку существование такого эф112
фективного алгоритма означает, что любая другая задача из класса
NP также может быть решена эффективным полиномиальным алгоритмом. Последнее заключение является очень сомнительным ввиду
огромного труда, затраченного многими известными математиками
на неудачные попытки построения полиномиальных алгоритмов решения NP-полных задач. В этом смысл теории NP-полных задач,
введенных в трудах С. Кука, Р. Карпа и других.
Перейдем к строгому определению класса NP.
4.2 Класс NP задач и недетерминированная машина
Тьюринга
Определим сначала класс NP неформально, а затем перейдем к его
формализации. Мы будем рассматривать некоторую произвольную
задачу Π распознавания свойства Q. Обозначим через DΠ область
определения задачи Π, а через I ∈ DΠ – ее индивидуальную задачу,
определяемую конкретными данными. Определим YΠ как множество
индивидуальных задач, имеющих ответ "да". Неформальное определение класса NP можно дать с помощью недетерминированного
алгоритма, состоящего из двух стадий:
1) угадывание;
2) проверка.
На первой стадии по индивидуальной задаче I ∈ DΠ происходит
«угадывание» некоторой структуры S (варианта), а затем I и S подаются совместно на вторую стадию – проверки, которая выполняется детерминированным способом и заканчивается либо ответом "да",
либо "нет", либо проверка неограниченно продолжается. Последние
две возможности мы не будем различать.
Недетерминированный алгоритм «решает» задачу распознавания
Π, если для любой индивидуальной задачи I ∈ DΠ выполнены условия:
1. Если I ∈ YΠ , то существует структура S, угадывание которой
для входных данных I приводит к тому, что стадия проверки со
входом (I, S) заканчивается ответом "да".
2. Если I 6∈ YΠ , то не существует такой структуры S, угадывание
113
которой для входных данных I приводит к тому, что стадия проверки со входом (I, S) заканчивается ответом "да".
Недетерминированный алгоритм, решающий задачу Π, работает в
течение «полиномиального времени», если найдется догадка S, которая приводит на стадии детерминированной проверки со входом
(I, S) к ответу "да" за время p(l(I)), где p – полином, а l(I) – объем входных данных задачи I. Такой алгоритм мы будем называть
недетерминированно-полиномиальным.
Класс NP – это класс всех задач распознавания, которые могут
быть решены недетерминированно-полиномиальными алгоритмами.
Отметим отличие от детерминированных алгоритмов. Если рассматривается задача класса P, то ее частная задача A: «Дано I;
верно ли, что выполняется свойство Q?» – может быть решена
детерминированным алгоритмом за полиномиальное время. Но также за полиномиальное время детерминированным алгоритмом может
быть решена и задача B: «Дано I; верно ли, что не выполняется
свойство Q?». Для класса NP такой симметрии нет: для задачи
A достаточен пример (вариант), и решение примера также идет за
полиномиальное время, а для задачи B нужно перебирать все варианты, чтобы удостовериться в отсутствии свойства Q, а это требует
уже экспоненциального времени для детерминированного алгоритма.
Для исследования класса NP нужно дать точное определение
недетерминированного алгоритма, что мы сделаем с помощью описания недетерминированной машины Тьюринга (НДМТ). Ее модель
(см. рис. 21) похожа на модель детерминированной МТ, но имеются
следующие отличия:
1. НДМТ имеет угадывающий модуль, головка которого только записывает информацию на ленту (слово-догадку). Угадывающий
модуль в "активном" состоянии на каждом шаге своей работы
совершенно произвольно либо записывает в обозреваемую ячейку любой символ внешнего алфавита Γ и сдвигается на одну
ячейку влево, либо останавливается и переходит в "пассивное"
состояние.
114
2. Внешний алфавит Γ, как и прежде, состоит из входного алфаS
вита Σ и пустого символа Λ (Γ = Σ {Λ}).
3. Устройство управления имеет 2 режима работы: "пассивный",
когда угадывающий модуль находится в "активном" состоянии,
и "активный", когда угадывающий модуль переходит в "пассивное" состояние. Множество состояний устройства управления
включает начальное состояние q1 , 2 заключительных состояния
q0Y (вычисления закончились ответом "да") и q0N (вычисления
закончились ответом "нет") и другие состояния, определяющие
алгоритм.
слово-догадка
z
}|
{
...
-2 -1 0
4
УгМ
входное слово
z
}|
{
...
...
1 2
|x|
4
УУ
Схема НДМТ: УгМ – угадывающий модуль; УУ – устройство управления
Рис. 21
Работа НДМТ в отличие от работы детерминированной МТ имеет
2 различные стадии, и ее можно описать следующим образом:
1. В начальный момент входное слово x записывается в ячейки
с номерами 1, 2, . . . , |x| и головка устройства управления смотрит на ячейку с номером 1, а головка угадывающего модуля
– на ячейку с номером -1 (см. рис. 21). При этом устройство
управления "пассивно" (в "пассивном" режиме), а угадывающий
модуль находится в активном состоянии.
2. Стадия угадывания: угадывающий модуль начинает работать,
формируя слово-догадку, которым может быть произвольное слово из Γ∗ (любое слово внешнего алфавита или пустое слово). Работа угадывающего модуля может продолжаться неограниченно
– это также соответствует ответу "нет" недетерминированного
алгоритма. Как только угадывающий модуль остановится и перейдет в "пассивное" состояние (слово-догадка сформировано),
устройство управления переходит в активный режим.
115
3. Стадия проверки: начинается с перехода устройства управления
в "активный" режим (в этот момент оно находится в состоянии
q1 ) и осуществляется функцией перехода δ, которая определяет записываемый символ в обозреваемую ячейку, изменение состояния и движение автомата вдоль ленты, т. е. работа стадии
проверки осуществляется так же, как и для детерминированной
МТ (угадывающий модуль и его головка в этом вычислении не
участвуют). Во время стадии проверки устройство управления
может обозревать любые ячейки, в том числе ячейки входного
слова и ячейки слова-догадки, сформированного на предыдущей
стадии.
4. Вычисления заканчиваются тогда, когда устройство управления
перейдет в одно из двух заключительных состояний. Вычисление называется принимающим, если оно заканчивается состоянием q0Y . Во всех других случаях вычисление называется непринимающим (заканчивается состоянием q0N или не заканчивается
совсем).
Говорят, что НДМТ принимает вход x, если, по крайней мере,
одно из ее вычислений является принимающим.
Следующим важным фактором в строгом определении недетерминированного алгоритма является трудоемкость НДМТ. Сначала
определим время принятия слова x НДМТ M как минимальное число шагов на обеих стадиях угадывания и проверки до момента достижения заключительного состояния q0Y . При этом минимум числа
шагов берется по всем принимающим вычислениям для x:
tM (x) =
min
принимающие вычисления
число шагов угадывания и проверки.
Трудоемкость НДМТ M определяется как максимальное время принятия для слов длины n:
TM (n) = max{1, max tM (x)}.
|x|=n
Если нет принимающих вычислений для слов длины n, то трудоемкость равна 1.
116
Будем говорить, что НДМТ M имеет полиномиальную трудоемкость, если найдется полином p(n) такой, что для всех n ≥ 1 имеет место неравенство TM (n) ≤ p(n). Нетрудно видеть, что между
недетерминированно-полиномиальными алгоритмами решения задач
распознавания и недетерминированными машинами Тьюринга устанавливается взаимосвязь: по каждому недетерминированно-полиномиальному алгоритму задачи распознавания Π можно определить
такую НДМТ MΠ полиномиальной трудоемкости, что каждой индивидуальной задаче I ∈ YΠ ставится в соответствие входное слово
xI , которое принимается НДМТ MΠ , и, наоборот, НДМТ полиномиальной трудоемкости соответствует задача распознавания свойства,
которое однозначно определяется для тех ее индивидуальных задач,
для которых соответствующее входное слово НДМТ является принимающим.
Так, для рассмотренного примера задачи распознавания коммивояжера структуру входного слова НДМТ, определяющего индивидуальную задачу I, можно задать в виде:
B ∗ n ∗ L12 ∗ L13 ∗ · · · ∗ L1n ∗ L23 ∗ · · · ∗ Ln−1,n ,
где B – символы ячеек, задающих ограничение задачи, n – символы
ячеек, задающих число городов, Lij (i ∈ 1, n − 1, j ∈ i + 1, n) –
символы ячеек, определяющих расстояние между i-м и j-м городами.
"Правильное" слово-догадка должно иметь структуру в виде:
i2 ∗ i3 ∗ · · · ∗ in
(ik ∈ 2, n при k ∈ 2, n, ik 6= im при k 6= m),
которая определяет маршрут <1, i2 , i3 , . . . , in , 1>, соответствующий структуре S. В ячейке с номером 0 находится пустой символ
Λ, разделяющий слово-догадку и входное слово. Алгоритм стадии
проверки НДМТ должен проверить структуру "правильного" словадогадки и в случае нарушения указанной структуры (НДМТ на стадии угадывания может сформировать любое слово-догадку) прекращает работу с выдачей результата "нет". В случае же "правильной"
структуры НДМТ вычисляет длину маршрута, сравнивает ее со значением B и в зависимости от этого выдает результат "да" или "нет".
Таким образом, мы формально определяем класс NP как класс
недетерминированных машин Тьюринга полиномиальной трудоемкости.
117
Рассмотрим теперь, как соотносятся между собой класс P задач с
полиномиальными алгоритмами и класс NP задач с недетерминированно-полиномиальными алгоритмами.
4.3 Взаимоотношение между классами P и NP
Во-первых, отметим, что любая задача класса P принадлежит и классу NP. Действительно, пусть Π ∈P и A – произвольный детерминированный полиномиальный алгоритм решения задачи Π. Тогда можно построить полиномиальную НДМТ для решения задачи Π, используя алгоритм A в качестве стадии проверки НДМТ и игнорируя
стадию угадывания. Поэтому
P ⊆ NP.
Неизвестно, является ли это включение строгим, так как ни для одной задачи класса NP не доказано, что она не может иметь полиномиального алгоритма. Однако, как мы уже указали, предполагается
гипотеза:
P 6= NP.
Рис. 22 иллюстрирует эту гипотезу.
Рис. 22
Нас в дальнейшем будут интересовать задачи из NP\P (заштрихованная область на рисунке).
118
Покажем теперь, что для любой задачи Π ∈ NP существует детерминированный алгоритм ее решения экспоненциальной трудоемкости.
Теорема 4.1. Если Π ∈ N P , то существует такой полином
p(n), что Π может быть решена детерминированным алгоритмом трудоемкости O(2p(n) ).
Доказательство. Пусть A – полиномиальный недетерминированный алгоритм решения задачи Π и q(n) – полином, оценивающий
трудоемкость A :
TA (n) ≤ q(n). Сам полином q(n) может быть
вычислен за полиномиальное время (например, схема Горнера имеет
трудоемкость 2 · m = Θ(m), где m – степень полинома).
Для каждого принимающего входа длины n найдется некоторое
слово-догадка длины не более q(n) (в TA (n) входит число операций
по формированию этого слова) и такое, что стадия проверки выполняется не более чем за q(n) шагов (в TA (n) входит число операций
по проверке принимающего вычисления).
Общее число слов-догадок не превосходит k q(n) , где k = |Γ| (если
слово короче q(n), то его можно дополнить пустыми символами).
Теперь будем выяснять, имеет ли алгоритм A на заданном входе длины n принимающее вычисление. Для этого надо для каждого
слова-догадки из k q(n) запустить детерминированную стадию проверки и дать ей работать до тех пор, пока она не остановится или пока
она не сделает q(n) шагов. Этот моделирующий алгоритм дает ответ
"да", если встретится слово-догадка, приводящая к принимающему
вычислению длины не более q(n), и "нет" – в противном случае.
Такой алгоритм будет детерминированным алгоритмом решения
задачи Π. Его трудоемкость равна q(n)·k q(n) и при подходящем выборе полинома p(n) может быть оценена как O(2p(n) ), что и требовалось
доказать.
4.4 Полиномиальная сводимость
и NP-полные задачи
Если классы NP и P не совпадают (NP 6= P), то все задачи из
NP\P труднорешаемы. Пока это не показано, нельзя говорить, что
119
конкретная задача из NP\P. Цель теории NP-полных задач состоит
в доказательстве более слабых, условных утверждений вида:
если NP 6= P, то Π ∈ NP\P.
Основная идея основана на понятии полиномиальной сводимости
задач.
Определение полиномиальной сводимости. Пусть имеются две
задачи – Π1 и Π2 . Пусть DΠ1 и DΠ2 – множества индивидуальных
задач для Π1 и Π2 соответственно, а YΠ1 ⊆ DΠ1 и YΠ2 ⊆ DΠ2 –
множества индивидуальных задач с ответом "да" соответственно для
Π1 и Π2 . Тогда если существует функция f из DΠ1 в DΠ2 такая, что:
1) f – вычисляется полиномиальным алгоритмом (алгоритмом полиномиальной трудоемкости);
2) ∀I ∈ DΠ1 I ∈ YΠ1 ↔ f (I) ∈ YΠ2 ,
то Π1 полиномиально сводится к Π2 , и это обозначается
Π1 ∝ Π2 .
Из введенного определения следуют 2 теоремы.
Теорема 4.2. Пусть Π1 ∝ Π2 и Π2 ∈ P. Тогда Π1 ∈ P.
Доказательство. Пусть A2 – полиномиальный алгоритм решения задачи Π2 . Тогда полиномиальный алгоритм A1 решения задачи Π1
можно построить следующим образом:
1) по данным индивидуальной задачи I1 ∈ Π1 находим данные
индивидуальной задачи f (I1 ) = I2 ∈ Π2 – эта часть алгоритма полиномиальна за счет полиномиальной вычислимости функции f ;
2) решаем задачу I2 алгоритмом A2 , полиномиальным по условию
теоремы. Ее решение является решением задачи I1 .
Теорема доказана.
Теорема 4.3. Пусть Π1 ∈ NP\P и Π1 ∝ Π2 . Тогда Π2 ∈ NP\P.
Доказательство. Действительно, если предположить противное, то
Π2 ∈ P и по предыдущей теореме Π1 ∈ P, что противоречит условию.
Теорема доказана.
Приведем пример полиномиальной сводимости. Рассмотрим задачу распознавания гамильтонова цикла (ГЦ): Задан граф G(V, U ).
Верно ли, что он содержит гамильтонов цикл? Обозначим описанную выше задачу распознавания коммивояжера через КМ. Покажем,
120
что ГЦ сводится к КМ. Для этого надо указать функцию f отображения индивидуальной задачи из ГЦ в индивидуальную задачу
КМ.
Пусть граф G(V, U ), |V | = n, |U | = m задает фиксированную индивидуальную задачу из ГЦ. Соответствующая ей индивидуальная
задача из КМ строится следующим образом:
1) V – множество городов, расстояние между каждой парой которого d(vi , vj ) = 1, если {vi , vj } ∈ U , и d(vi , vj ) = 2 в противном
случае;
2) полагаем B = n.
Это соответствие и выражает функцию f . Ее трудоемкость не более
O(m2 ), так как требует рассмотрения всех пар городов (O(n2 ) операций) и проверки в списке ребер соответствующего ребра для пары
городов (O(m) операций). Таким образом, функция f полиномиальна
по трудоемкости.
Для обоснования выполнения второго требования определения полиномиальной сводимости надо показать, что граф G содержит гамильтонов цикл тогда и только тогда, когда в f (G) имеется маршрут,
который проходит через все города и длина которого не превосходит
B. Пусть <v1 , . . . , vn , v1 > – гамильтонов цикл в G. Тогда это маршрут
в f (g) и его длина равна n = B (расстояние между любыми городами маршрута равно 1). Наоборот, пусть <v1 , . . . , vn , v1 > – маршрут в
f (G), длина которого не превосходит B. Так как расстояние между
любыми городами либо 1, либо 2, суммируются ровно n расстояний и B = n, то расстояние между каждой парой городов равно 1.
По построению функции f каждой паре городов соответствует ребро
графа G, а маршруту, проходящему через все города, соответствует
гамильтонов цикл в G. Таким образом, ГЦ ∝ КМ.
Введенное отношение полиномиальной сводимости задач обладает
свойством транзитивности:
Π1 ∝ Π2 & Π2 ∝ Π3 → Π1 ∝ Π3 .
Это позволяет построить отношение полиномиальной эквивалентности задач: задачи класса NP полиномиально эквивалентны, если
они полиномиально сводятся друг к другу (Π1 ∝ Π2 и Π2 ∝ Π1 ).
121
Отношение полиномиальной эквивалентности позволяет факторизовать задачи класса NP. При этом класс P является классом эквивалентности (наименьшим в NP). Среди других задач класса NP
большой интерес представляют задачи, к каждой из которых полиномиально сводится любая другая задача этого класса. Они входят
в класс эквивалентности и называются NP-полными задачами.
Определение NP-полной задачи. Задача Π ∈ NP называется
0
NP-полной, если любая другая задача Π ∈ NP полиномиально к
0
ней сводится: Π ∝ Π.
Если NP-полную задачу Π можно решить за полиномиальное время,
то тогда за полиномиальное время можно решить любую задачу из
класса NP, т. е. NP=P. Так как у нас есть сомнение в этом, то NPполные задачи являются полными представителями труднорешаемых
задач в классе NP. Следствием из теоремы 4.3 является следующая
теорема.
Теорема 4.4. Если Π1 , Π2 ∈ N P, Π1 – NP-полная задача и
Π1 ∝ Π2 , то Π2 также является NP-полной задачей.
Структура класса NP: P – полиномиальные задачи; NPC – NP-полные задачи; NPI –
промежуточные задачи класса NP. Вне класса NP – NP-трудные задачи
Рис. 23
122
На рис. 23 представлена структура класса NP. Здесь между классом NP-полных задач и классом P задач полиномиальной трудоемкости указан промежуточный класс NPI задач класса NP, для которых неизвестны полиномиальные алгоритмы, но они не являются
NP-полными задачами. Помимо этого за пределами класса NP указаны задачи, которые называются NP-трудными. Для таких задач
может быть неизвестно, принадлежат ли они классу NP, но к ним
полиномиально сводится любая задача из класса NP. Они не менее труднорешаемы, чем NP-полные задачи. Оптимизационные задачи, для которых соответствующие задачи распознавания NP-полные,
являются NP-трудными. Что касается класса NPI, то мы также не
станем обосновывать их существование 4 .
Нашей целью является показать существование NP-полных задач. Исторически первый результат в этом направлении установил
С. Кук для задачи выполнимости булевых функций (мы будем именовать эту задачу термином ВЫПОЛНИМОСТЬ ): существует ли
набор значений аргументов, доставляющий булевой функции значение истина? Набор аргументов, доставляющий булевой функции
значение истина, будем называть выполняющим набором.
4.5 Теорема Кука
Теорема 4.5. Задача ВЫПОЛНИМОСТЬ булевых функций является NP-полной.
Доказательство. Прежде всего отметим, что задача ВЫПОЛНИМОСТЬ принадлежит классу NP, так как для любого набора значений аргументов проверка истинности булевой функции осуществляется за полиномиальное число шагов. Остается показать, что любая
задача Π ∈ NP полиномиально сводится к данной задаче ВЫПОЛНИМОСТЬ: Π ∝ ВЫПОЛНИМОСТЬ.
Каждую задачу Π можно представить НДМТ, работающей полиномиальное время. Рассмотрим произвольную НДМТ M . Она описывается компонентами Γ, Σ, Λ, Q, q1 , q0Y , q0N , δ (см. раздел 4.2).
Пусть p(n) – полином с целыми коэффициентами, ограничивающий
Некоторые обоснования см.: Гэри М., Джонсон Д. Вычислительные машины и
труднорешаемые задачи. М., 1982.
4
123
сверху трудоемкость M : TM (n) ≤ p(n). Можно считать, что выполнено условие p(n) ≥ n для всех n (иначе можно увеличить p(n) на
n + const). Функция f , реализующая общую сводимость (полиномиальную сводимость для общей НДМТ), будет описана в терминах
M, Γ, Σ, Λ, Q, q1 , q0Y , q0N , δ и p. Ее надо построить так, чтобы она
обладала следующим свойством:
∀x ∈ Σ∗
x ∈ YΠ ↔ для f (x) имеется выполняющий набор.
Ключом построения функции f является построение КНФ (конъюнктивно-нормальной формы) для утверждения x ∈ YΠ .
Если x ∈ Σ∗ принимается НДМТ M (x ∈ YΠ ), то для x существует принимающее вычисление программы M такое, что число шагов
на стадии проверки и число символов в слове-догадке ограничены
величиной p(n), где n = |x|. В таком вычислении участвуют лишь
ячейки с номерами от −p(n) до p(n) + 1:
1) головка угадывающего модуля начинает работу в ячейке с номером -1 и на каждом шаге сдвигается не более чем на 1 ячейку
влево, и так как общее число шагов ограничено p(n), то головка
может достигнуть ячейки с номером не далее чем −p(n);
2) проверяющее вычисление полностью определяется заданием в
каждый момент времени содержания ячеек с этими номерами, внутренним состоянием и положением головки устройства управления, и
так как имеется не более p(n) шагов, то головка не может достигнуть
ячейки с номером, большим, чем p(n) + 1.
Перейдем к описанию множества U переменных, участвующих в
определении функции f . Для удобства описания перенумеруем элементы внутреннего алфавита
0
0
0
0
0
Q = {q0 = q0Y , q1 = q0N , q2 = q1 , q3 , . . . , qr } (r = |Q|)
и внешнего алфавита
Γ = {a0 = Λ, a1 , . . . , av }
(v = |Γ| − 1).
В дальнейшем мы используем 3 типа переменных:
1) Q[i, k] i ∈ 0, p(n), k ∈ 0, r – после выполнения i-го шага про0
веряющего вычисления M находится в состоянии qk ;
124
2) H[i, j] i ∈ 0, p(n), j ∈ −p(n), p(n) + 1 – после выполнения i-го
шага проверяющего вычисления M головка устройства управления
просматривает ячейку с номером j;
3) A[i, j, l] i ∈ 0, p(n), j ∈ −p(n), p(n) + 1, l ∈ 0, v – перед выполнением i-го шага проверяющего вычисления M в j-ю ячейку записан
символ al .
Когда программа M вычисляется, она индуцирует изменение значений истинности на этом наборе переменных. При завершении программы раньше момента p(n) конфигурация остается неизменной –
сохраняется заключительное состояние, положение головки устройства управления НДМТ и запись на ленте. Поэтому можно рассматривать состояние и запись на ленте в момент p(n). В нулевой момент
времени (перед началом работы M ) на ленте находится входное слово в ячейках с 1 по n и слово-догадка w в ячейках с -1 по -|w|.
Остальные ячейки пусты. Этим моментам (p(n) и 0) соответствуют
2 набора переменных множества U . Другие наборы переменных U
соответствуют другим моментам времени работы M .
Произвольный набор значений истинности переменных U не обязательно соответствует какому-то вычислению, тем более принимающему вычислению. Описание функции f осуществляется при помощи построения такой ее КНФ, содержащей перечисленные переменные, что
набор истинностных значений переменных будет выполняющим тогда и только тогда, когда этот набор индуцируется принимающим вычислением на входе x; при
этом стадия проверки этого вычисления осуществляется не более чем за p(n) шагов и слово-догадка имеет
длину не более p(n).
Таким образом, на входе x осуществляется принимающее вычисление программы M тогда и только тогда, когда на входе x осуществляется принимающее вычисление программой M , число шагов
которой не превосходит p(n) и слово-догадка имеет длину, не превосходящую p(n), а это выполняется в том и только в том случае,
когда существует выполняющий набор значений переменных для
КНФ f (x).
125
Тем самым для f будет выполнено условие 2) полиномиальной
сводимости. Надо показать, что условие 1) также выполняется: функция f вычислима за полиномиальное время. Для этого нужно завершить описание КНФ функции f и оценить ее сложность.
КНФ индивидуальной задачи f (x) разделим на конъюнкцию 6
групп:
f (x) = G1 ∧ G2 ∧ G3 ∧ G4 ∧ G5 ∧ G6 ,
где
G1 – на каждом шаге работы программа M находится ровно в
одном состоянии;
G2 – на каждом шаге работы головка просматривает ровно одну
ячейку;
G3 – после каждого шага каждая ячейка содержит ровно один
символ из Γ;
G4 – перед 1-м шагом вычисление находится в исходной конфигурационной стадии проверки при входе x;
G5 – не позднее чем через p(n) шагов M переходит в состояние
Y
q0 и, следовательно, принимает x;
G6 – для любого i-го шага конфигурация программы, образующаяся после (i + 1)-го шага, получается из конфигурации после i-го
шага применением функции перехода δ.
Перейдем теперь к описанию каждой из групп.
G1 =
p(n) r
^_
p(n)
Q[i, k] ∧
i=0 k=0
=
p(n) r
^_
i=0 k=0
где дизъюнкция
^
Q[i, k] ∧ Q[i, k 0 ] =
i=0
0
0≤k<k ≤r
p(n)
^
Q[i, k] ∧
Q[i, k] ∨ Q[i, k 0 ],
i=0
0
0≤k<k ≤r
r
_
Q[i, k]
k=0
обеспечивает, что после каждого шага программа находится, по край-
126
ней мере, в 1 состоянии, а конъюнкция
^
Q[i, k] ∧ Q[i, k 0 ]
0≤k<k 0 ≤r
обеспечивает, что после каждого шага программа не может находиться в 2 состояниях.
p(n) p(n)+1
G2 =
^
_
p(n)
^
H[i, j] ∧
i=0 j=−p(n)
i=0
0
−p(n)≤j<j ≤p(n)+1
p(n) p(n)+1
=
^
H[i, j] ∧ H[i, j 0 ] =
p(n)
_
^
H[i, j] ∧
i=0 j=−p(n)
¡
¢
H[i, j] ∨ H[i, j 0 ] ,
i=0
0
−p(n)≤j<j ≤p(n)+1
где дизъюнкция
p(n)+1
_
H[i, j]
j=−p(n)
обеспечивает, что после каждого шага головка просматривает, по
крайней мере, 1 ячейку, а конъюнкция
^
H[i, j] ∧ H[i, j 0 ]
−p(n)≤j<j 0 ≤p(n)+1
обеспечивает, что после каждого шага головка не может просматривать 2 ячейки.
p(n) p(n)+1
G3 =
^
v
^ _
p(n) p(n)+1
A[i, j, l] ∧
i=0 j=−p(n) l=0
p(n) p(n)+1
=
^
v
^ _
i=0 j=−p(n) l=0
где дизъюнкция
^
^
A[i, j, l] ∧ A[i, j, l0 ] =
i=0 j=−p(n) 0≤l<l0 ≤v
p(n) p(n)+1
A[i, j, l] ∧
^
^
^
^
i=0 j=−p(n) 0≤l<l0 ≤v
v
_
A[i, j, l]
l=0
127
¢
A[i, j, l] ∨ A[i, j, l0 ] ,
обеспечивает, что перед каждым шагом каждая ячейка с номерами
из интервала от −p(n) до p(n) + 1 содержит, по крайней мере, 1
символ, а конъюнкция
^
A[i, j, l] ∧ A[i, j, l0 ]
0)≤l<l0 ≤v
обеспечивает, что перед каждым шагом каждая ячейка с номерами
из интервала от −p(n) до p(n) + 1 не может содержать 2 символа.
G4 = Q[0, 2] ∧ H[0, 1] ∧ A[0, 0, 0] ∧
n
^
p(n)+1
A[0, j, lj ] ∧
j=1
^
A[0, j, 0],
j=n+1
где
0
Q[0, 2] означает начальное состояние q2 = q1 перед выполнением
1-го шага программы M ;
H[0, 1] означает, что головка устройства управления просматривает ячейку с номером 1 перед выполнением 1-го шага программы
M;
A[0, 0, 0] означает, что в ячейке с номером 0 записан пустой символ a0 = Λ перед выполнением 1-го шага программы M ;
Vn
j=1 A[0, j, lj ] означает запись входного слова x = al1 . . . aln в ячейках с номерами от 1 по n перед выполнением 1-го шага программы
M ;V
p(n)+1
j=n+1 A[0, j, 0] означает запись пустых символов в ячейках с номерами от n + 1 по p(n) + 1 перед выполнением 1-го шага программы
M.
G5 = Q[p(n), 0]
означает, что через p(n) шагов программа M придет в заключитель0
ное состояние q0 = q0Y .
Группу G6 разобьем на конъюнкцию 2 подгрупп
0
00
G6 = G6 ∧ G6 , где
0
G6 означает, что если на i-м шаге не просматривается ячейка с
номером j, то после выполнения этого шага (перед выполнением
(j + 1)-го шага) символ в ячейке j не изменится;
128
00
G6 означает, что перестройка в следующую конфигурацию происходит согласно функции перехода δ программы M для каждой
четверки (i, j, k, l) i ∈ 0, p(n), j ∈ −p(n), p(n) + 1, k ∈ 0, r, l ∈ 0, v.
p(n) p(n)+1
0
G6 =
^
v
^ ^
H[i, j] → (A[i, j, l] → A[i + 1, j, l]) =
i=0 j=−p(n) l=1
p(n) p(n)+1
=
00
G6 =
^
v
^ ^
H[i, j] ∨ A[i, j, l] ∨ A[i + 1, j, l]).
i=0 j=−p(n) l=1
^
(Q[i, k] ∧ H[i, j] ∧ A[i, j, l]) →
i,j,k,l
0
0
→ (Q[i+1, k ]∧H[i+1, j +∆]∧A[i+1, j, l ]) =
^
=
Q[i, k] ∧ H[i, j] ∧ A[i, j, l] ∨
i,j,k,l
0
=
^
0
(Q[i+1, k ]∧H[i+1, j +∆]∧A[i+1, j, l ]) =
(Q[i, k] ∨ H[i, j] ∨ A[i, j, l]) ∨
i,j,k,l
0
=
^
0
(Q[i+1, k ]∧H[i+1, j +∆]∧A[i+1, j, l ]) =
0
(Q[i, k] ∨ H[i, j] ∨ A[i, j, l] ∨ Q[i + 1, k ]) ∧
i,j,k,l
(Q[i, k] ∨ H[i, j] ∨ A[i, j, l] ∨ H[i + 1, j + ∆]) ∧
0
(Q[i, k] ∨ H[i, j] ∨ A[i, j, l] ∨ A[i + 1, j, l ]),
0
где значения номера нового состояния k , сдвига ∆ и номера запи0
сываемого символа l определяются следующим образом:
0
0
0
(qk0 , ∆, al0 ) = δ(qk , al ), если qk ∈ Q \ {q0Y , q0N };
0
0
k = k, ∆ = 0, l = l, если qk 0 ∈ {q0Y , q0N }.
Каждая из определенных групп представлена в виде КНФ. Поэтому их конъюнкция также является КНФ. Если x есть принимающее
вычисление, длина которого не превосходит p(n), то f (x) = T . Наоборот, пусть f (x) = T для некоторого значения x. Тогда имеется
принимающее вычисление.
Для того чтобы убедиться в полиномиальной ограниченности вычисления функции f (x), нужно показать, что длина f (x) ограничена
сверху полиномом от n. Для оценки сверху такой длины можно взять
129
|U | · |C|, где C – множество конъюнкций в КНФ. Ни одна конъюнкция, являющаяся элементарной дизъюнкцией, не может содержать
больше чем 2 · |U | литералов (переменные множества U и их отрицания). Оценим сверху число операций для каждой группы, входящей
в КНФ:
для G1 : O(p(n));
для G2 : O(p(n)3 );
для G3 : O(p(n)2 );
для G4 : O(p(n));
для G5 : O(1);
0
для G6 : O(p(n));
00
для G6 : O(p(n)2 ).
Так как вычисление каждого литерала КНФ также можно оценить
сверху как O(p(n)3 ), то общая оценка трудоемкости вычисления f (x)
равна O(n · p(n)3 ), что показывает полиномиальную ограниченность
вычисления f (x) – свойство 1) полиномиальной сводимости.
Таким образом, для произвольной задачи Π ∈NP
Π ∝ ВЫПОЛНИМОСТЬ,
что и требовалось доказать.
4.6 Методы доказательства NP-полноты
Все методы доказательства NP-полноты задач используют уже известные NP-полные задачи и на основании их конструируют доказательство NP-полноты других задач, используя теорему 4.4. При этом
строится полиномиальная сводимость известной NP-полной задачи
к интересующей нас задаче. Специфика задач оказывает влияние на
доказательство. Вместе с этим имеются несколько общих приемов,
которые могут помочь при доказательстве NP-полноты новой задачи.
4.6.1
Локальная замена и NP-полнота задачи
3-ВЫПОЛНИМОСТЬ
В основе приема, называемого локальной заменой, лежит выделение
в известной NP-полной задаче основных модулей индивидуальных
130
задач и построение индивидуальных задач заданной задачи с помощью единообразной локальной замены этих модулей на другие. Для
примера рассмотрим следующее доказательство NP-полноты задачи
3-ВЫПОЛНИМОСТЬ.
Известной задачей является задача ВЫПОЛНИМОСТЬ, и нужно показать ее полиномиальное сведение к другой интересующей
нас задаче 3-ВЫПОЛНИМОСТЬ, которая является сужением задачи ВЫПОЛНИМОСТЬ:
вместо любых КНФ рассматриваются только такие, любой конъюнктивный член которых является элементарной дизъюнкцией ровно 3 переменных или их отрицаний.
Вопрос задачи остается прежним: существует ли на
множестве U переменных такой набор значений истинности, при котором КНФ задачи имеет значение “истина”?
Эту задачу проще, чем задачу ВЫПОЛНИМОСТЬ, использовать
при доказательстве NP-полноты других задач.
Теорема 4.6. Задача 3-ВЫПОЛНИМОСТЬ является NP-полной.
Доказательство. Задача 3-ВЫПОЛНИМОСТЬ принадлежит классу
NP как сужение NP-полной задачи. КНФ этой задачи мы будем
называть для краткости
Vm Wki 3-КНФ. Vm
Пусть C = i=1 j=1 xij = i=1 ci – произвольная КНФ задачи ВЫПОЛНИМОСТЬ на множестве переменных U = {u1 , . . . , un },
S Sk j
S
x
⊆
U
U ). Для определения функции f отображения,
( m
i=1 j=1 ij
осуществляющей полиномиальную
сводимость, построим соответVm 0
0
0
ствующую C 3-КНФ C = i=1 ci , где ci является 3-КНФ на объеди0
нении множества U и множества дополнительных переменных Ui ,
0
0
используемых только в дизъюнкциях из ci (C определяется на мноS
0
0
жестве переменных U = U ∪ ( m
U
)). Для того чтобы обосновать,
i
i=1
что КНФ C выполняется тогда и только тогда, когда выполняется
0
3-КНФ C , мы покажем, что для всех i ∈ 1, m ci выполняются в
том и только в том случае, когда выполняются соответствующие им
0
3-КНФ ci .
0
0
Вид 3-КНФ Ci и множество Ui дополнительных переменных будут
131
зависеть от числа элементов ki в дизъюнкции ci =
рим 4 случая.
Wki
j=1 xij .
Рассмот-
0
1. ki = 1. Тогда полагаем Ui ≡ {zi1 , zi2 } и
0
ci ≡ (xi1 ∨ zi1 ∨ zi2 ) ∧ (xi1 ∨ z i1 ∨ zi2 ) ∧ (xi1 ∨ zi1 ∨ z i2 ) ∧ (xi1 ∨ z i1 ∨ z i2 ).
0
При истинности ci = xi1 истинным является и Ci , и, наоборот,
0
при ложности ci ложным является и Ci .
0
2. ki = 2. Тогда полагаем Ui ≡ {zi1 } и
0
ci ≡ (xi1 ∨ xi2 ∨ zi1 ) ∧ (xi1 ∨ xi2 ∨ z i1 ).
0
При истинности ci = xi1 xi2 истинным является и Ci , и, наоборот,
0
при ложности ci ложным является и Ci .
0
0
0
3. ki = 3. Тогда полагаем Ui ≡ ∅, Ci ≡ ci . В этом случае ci и ci
эквивалентны.
0
4. ki > 3. Тогда полагаем Ui ≡ {zij | j ∈ 1, ki − 3} и
0
ci ≡ (xi1 ∨ xi2 ∨ zi1 ) ∧
k^
i −4
(z ij ∨ xi,j+2 ∨ zi,j+1 ) ∧ (z i,ki −3 ∨ xi,ki −1 ∨ xi,ki ).
i=1
Wi
Так как ci = kj=1
xij , то при ci = T найдется такое l ∈ 1, ki , что
xil = T .
Если l ∈ {1, 2}, то полагаем zij = F (j ∈ 1, ki − 3).
Если l ∈ {ki − 1, ki }, то полагаем zij = T (j ∈ 1, ki − 3).
В противном случае полагаем
½
T (j ∈ 1, l − 2),
zij =
F (j ∈ l − 1, ki − 3).
0
При этих значениях дополнительных переменных ci будет истинным тогда и только тогда, когда ci является истинным.
0
Таким образом, эквивалентность булевых функций C и C доказана.
0
Так как число элементарных дизъюнкций в C ограничено полиномом от m · n, то функция f , осуществляющая описанное сведение,
является полиномиально вычислимой. Полученное полиномиальное
132
сведение задачи ВЫПОЛНИМОСТЬ к задаче 3-ВЫПОЛНИМОСТЬ
доказывает теорему.
Анализ проведенного доказательства NP-полноты показывает, что
в исходной задаче ВЫПОЛНИМОСТЬ основными модулями являлись элементарные дизъюнкции. Каждая из таких дизъюнкций заменялась на КНФ специального вида – 3-КНФ, а такая замена и
доказательство полиномиального сведения производились локально.
В этом и состоит суть приема локальной замены.
Отметим в заключение, что аналогичная 3-ВЫПОЛНИМОСТИ
задача 2-ВЫПОЛНИМОСТЬ принадлежит классу P и для нее не
удается установить NP-полноту (и тем самым опровергнуть гипотезу
NP6=P).
4.6.2
Построение компонент и NP-полнота задач
ВЕРШИННОЕ ПОКРЫТИЕ и КЛИКА
Метод построения компонент основан на идее конструирования с
помощью составных частей некоторых компонент индивидуальных
задач NP-полной задачи. Так, в доказательстве теоремы Кука использована идея конструирования индивидуальной задачи с помощью 6 типов компонент, являющихся группами КНФ. Другой более
интересный пример связан с доказательством NP-полноты задачи
ВЕРШИННОЕ ПОКРЫТИЕ.
Перейдем сначала к задаче распознавания КЛИКА, связанной с
упоминавшейся в разделе 4.1 оптимизационной задачей о клике. Соответствующая задача распознавания формулируется следующим образом:
Задан граф G(V, U) и натуральное число K ≤ |V |. Верно ли,
что G содержит клику размером не менее K?
Задача о максимальной клике тесным образом связана со следующей оптимизационной задачей о вершинном покрытии: найти наименьшее (по числу вершин) множество X вершин в графе G(V,U)
такое, что каждое ребро {v1 , v2 } ∈ U графа G содержит хотя бы
1 вершину v1 или v2 в этом множестве X.
Действительно, множество V \ X содержит максимальное число
попарно не смежных вершин в графе G и, следовательно, макси133
мальную клику в дополнительном графе G(V, U ). Обратно, если X
– множество вершин максимальной клики в G(V, U ), то в дополнительном графе G(V, U ) это максимальное множество попарно не
смежных вершин, и, следовательно, множество V \ X образует минимальное вершинное покрытие в G(V, U ).
С задачей о минимальном вершинном покрытии связана задача
распознавания ВЕРШИННОЕ ПОКРЫТИЕ:
Задан граф G(V,U) и натуральное число K ≤ |V |. Верно ли, что
граф имеет вершинное покрытие не более чем из K вершин?
Теорема 4.7. Задачи КЛИКА и ВЕРШИННОЕ ПОКРЫТИЕ являются NP-полными.
Доказательство. Достаточно установить полиномиальную сводимость
задач:
3-ВЫПОЛНИМОСТЬ ∝ ВЕРШИННОЕ ПОКРЫТИЕ,
так как задача ВЕРШИННОЕ ПОКРЫТИЕ принадлежит классу NP
(достаточно угадать множество вершин и проверить неравенство для
его числа и то, что каждое ребро имеет одну из своих вершин в этом
множестве), а задача КЛИКА полиномиально эквивалентна последней.
Нам необходимо по 3-КНФ x задачи 3-ВЫПОЛНИМОСТЬ сконструировать функцию f (x) = G(V (x), E(x)) отображения в граф
G(V, E), которая осуществляет полиномиальную сводимость к задаче
ВЕРШИННОЕ ПОКРЫТИЕ для этого графа. Граф G отображения
будет состоять из 3 частей:
1) G1 (V1 , E1 ), которая содержит для каждой переменной ui ∈ U
(i ∈ 1, n) 3-КНФ x пару вершин ui ∈ V1 , ui ∈ V1 и связывающее их
ребро {ui , ui } ∈ E1 ;
2) G2 (V2 , E2 ), которая для каждой дизъюнкции
0
0
0
0
cj = uj1 ∨ uj2 ∨ uj3 (ujk ∈ {ujk , ujk }, k ∈ {1, 2, 3})
W
j
3-КНФ m
j=1 cj содержит 3 вершины множества V2 = {vj1 , vj2 , vj3 }
и 3 связывающих
множества
E2j ; при этом определяются
Smих ребра
S
j
множества V2 = j=1 V2j , E2 = m
j=1 E2 ;
S
j
3) множество E3 = m
j=1 E3 связывающих обе части G1 и G2 ребер,
0
0
0
где E3j = {{vj1 , uj1 }, {vj2 , uj2 }, {vj3 , uj3 }}.
134
Для завершения построения индивидуальной задачи ВЕРШИННОЕ ПОКРЫТИЕ положим
V = V1 ∪ V2 , E = E1 ∪ E2 ∪ E3 , K = n + 2m.
На рис. 24 приведен граф, соответствующий 3-КНФ
(u1 ∨ u2 ∨ u3 ) ∧ (u1 ∨ u2 ∨ u4 ).
Индивидуальная задача из задачи ВЕРШИННОЕ ПОКРЫТИЕ, соответствующая
индивидуальной задаче из задачи 3-ВЫПОЛНИМОСТЬ для 3-КНФ
(u1 ∨ u2 ∨ u3 ) ∧ (u1 ∨ u2 ∨ u4 ). K = 8.
Рис. 24
Функция f (x), осуществляющая построение графа G(V (x), E(x)),
имеет полиномиальную трудоемкость Θ(mn). Поэтому остается показать, что 3-КНФ x выполнима тогда и только тогда, когда граф G
имеет вершинное покрытие с числом вершин не более K.
0
0
0
Пусть V – вершинное покрытие G и |V | ≤ K. Так как V должно
содержать как минимум 1 вершину для каждого ребра из части G1
и как минимум 2 вершины для каждого треугольника части G2 ,
0
0
то |V | ≥ n + 2m = K, и, следовательно, |V | = n + 2m. Возьмем
следующий набор значений переменных 3-КНФ:
0
t(ui ) = (ui ∈ V )
(i ∈ 1, n)
(здесь ui слева от знака равенства – переменная, а справа – соответствующая ей вершина графа). Чтобы показать, что этот набор
выполняет 3-КНФ, достаточно показать, что он выполняет каждую
135
j
дизъюнкцию cj 3-КНФ.
Из
трех
ребер
подграфа
E
только 2 инци3
jT 0
дентны вершинам V2 V (пусть это будут вершины vj1 и vj2 ), и так
0
0
0
как вершина vj3 не входит в V , то в V входит другая вершина uj3
0
третьего ребра. По определению части E3 переменная uj3 является
0
членом дизъюнкции cj , и потому t(uj3 ) = T , а это дает значение
истина для дизъюнкции cj , что и требовалось.
Обратно, предположим, что t(ui ) (i ∈ 1, n) – выполняющий набор
0
3-КНФ. Построим вершинное покрытие V следующим образом:
0
1) включим в V вершину ui , если t(ui ) = T или ui в противном
случае (включение n вершин, покрывающих все ребра части G1 ); так
как t выполняет каждую дизъюнкцию 3-КНФ, то это обеспечивает
покрытие, по крайней мере, одного из трех ребер E3j ;
2) остается включить для каждого треугольника из G2 другие
две вершины, не являющиеся вторым концом ребра с включенной
вершиной из G1 , что дает включение еще 2m вершин и завершает
0
построение V .
0
Так как мы включили в V n + 2m вершин, то требуемое вершинное
покрытие построено. Теорема доказана.
Анализ проведенного доказательства показывает, что оно связано
с компонентами 2 типов: делающими выбор и проверяющими свойства. Первая компонента выбирает в одном случае вершины, а в
другом – значения истинности переменных. Вторая компонента проверяет выполнение некоторого свойства: в первом случае – каждая
дизъюнкция выполнена, а во втором – каждое ребро покрыто. Взаимодействие компонент осуществляется с помощью связей, которыми
являются ребра, соединяющие компоненты набора значений истинности с компонентами проверки выполнения 3-КНФ. В доказательстве использован также и прием локальной замены, когда каждой
дизъюнкции из 3-КНФ соответствует определенная часть графа.
4.6.3
Метод сужения
Этот метод доказательства NP-полноты некоторой задачи Π заключается в установлении того факта, что она включает в себя в качестве частного случая некоторую уже известную NP-полную задачу
0
Π либо полиномиально эквивалентную ей задачу.
136
Пусть, например, уже доказана NP-полнота задачи ГЦ (гамильтонов цикл). Тогда для доказательства NP-полноты задачи ОРИЕНТИРОВАННЫЙ ГАМИЛЬТОНОВ ЦИКЛ (ОГЦ) можно заметить, что
среди ориентированных графов мы можем рассматривать в качестве
частного случая неориентированные графы как графы, у которых
для каждой дуги (a, b) существует и дуга (b, a). Для такого частного
случая установление существования ориентированного гамильтонова
цикла эквивалентно задаче ГЦ, которая является NP-полной. Отсюда следует NP-полнота ОГЦ.
Используя метод сужения, следует избегать ошибки: не всякое
сужение задачи может оказаться NP-полным; если оно окажется полиномиально разрешимым, то из этого никак не будет следовать полиномиальная разрешимость исходной задачи. Например, если ограничить задачу КЛИКА рассмотрением только задач для K = 3 (назовем такую задачу 3-КЛИКА), то для определения, существует ли
клика с числом вершин, не меньшим 3, достаточно перебрать все
тройки вершин (Cn3 = n(n − 1)(n − 2)/6 = Θ(n3 )) и для каждой проверить, есть ли ребра между всеми ее вершинами (Θ(m) операций).
Поэтому задача 3-КЛИКА имеет полиномиальную трудоемкость, что
не мешает быть задаче КЛИКА NP-полной. Так, при K = n/2 число
вариантов CnK растет как Θ(nn ).
4.6.4
Список некоторых известных NP-полных задач
Этот небольшой список известных задач распознавания, связанных
с дискретными оптимизационными задачами, мы приводим с целью
использования при оценке трудоемкости дискретных задач оптимизации 5 .
1. ВЫПОЛНИМОСТЬ: выполнима ли заданная булева функция?
(Теорема 4.5).
2. 3-ВЫПОЛНИМОСТЬ: выполнима ли заданная 3-КНФ? (Теорема 4.6).
Большой список NP-полных задач см.: Гэри М., Джонсон Д. Вычислительные
машины и труднорешаемые задачи. М., 1982.
5
137
3. ВЕРШИННОЕ ПОКРЫТИЕ: имеет ли заданный граф G(V, E)
0
вершинное покрытие V ⊆ V (любое ребро из E имеет вершину
0
0
в V ) размера |V | ≤ K при заданном целом K? (Теорема 4.7).
4. КЛИКА: имеет ли заданный граф G(V, E) клику
0
0
0
0
G (V , E )
0
0
0
V ⊆ V, E ⊆ E, ∀a ∈ V , b ∈ V : {a, b} ∈ E
0
0
размерности |V | ≥ K при заданном K ≤ |V |? (следует из теоремы 4.7).
5. ГАМИЛЬТОНОВ ЦИКЛ: имеет ли заданный граф G(V, E) гамильтонов цикл (цикл, содержащий все вершины графа)?
6. ГАМИЛЬТОНОВ ПУТЬ: имеет ли заданный граф G(V, E) гамильтонов путь (путь, содержащий все вершины графа)?
7. ОРИЕНТИРОВАННЫЙ ГАМИЛЬТОНОВ ЦИКЛ: имеет ли заданный орграф G(V, E) ориентированный гамильтонов цикл (контур, содержащий все вершины графа)?
8. РАСКРАШИВАЕМОСТЬ ГРАФА: можно ли раскрасить заданный граф G(V, E) (каждой вершине определить цвет, чтобы
смежные были раскрашены в разные цвета) в заданное число
цветов K?
9. ВЕРШИННЫЙ РАЗРЕЗ ЦИКЛОВ: имеет ли заданный граф
0
0
G(V, E) вершинный разрез V ⊆ V (удаление из V вершин V
вместе с инцидентными ребрами делает граф ациклическим)
0
размерности V ≤ K при заданном целом K ≤ |V |?
10. РЕБЕРНЫЙ РАЗРЕЗ ЦИКЛОВ: задан граф G(V, E) и целое
0
K ≤ |E|; имеет ли граф G реберный разрез E ⊆ E (удаление из
0
0
E ребер E делает граф ациклическим) размерности |E | ≤ K?
11. ИЗОМОРФИЗМ ПОДГРАФУ: заданы граф G(V1 , E1 ) и граф
H(V2 , E2 ); содержит ли граф G подграф, изоморфный графу H?
12. ОСТОВНОЕ ДЕРЕВО ОГРАНИЧЕННОЙ СТЕПЕНИ: содержит
ли заданный граф G(V, E) остовное дерево, в котором все вершины имеют степень не больше заданного целого K ≤ |V | − 1?
138
13. МАКСИМАЛЬНЫЙ ПУТЬ: для заданных графа G(V, E) и натурального числа K ≤ |V | определить, содержит ли граф G простой путь (не проходящий дважды через одну вершину) длины
не менее K?
14. РАЗБИЕНИЕ НА ГАМИЛЬТОНОВЫ ПОДГРАФЫ: задан граф
G(V, E) и натуральное число K ≤ |V |; можно ли разбить множество вершин V на непересекающиеся подмножества Vi (i ∈ 1, K)
так, чтобы каждый подграф Gi (Vi , Ei ) (i ∈ 1, K), индуцированный подмножеством Vi , содержал бы гамильтонов цикл?
15. НАИБОЛЬШИЙ ОБЩИЙ ПОДГРАФ: заданы графы G1 (V1 , E1 )
и G2 (V2 , E2 ) и натуральное число K; существуют ли подмноже0
0
ства ребер E1 ⊆ E1 и E2 ⊆ E2 с одинаковым числом ребер, не
0
0
0
0
меньшим K, такие, что подграфы G1 (V1 , E1 ) и G2 (V2 , E2 ) изоморфны?
16. МИНИМАЛЬНОЕ ПОКРЫТИЕ МНОЖЕСТВАМИ: содержит
0
ли семейство C подмножеств множества S покрытие C размерS
0
0
ности |C | ≤ K (подмножество C ⊆ C такое, что c∈C 0 c = S)
для заданного целого K?
17. МНОЖЕСТВО ПРЕДСТАВИТЕЛЕЙ: задано семейство C подмножеств множества S и натуральное число K; содержит ли S
0
подмножество S представителей для C (для каждого подмножества из C включает, по крайней мере, 1 элемент) мощности,
0
не превосходящей K : |S | ≤ K?
18. РАЗБИЕНИЕ: Заданы конечное множество U и натуральные
“веса” g(u) для каждого элемента u ∈ U ; существует ли под0
множество U ⊆ U такое, что
X
X
g(u) =
g(u) ?
u∈U 0
u∈U \U 0
19. РЮКЗАК: Заданы конечное множество U , натуральные “размеры” g(u) и “стоимости” p(u) для каждого элемента u ∈ U , а
также натуральные числа G и P ; существует ли подмножество
139
0
U ⊆ U такое, что
X
u∈U
p(u) ≤ P,
0
X
u∈U
g(u) ≥ G ?
0
20. РАСПИСАНИЕ: Заданы конечное множество “заданий” T и для
каждого t ∈ T неотрицательное целое “время готовности” r(t),
натуральные “директивный срок” d(t) и “длительность” l(t);
существует ли допустимое расписание, при котором каждое задание t ∈ T “выполняется” на интервале от σ(t) до σ(t) + l(t),
где σ(t) ≥ r(t) и σ(t) + l(t) ≤ d(t), и времена интервалов различны (в одно время не выполняется 2 задания)?
21. МИНИМАЛЬНЫЙ НАБОР ТЕСТОВ: заданы конечное множество U “возможных диагнозов”, набор C подмножеств множества U (“бинарные тесты”) и натуральное число K ≤ |C|; су0
0
ществует ли поднабор C ⊆ C, |C | ≤ K, такой, что для любой
0
пары ui ∈ U, uj ∈ U имеется некоторый “тест” c ∈ C , который
“различает” ui и uj (|{ai , aj } ∩ c| = 1)?
22. МИНИМУМ СУММЫ КВАДРАТОВ: заданы конечное множество A, каждый элемент a которого имеет натуральный “вес”
g(a) и натуральные числа k и M ; можно ли A разбить на k
подмножеств A1 , . . . , Ak , чтобы
k X
X
(
g(a))2 ≤ M ?
i=1 a∈Ai
4.7 Упражнения
1. Опишите структуру входного слова и слова-догадки для НДМТ
ГАМИЛЬТОНОВ ЦИКЛ.
2. Опишите структуру входного слова и слова-догадки для НДМТ
КЛИКА.
3. Определите 3-КНФ, заменяющую в теореме 4.6 КНФ
(x1 ∨ x2 ) ∧ (x2 ∨ x3 ∨ x4 ∨ x5 ) ∧ (x1 ∨ x2 ∨ x3 ∨ x4 ) ∧ x5 .
140
4. Постройте граф индивидуальной задачи ВЕРШИННОЕ ПОКРЫТИЕ, соответствующий 3-КНФ задачи 3-ВЫПОЛНИМОСТЬ,
полученной в предыдущем упражнении.
5. Определите дополнительный граф для графа, построенного при
решении предыдущего упражнения, и определите 3-КНФ индивидуальной задачи 3-ВЫПОЛНИМОСТЬ, соответствующей индивидуальной задаче ВЕРШИННОЕ ПОКРЫТИЕ с этим определенным графом.
6. Определите индивидуальную задачу КЛИКА, соответствующую
индивидуальной задаче ВЕРШИННОЕ ПОКРЫТИЕ из предыдущего упражнения.
7. Обоснуйте NP-полноту задачи ИЗОМОРФИЗМ ПОДГРАФУ,
используя NP-полноту задачи КЛИКА.
8. Обоснуйте NP-полноту задачи ОСТОВНОЕ ДЕРЕВО ОГРАНИЧЕННОЙ СТЕПЕНИ, используя NP-полноту задачи ГАМИЛЬТОНОВ ПУТЬ.
9. Обоснуйте NP-полноту задачи МНОЖЕСТВО ПРЕДСТАВИТЕЛЕЙ, используя NP-полноту задачи ВЫПОЛНИМОСТЬ.
10. Обоснуйте NP-полноту задачи РАЗБИЕНИЕ НА ГАМИЛЬТОНОВЫ ПОДГРАФЫ, используя NP-полноту задачи ГАМИЛЬТОНОВ ЦИКЛ.
11. Является ли NP-полной задача РАСКРАШИВАЕМОСТЬ ГРАФА в 2 цвета?
12. Обоснуйте NP-полноту задачи МАКСИМАЛЬНЫЙ ПУТЬ, используя NP-полноту задачи ГАМИЛЬТОНОВ ПУТЬ.
141
4.8 Литература
1. Гэри М., Джонсон Д. Вычислительные машины и труднорешаемые задачи. М., 1982.
2. Кормен Т., Лейзерсон Л., Ривест Р. Алгоритмы: построение и
анализ. М., 2001.
3. Пападимитриу Х., Стайглиц К. Комбинаторная оптимизация.
Алгоритмы и сложность. М., 1985.
4. Ху Т.Ч., Шинг М.Т. Комбинаторные алгоритмы. Нижний Новгород, 2004.
142
Учебное издание
Рублев Вадим Сергеевич
ОСНОВЫ ТЕОРИИ АЛГОРИТМОВ
Учебное пособие
Редактор, корректор В.Н. Чулкова
Компьютерная верстка В.С. Рублева
Подписано в печать 29.12.2005 г. Формат 60×84/16.
Усл. печ. л. 8,14. Уч.-изд. л. 6,0. Тираж 100 экз. Заказ
Оригинал-макет подготовлен
в редакционно-издательском отделе ЯрГУ.
Ярославский государственный университет
150000 Ярославль, ул. Советская, 14
Отпечатано ООО «Ремдер» ЛР ИД № 06151 от 26.10.2001
г. Ярославль, пр. Октября, 94, оф. 37 тел. (0852) 73-35-03
Download