Скачать алгоритмы руководства по разработке скиена

Алгоритмы. Руководство по разработке 2022.pdf

Имя файла: Алгоритмы. Руководство по ра зработке 2022.pdf
Размер файла: 106.35 MB
Файл загружен: 25/09/2022 18:58:15
Файл скачали: 342

Поделиться этой ссылкой

<a href=»https://wdfiles.ru/5533fe» target=»_blank» title=»Download from загрузить файл»>Скачать Алгоритмы. Руководство по разработке 2022.pdf — загрузить файл</a>

[url]https://wdfiles.ru/5533fe[/url]

QR-код это ссылка для мобильных устройств

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

Или получить файл на мобильное устройство, отсканировав QR-код программой с мобильного устройства

Приветствуем Вас на файлообменнике WdFiles.ru!

Ресурс WDfiles.RU был задуман как бесплатный файлообменник. Согласитесь, удобно с его помощью делиться файлами, или перемещать их с одного устройства на другое!

Всем спасибо за использование нашего ресурса!

Преимущества:

 Прямые загрузки. Без ожидания.

 Отсутствие рекламы.

 Неограниченное количество загрузок.

 Максимальная скорость загрузки.

 Техподдержка (support@wdfiles.ru).

 Неограниченная файловая зона.

 Файлы в премиум-аккаунте хранятся вечно.

 Загрузка файла размером свыше 10.00 GB .

 Отсутствие ограничения на загрузки.

 Низкая цена в сутки.

 Отсутствие подписок.

Steven S. Skiena

The Algorithm Design Manual
Тhird

Edition

~ Springer

Стивен С. Скиена

Алгоритмы
Руководство

по разработке
3-е издание

Санкт-Петербург
«БХВ-Петербург»

2022

УДК
ББК

681.3.06
32.973.26-018.2
С42

Скиена С. С.
С42

Алгоритмы. Руководство по разработке.
БХВ-Петербург,

2022. —

848

3-е изд.: Пер. с англ.

СПб.:

с.: ил.

ISBN 978-5-9775-6799-2
Книга является наиболее полным руководством по разработке эффективных ал­
горитмов. Первая часть книги содержит практические рекомендации по разработке
алгоритмов:

приводятся основные понятия, дается анализ алгоритмов, рассматри­

ваются типы структур данных, основные алгоритмы сортировки, операции обхода

графов и алгоритмы для работы со взвешенными графами, примеры использования
комбинаторного поиска, эвристических методов и динамического программиро­

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

75

наиболее распространенных алгоритмических задач, для которых перечислены

существующие программные реализации.

В третьем издании расширен набор рандомизированных алгоритмов, алгорит­
мов хеширования,

100

аппроксимации

и квантовых вычислений. Добавлено более

новых задач, даны ссылки к реализациям на С, С++ и

Java.

Книгу можно использовать в качестве справочника по алгоритмам для про­

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

ББК

УДК 681.3.06
32.973.26-018.2

First puЫished in English under the title
The Algorithm Design Manual
Ьу Steven S. Skiena, edition: 3
Copyright © The Editor(s) (ifapplicaЬle) and The Author(s), under exclusive license to
Springer Nature Switzerland AG, 2020
This edition has been translated and puЫished under licence from
Springer Nature Switzerland AG.
Springer Nature Switzerland AG takes no responsiЬility and shall not Ье made liaЫe for the accuracy ofthe translation.
Впервые опубликовано на английском языке под названием

The Algorithm Design Maпual
Steven S. Skiena, edition: 3
© Springer Nature Switzerland AG, 2020
Издание переведено и опубликовано по лицензии

Springer Nature Switzerland AG

ISBN 978-3-030-54255-9
ISBN 978-5-9775-6799-2

Springer Nature Switzerland AG.

не несет ответственности за точность перевода.

(англ.)
(рус.)

© Springer Nature Switzerland AG, 2020
© П~ревод на русский язык, оформление.
ООО «БХВ», 2022

ООО «БХВ-Петербург»,

Оглавление

Предисловие ….. «» … «» … «».»» … «».»» … » … «» ….. » ….. » … «» … » … «»» …… » ……. » ….. «»»17

………………………………………………………………………………………………………………………. 17
Преподавателю …………………………………………………………………………………………….•………………. 19
Благодарности ……………………………………………………………………………………………………………….. 20
Ограничение ответственности ………………………………………………………………………………………… 21

Читателю

ЧАСТЬ 1. ПРАКТИЧЕСКАЯ РАЗРАБОТКА АЛГОРИТМОВ»»»»»»»»»»»»»»23

Глава 1. Введение в разработку алгоритмов»»»»»»»»»»»»»»»»»»»»»»»»»»»»».25
1.1. Оптимизация маршрута робота … » ……….. » ………………… » …… » …………………………………….. 26
1.2. Задача календарного планирования ………………………………………………………………………….. 30
1.3. Обоснование правильности алгоритмов ……………………………………………………………………. 33
1.3.1. Задачи и свойства …………………………………………………………………………………………… 34
1.3.2. Представление алгоритмов …………………………………………………………………………….. 35
1.3.3. Демонстрация неправильности алгоритма ……………………………………………………….. 35
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: «Жадные» кинозвезды? ……………………………………………… 37
1.3.4. Индукция и рекурсия ……………………………………………………………………………………… 38
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Правильность инкрементных алгоритмов …………………… 39
1.4. Моделирование задачи ……………………………………………………………………………………………. .40
1.4 .1. Комбинаторные объекты ……………………………………………………… » ……………………… .41
1.4.2. Рекурсивные объекты ……………………………………………………………………………………. .42
1.5. Доказательство от противного ………………………………………………………………………………… .44
1.6. Истории из жизни …………………………………………………………………………………………………… .45
1.7. История из жизни. Моделирование проблемы ясновидения ……………………………………… .45
1.8. Прикидка ……………………………………………………………………………………………………………….. .49
Замечания к главе ………………………………………………………………………………………………………….. 50
1.9. Упражнения …………………………………………………………………………………………………………….. 50
Поиск контрпримеров ……………………………………………………………………………………………… 50
Доказательство правильности ………………………………………………………………………………….. 51
Математическая индукция ……………………………………………………………………………………….. 52
Приблизительные подсчеты …………………………………………………………………………………….. 52
Проекты по реализации …………………………………………………………………………………………… 53
Задачи, предлагаемые на собеседовании ………………………………………………………………….. 53
LeetCode …………………………………………………………………………………………………………………. 54
HackerRank ……………………………………………………………………………………………………………… 54
Задачи по программированию …………………………………………………………………………………. 54

Оглавление

6
Глава

2.1.

2. Анализ

алгоритмов …………………………………………………………………………… 55

Модель вычислений

RAM ………………… » …………….. » … » …………. » … «» ………….. » ……….. » .. «55
2.1.1. Анализ сложности наилучшего, наихудшего и среднего случая «»»»»»»»»»»»»».56
2.2. Асимптотические («Big Oh») обозначения ……………………………………………….. «» ….. » …….. 58
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Возвращение к определениям «»»»»»»»»»»»»»»»»»»»»»61
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Квадраты …… » ………………………………………………………. » …. 61
2.3. Скорость роста и отношения доминирования»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»61
2.3.1. Отношения доминирования «»»»»»»».»»».»».»»»»»»»».»»»».»»»»»»»»»»»»»»»».63
2.4. Работа с асимптотическими обозначениями «»»»»»».»».»»»»»»»»»».»»»»»»»»»»»»»».»64
2.4.1. Сложение функuий» …………………………. » ……. » ………………………………………………….. 64
2.4.2. Умножение функuий …………… » ………. » ……………………… » …………………………………… 64
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Транзитивность»»»»»»»»» .. «»»»».».»»»»»»»».»»»»»»»» 65
2.5. Оuенка эффективности ……………………….. » …………… » … «» ………………. » …………………………. 65
2.5.1. Сортировка методом выбора …. » ……………………………………. » ……… » ……………. » ……. 65
Доказываем временную сложность 0-большое «»»»»»»»»»»»»»»»»»» .. «»»»»»»»66
2.5.2. Сортировка вставками «»» ….. » … » ….. «» ……………………… » ………………………………….. 67
Доказываем временную сложность 0-большое «»»»»»»»»»»»»»»»»»»»»»»»»»»»68
2.5.3. Сравнение строк » … » ……. » ………………… » …………………………….. » …. » ……………………. 68
Доказываем время исполнения по 0-большое «»»»»»»»»»»»»»»»»»»»»»»»»»»»»69
……… » ….. » ………………………………………………………….. » .. «» .. » …. 70
2.6. Суммирование ………………………………… » ………. » …………………… » ………………_.» …….. » ……….. 71
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Формулы факториала «»»»»»»»»»»»»»»»»»»»»»»»»»»». 72

2.5.4.

Умножение матриu

2.7. Логарифмы и их применение»»».»»»»»»»»»».»»»»»»»»»»»»»»»»».»».»»»»»»»».»».»»».73
2.7.1. Логарифмы и двоичный поиск».»»»»»»»»»».»»»»»»»»»»»»»»»»»»»»»»»»»»».»».73
2.7.2. Логарифмы и деревья «»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»»»»»»»»».74
2.7.3. Логарифмы и биты»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»».»»»»»».74
2.7.4. Логарифмы и умножение «»».,»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»75
2.7.5. Быстрое возведение в степень»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»»»»»»»»»»»»75
2.7.6. Логарифмы и сложение «»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»»»»»»»»»»»»»»»76
2. 7. 7. Логарифмы и система уголовного судопроизводства»»»»»»»»»»»»»»»»»»»»»»». 76
2.8. Свойства логарифмов … » … » ………………… «» …………….. » ……. » ……………………… » ……………… 77
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Важно ли деление точно пополам «»»»»»»»»»»»»»»»»». 79
2.9. История из жизни. Загадка пирамид»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»». 79
2.1 О. Анализ высшего уровня (*)»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»82
2.10.1. Малораспространенные функuии»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»».»»»»»»83
2.10.2. Пределы и отношения доминирования»»»»»»»»».»»»»»»»»»»»»»»»»»»»»»»»» 84
Замечания к главе ……….. » … » ……… » ….. » ….. » …………………… » ………………………… » ………………… 85
2.11. Упражнения»»»»»»»»»»»»».»»».»»».»»»».»»»»»»»».»»»»»»»»»»»»»»»»».»»»»»»»»»».86
Анализ программ … » ………….. » ………………. » ………………………………….. » ……. » ……………….. 86
Упражнения по асимптотическим обозначениям «» … «».» ………… » … » .. » ……………………. 87
Суммирование … » ………………. » ………………… » ……………… » …………………………………… » …… 92
Логарифмы …… » ………………………………. » … » …………………….. » …… «» …. » ………………………. 93
Задачи, предлагаемые на собеседовании .. «» … » ……………………….. » …. » ………………………. 93
LeetCode …………… » …………………….. » …………………………. » ………. » … » …………………. » ………. 94
HackerRank …………………………. » ………………. » … » …………………… » ……… » ……………… » … » … 94
Задачи по программированию ……. » ………………. » ………………………………. » ………. » ………… 94

Оглавление

Глава

3.1.

3.

7

Структуры данных ……………………………………………………………………………. 95

Смежные и связные струкrуры данных …………………………………………………………………….. 95

3.1.1.
3.1.2.

Массивы ………………………………………………………………………………………………………… 96

………………………………………………………….. 97
…………………………………………………………………… 98
Вставка элемента в связный список » …………………………….. » ……………………………… 99
Удаление элемента из связного списка ……………………………………………………………. 99
3.1.3. Сравнение ….. :………………………………………………………………………………………………. 100
3 .2. Стеки и очереди …………………………………………………………………………………………………….. 1О1
3.3. Словари …………………………………………………………………………………………………………………. 102
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Сравнение реализаций словаря (1) …………………………….. 103
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Сравнение реализаций словаря (11) …………………………… 105
3 .4. Двоичные деревья поиска ………………………………………………………………………………………. 108
3 .4.1. Реализация двоичных деревьев ……………………………………………………………………… 108
Поиск в дереве ……………………………………………………………………………………………… 109
Поиск наименьшего и наибольшего элементов дерева …………………………………… 11 О
Обход дерева …………………………………………………………………………………….. :……….. 11 О
Вставка элементов в дерево ………………………………………………………………………….. 11 1
Удаление элемента из дерева ………………………………………………………………………… 112
3.4.2. Эффективность двоичных деревьев поиска ……………………………………………………. 112
3.4.3. Сбалансированные деревья поиска ……………………………………………………………….. 113
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Использование сбалансированных деревьев поиска …….. 114
3.5. Очереди с приоритетами …………………… :………………………………………………………………….. 115
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Построение базовых очередей с приоритетами …………. 115
3.6. История из жизни. Триангуляция ……………………………………………………………………………. 117
3.7. Хеширование …………………………………………………………………………………………………………. 121
3.7.1. Коллизии ……………………………………………………………………………………………………… 121
3.7.2. Выявление дубликатов с помощью хеширования ………………………….. ,…………… «.123
3.7.3. Прочие приемы хеширования ……………………………………………………………………….. 125
3.7.4. Каноникализация ………………………………………………………………………………………….. 125
3.7.5. Уплотнение ………………………………………………………………………………………………….. 126
3.8. Специализированные струкrуры данных ………………………………………………………………… 126
3.9. История из жизни. Геном человека …………………………………………………………………………. 127
Замечания к главе ………………………………………………………………………………………………………… 13 1
3.10. Упражнения …………………………………………………………………………………………………………. 131
Стеки, очереди и списки ………………………………………………………………………………………. 13 1
Элементарные струкrуры данных …………………………………………………………………………. 132
Деревья и другие словарные струкrуры …………………………………………………………. » …… 132
Применение древовидных струкrур ……………………………………………………………………… 134
Задачи по реализации …………………………………………………………………………………………… 135
Задачи, предлагаемые на собеседовании ……………………………………………………………….. 136
LeetCode ……………………………………………………………………………………………………………… 13 7
HackerRank ………………………………………………………………………………………………………….. 13 7
Задачи по программированию ………………………………………………………………………………. 137
Указатели и связные структуры данных
Поиск элемента в связном списке

Глава

4.1.

4.

Сортировка и поиск ………………………………………………………………………… 138

………………………………………………………………………………………… 138
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Поиск пересечения множеств …………………………………… 141
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Использование хеша для решения задач ……………………. 142

Применение сортировки

в

Оглавление

4.2. Практические аспекты сортировки «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».143
4.3. Пирамидальная сортировка ……………… » …………….. » …………. » ………. «.» …. » …………………. 145
4.3 .1. Пирамиды » ……………. » ………… » … » ………………………… «» …………….. » ….. » … » .. «» ….. 146
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Поиск в пирамиде «»»»»»»»»»»»»»»»»»»»»»»»»»»»»».148
4.3.2. Создание пирамиды ……… » ………. «» ……………… «» ……………… » … » .. » … » … » ……. » …. 148
4.3.3. Наименьший элемент пирамиды «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»149
4.3.4. Быстрый способ создания пирамиды (*)»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»151
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Расположение элемента в пирамиде «»»»»»» «»»»»»»». 15 3
4.3.5. Сортировка вставками ……… » ……. » … » ……………………. » ………… » ……………….. » ……. 154
4.4. История из жизни. Билет на самолет «»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»».155
4.5. Сортировка слиянием. Метод «разделяй и властвуй» «»»»»»»»»»»»»»»»»»»»»»»»»»».158
4.6. Быстрая сортировка. Рандомизированная версия»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»160
4.6.1. Интуиция: ожидаемое время исполнения алгоритма быстрой сортировки «»»»»163
4.6.2. Рандомизированные алгоритмы .» … » ….. » ……………….. «.» … «»» …… «»» … » … » …….. 164
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Болты и гайки»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»». 166
4.6.3. Действительно ли алгоритм быстрой сортировки работает быстро? «»»»»»»»»»167
4. 7. Сортировка распред(:лением. Метод блочной сортировки «»»»»»»»»»»»»»»»»»»»»»».167
4. 7.1. Нижние пределы для сортировки «»»»»»»»»»»»».».»» «»»»»»»»»»»»»»»»»»»».» 168
4.8. История из жизни. Скиена в суде «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»170
Замечания к главе ……… » ….. » ….. «» ………….. «» …………… » ….. «» … » … » ………………… » …. » …… » .. 172
4.9. Упражнения …….. » ……. » ……… » …….. :….. » … » ….. » ….. «» … » ………. » ….. » … » .. » ………. «.»»»» .. 172
Применение сортировки: сортировка чисел «».»»»»»»»» «»»»»» «»»»»»»»»»»»»»»»»». 172
Применение сортировки: интервалы и множества»»»»»»»»»»»»»»»».»»»»»»»»»»»»».174
Пирамиды

.. » ….. «»» ….. «» ….. «» ….. » ………… » ……. » … » … «» … » ……………… » ……………………. 175
…… » ………………. «» ….. «» ….. » …….. «» ………. «» … » ……….. » ….. » ……… «175
Сортировка слиянием .»» ………… «» ….. » … «»» ….. » …………………………….. » ….. » ….. » ……….. 176
Другие алгоритмы сортировки» … » ….. » ….. «».» ……. » … «» … » ……… «.» ……….. » ……. «» ……. 177
Нижние пределы.» ….. «»» ….. «» … «» ….. » ….. » ….. «» … » ….. » ….. » ………… » .. » ….. » ….. » ……….. 178
Поиск ……. «» ….. » ……… «» .. «.»» ….. » ……. » … » ……. » ………. » … «» ………. «» …. » … » … «.» …. » ….. 178
Задачи по реализации » ……. «» … » ……… » … » ….. » ….. » … » ………… » ….. » … » .. «» … «» ………….. 178
Задачи, предлагаемые на собеседовании «».»» «»»»»» «»»»»»» «»»»»»»»»»»»»»»»»»»» 179
LeetCode .. «» ….. » .. «» … «»» … «» … «» … «»» ….. » ………. » ….. » ….. » … «» … «».».» … «.» .. «.» …. «». 179
HackerRank» … «» ……. «»»» … «» … » ………… «» ….. » ….. «».»» … » … » ….. «» ……….. «.»» .. «.» ……. 180
Задачи по программированию .. » … » ……. «» … » ….. » ….. » ………. » … «» ……….. «» ….. «» ………. 180
Быстрая сортировка

Глава

5.

Метод «разделяй и властвуй» …………………………………………………………. 181

5.1. Двоичный поиск и связанные с ним алгоритмы «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»181
5. 1. 1. Частота вхождения элемента»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»» 182
5.1.2. Односторонний двоичный поиск «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»183
5.1.3. Корни числа»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».184
5.2. История из жизни. Поиск «баrа в баге» «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»184
5.3. Рекуррентные соотношения.» ….. » ….. » ….. » … » ………………. » …….. » ….. » …. » ……………. «» .. «186
5.3 .1. Рекуррентные соотношения метода «разделяй и властвуй»»»» .. «»»»»»»»»»»»». 187
5.4. Решение рекуррентных соотношений типа «разделяй и властвуй» (*)»»»»»»»»»»»»»»188
5.5. Быстрое умножение …. «» … «» …………………… «» … » …………… » … «» … » …………………………… 190
5.6. Поиск наибольшего поддиапазона и ближайшей пары»»»»»»»»»»»»»»»»»»»»»»»»»».192
5. 7. Параллельные алгоритмы …….. «» ………… » …….. » ………… » ………. » … » … » …….. » ….. » ……… » 194
5. 7.1. Параллелизм на уровне данных «»» «»»»»»» «»»»» «»»»»» «»»»»»»»»» «»».» «»»». 194
5.7.2. Подводные камни параллелизма»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».195

Оглавление

9

«»»»» …. «»» … «» … «»»».»»» .. «»»»»» … «»»»»196
(*) .. «» ……………. » ………. » … » ……. » ….. » ….. » ………… » ………………………… » ………. » … 197
5.9.1. Применение свертки «»»»».»»»».».»»».»»» «.»»».»»»».»»»»»».». «»»».»»»»»»»»»198
5.9.2. Быстрое полиномиальное умножение (**)».»»»».»»».»»»» .»»»»»»»»»»»»»»»»». 199
Замечания к главе …. » ………… » … «» ………… » … «» … «» ……. » ….. «» ………… » ………… » .. » … » ………. 202
5.1 О. Упражнения …………………. » …….. » .. » ….. » ….. » ………………. » .. «» ….. «» …… «» …………….. «» .. 202
Двоичный поиск .. «» ….. » ….. «» … » ….. » ….. » ………………………… » ………………. » ………………. 202
Алгоритмы типа «разделяй и властвуй» «».»»»».»»».»»»».»»» .»»»»»»»»»»»»».»»»» «.203
Рекуррентные соотношения …… » ………. » ……. «» ………………………. » .. » …… «» ……. » .. » …… 203
LeetCode …………. » …………………….. «» ………… » ….. » ……. » .. » ……………….. » ……………. » ….. «204
HackerRank …………. «» … » ….. » ……………. » ………. » ………….. «» ….. » ……… » …….. » ………. » ….. 204
Задачи по программированию …… » ………………… » ………………………………. » ……… » ………. 205

5.8.
5.9.

История из жизни. «Торопиться в никуда»
Свертка

Глава

6.

Хеширование и рандомизированные алгоритмы …………………………… 206

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Город быстрой сортировки

«»»»»»»»»»»».»»»»» .»»»»»207
» ….. » … » … «» ……. «» … » ……………………. » ………………… » .. » …… 207
6.1.1. Теория вероятностей …. » ………… » ………….. » ….. » ……. » ………………………………. «»» … 207
6.1.2. Составные события и независимость «.»»»»».»»».»»»».»»» «.»»».»»»»»».»».»»». 209
6.1.3. Условная вероятность»».»»»»»»».»»»»»».»»»»»».».»»»»».»»»».».»»»»».»»»»».»210
6.1.4. Распределения вероятностей.» ….. » ………………………… » ……………………………………. 211
6.1.5. Среднее и дисперсия»»».»».»»»».»»».»»»».»»»».»»».»»»».»»»».»».»»»»».»»»».»212
6.1.6. Броски монет «.».»»»»»».»».»»».». «»»»»».».»»»».»»»»» «.»»»».»»».»»».»»»»» «».212
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Случайный обход графа»»»»»».»»».».» «»»»»»».»»»».»213
6.2. Задача мячиков и контейнеров «.»».»»».»»»».»»»».» «».»»».»»»»».»»»»»» «»»»»».»»»».214
6.2.1. Зада~а о собирании купонов «»».».»»»»»».»»»».» «»»»»»»»»»»»»»»».» «»»»»»»»216
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Время покрытия для Кп .»»».»»»»»»»»»»»»»» «»».»»».»216
6.3. Почему хеширование является рандомизированным алгоритмом? «»»»»»»»»»»».»»»»2 17
6.4. Фильтры Блума …………. » …………….. » ….. » ………….. » ….. » ……………… » …… » ……… » …….. » .. «218
6.5. Парадокс дня рождения и идеальное хеширование».»»»».»»»».»»»».»»»»»»»»».»»»»»220
6.6. Метод минимальных хеш-кодов» ………….. » ………. «» ….. » ….. «» ……. «» …….. «» ….. » ………… 222

6.1.

Обзор теории вероятностей

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Приблизительная оценка численности популяции.»»»»»224

6.7. Эффективный поиск подстроки в строке».:».»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»».»».225
6.8. Тестирование чисел на простоту …. «» ………. «» ….. » ….. » ….. » ……. «»» ………. » ……. » ……… «.226
6.9. История из жизни. Как я дал Кнуту свой средний инициал»»»»»»».»»»»»»»»»»».»»»»228
6.1 О. Откуда берутся случайные числа? .»»»»»»»».»»»»»»»»» «»».»»»:»»»»».»»».»» «».»»».229
Замечания к главе ………………….. » …………………………….. » ….. » ………….. » … _… » ……… » ………….. 230
6.11. Упражнения .. » ……. » … » ………. » ….. » … » ….. » ….. » ……. » ……. » ………….. » …………………… » ….. 230
Вероятность …… » ………………. » ………. » ….. » ………….. :………………………….. » ……… » ………… 230
Хеширование … » … » ………… » …………………. » ……. » ………………… » …………… » ……….. » …….. 231
Рандомизированные алгоритмы …………………………….. » ………………………. » ……. «» ……. «.231
LeetCode ………… » …………………… » …………………… » ………………… » ……. » …….. » ……… » ……. 232
HackerRank ……. » ………. » … «» … » ….. » … » ….. » ….. » ….. » ………… » ………………………. » ………… 232
Задачи по программированию … » …………… » ………………… » ….. » ………………….. » … » ……… 232
Глава

7.1.

7.

Обход графов …………………………………………………………………………………… 233

Разновидности графов» ….. » …….. » ……. » ………. » …………….. » ……………………………………… «234

7.1.1. Граф дружеских отношений»»».».»»»».»»»»»»»»»».».»»»».»».»»»».»»»»»»»»»»237
7.2. Структуры данных для графов … » ………. » ……… » ………… » ………….. » …….. » ……… » …………. 240
7.3. История из жизни. Жертва закона Мура»».»».»»».»»»»»»»»»».»»»».».»»».»»»»»»».».»244

Оглавление

10

7.4. История из жизни. Создание графа»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».248
7.5. Обход графа » ……. » ….. » …. » … » ……. » ….. » ………. » …….. «».»».» … » … » ……… » ………. » ……… «.250
7.6. Обход в ширину ………. » ….. » ………….. » … «».» ……. » ………. «.»» … » ……….. » .. » … » .. «.»» ….. » .. 251
7.6.1. Применение обхода … » ….. » ……. «.»» ….. » ………. » …….. » … » ………….. «.» … «» … » .. «» … 254
7.6.2. Поиск путей .»» ….. » .. «.»»» … «»».»» … «.» .. » … «».» … » …….. » ……. » ….. «.».»» ……… » … 255
7.7. Применение обхода в ширину «»»»»»»»»»»»»»»»»»».»» ..»»»»»»»»»»»»»»»»»»»»»»»».256
7.7.1. Компоненты связности ….. » ………… » …………… » ……………….. » .. » ……. » .. «».» ………… 256
7.7.2. Раскраска графов двумя цветами»»»»»»»»»»»»»»».,»»»»»»»»» .. «»»»»»»»»»»»»257
7.8. Обход в глубину …. » ….. » ……. » ………… » ….. » …………… » … » ………………. «» …. «.».» …. » ………. 259
7.9. Применение обхода в глубину.» ………. «»».» ……………….. » … » … » …………….. «.».».».»» .. » .. 263
7.9.1. Поиск циклов» .. » … » ………………………. «» …… » ……………….. » .. » …… » ….. «.» …. » … » …. 263
7.9.2. Шарниры графа .. » ………………….. » … «».»» …. » ….. » … «».»» … «»» …….. » .. » ………….. «.264
7.10. Обход в глубину ориентированных графов»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»:.268
7.10.1. Топологическая сортировка .»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».270
7.10.2. Сильно связные компоненты «»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»»»».272
Замечания к главе .. » .. » ……. «» … «» ….. » ….. «»».»».» … » ….. » … «» … «» … » …………. » .. «» … » …. «» … 274
7.11. Упражнения … » ……….. «» ….. » ….. » … » .. » … «» … » ….. » …….. «» … «».»» … «.».»» .. «» …… » …….. 275
Алгоритмы для эмуляции графов .» ….. » … «» … » …………….. » ………. » ……….. «»» ……. «» .. «275
Обход графов» ….. «» ….. «» ……. » ….. » …………….. «» … «» … » ….. » ………. «.» …… » .. » ………… «.276
Приложения …………. » ……………………. » ….. » ….. » ………… » … » ………………. «» …………………. 277
Разработка алгоритмов … » ….. » ………… «» ….. » ….. » …………… «»».» ………… «» ……. «»»» ….. 278
Ориентированные графы .. » ….. » ………….. » ….. » ………… » …………… » ….. » .. » … «» ……….. » … 280
Шарниры графа ………… » ……. » ….. » ……. «».» ………… » ….. » … «» … «» … » …………. » … «» …. «»281
Задачи, предлагаемые на собеседовании …. «» ….. » … » … «» ………… » …. «».» .. «».»»»» …… 282
LeetCode ……. » …………. » ……. «» ………. «»»».» ………… » ….. » … «» … » ….. «» .. «»» ….. » …… «» … 282
HackerRank .. » ……. » .. » … «» ………….. » …………………….. » ……………………………. » ….. «»» ……. 282
Задачи по программированиiо ………. » ….. «».»» ….. » … » ….. » ….. » ….. «.».»» … «»» ……. » ….. 282
Глава

8.1.

8.

Алгоритмы для работы со взвешенными графами ………………………… 283

Минимальные остовные деревья» … » …. «.»» ….. » ….. » ………. «» … » ….. » …. » … «.»»»»»» ……. 284

8.1.1. Алгоритм Прима «»»»»»»»».»»».»»»»»»»»»»»»»»»»»»»»»»»»».»»».»»»»»»»»»» 285
8.1.2. Алгоритм Крускала …………. «»».»»» ….. » … » ………… » …………….. » ……… «» ………… » .. 288
8.1.3. Структура данных непересекающихся множеств»»»»»»»»»»»»»»»»»»»»»»»»»»290
8.1.4. Разновидности остовных деревьев «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».293
8.2. История из жизни. И всё на свете — только сети»»».»»»»»»»»»»»»»»»»»»»»»»»»»»».295
8.3. Поиск кратчайшего пути ….. «» … «» … «» ….. » … «» .. » … «.»» ….. » … » …………… «.»»» … «»»»» .. 298
8.3 .1. Алгоритм Дейкстры .. » ………….. » ……. » ………… » … «»».»» … » ……….. » … «»» … «»»»»»299
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Кратчайший путь с учетом веса вершин»»»»»»»»»»»».302

8.3.2. Кратчайшие пути между всеми парами вершин»»»»»»»»»»»»».»»»»»»»»»»»»».302
8.3.3. Транзитивное замыкание.» ……. » …. «.» ………. » … » ….. » ….. » ….. «» ……… «.»»»»»»»»»304
8.4. История из жизни. Печатаем с помощью номеронабирателя»»»»»»» .. «»»»»»»»»»»»».305
8.5. Потоки в сетях и паросочетание в двудольных графах»»»»»»»»»»»»»»»»»»»»»»»»»».309
8.5.1. Паросочетание в двудольном графе «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».31 О
8.5.2. Вычисление потоков в сети»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».311
8.6. Произвольный минимальный разрез …. » ……. » ….. » ………. » ….. » ………. «» .. «» … «»»» ….. » …. 315
8.7. Разрабатывайте не алгоритмы, а графы»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»316
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Нить Ариадны «»»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»317
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Упорядочивание последовательности» .. «»»»»»»»»»»».317

11

Оглавление

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Размещение прямоугольников по корзинам ………………. 318

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Конфликт имен файлов»»»»»»»»»»»»»»»»»»»»»»»»»».318
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Разделение текста»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»318
Замечания к главе

8.8.

……… » ……. » … » …………………….. » ….. » ……………………………….. » .. » …… » …….. 319

Упражнения …………. «»»»» …………………… » ….. » ……. » …………………………. » ………… «.»» …… 319

Алгоритмы для эмуляции графов «»»»»»»»»»»»»»»»»»»»»»»»»» «»»»»»»»»»»»»»»»».319
Минимальные остовные деревья «»»»»»»»»»»»»»»»»»»»»»»»»» «»»»»»»»»»»»»»»»»»319
Поиск-объединение ……… » …………………………. » ….. » ……………. » ….. » ….. » ………. «» …………. 321

…. » ….. » ………. » … » ……… » ….. » … «» ………….. » …….. » ……………….. 321
Потоки в сети и паросочетание»»»»»»»»»»».»»»»»»»»»»»»»»»»»»»» .. «»»»».»»»».»»»323
LeetCode …………… » … » ….. » ….. » ….. » ….. » … » ……. » ………… » ……. » ……… » ………. «» ….. » …… «.323
HackerRank ……………………………………………………………………………………………. » ……. » ……. 323
Задачи по программированию …………………………………… » … «» ….. » ………. » ……. » …………. 323
Поиск кратчайшего пути

Глава

9.

Комбинаторный поиск ……………………………………………………………………. 324

9.1. Перебор с возвратом «»»»»»»»»»»»»»»»»»»»»»»»»» «»»»»»»»,»»»»»»»»»»»»»»»»»» «.324
9.2. Примерц~ перебора с возвратом …. » …………………………………………………………………. «» ….. 327
9.2 .1. Генерирование всех подмножеств»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» 327
9.2.2. Генерирование всех перестановок ……………………………………………………… «» ……. «329
9.2.3. Генерирование всех путей в графе «»»»»»»»»»»»»»»»»»»»»»»»»» «»»»»»»»»»».330
9.3. Отсечение вариантов поиска …… » ….. » ………………… » ……………………. » ………………………… 332
9.4. Судоку ………. » ………………………. » …………………….. » ………………………… » ………. » ……………… 333
9.5. История из жизни. Покрытие шахматной доски»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»339
9.6. Поиск методом «лучший-первый» «»»»»»»»»».»»»».»»»».»».»»»» «»»»»».»»»»».»»»» .. 342
9.7. Эвристический алгоритм А*.»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»».»»»»»»»»»»»»».345
Замечания к главе …………………………………………………………………….. » ….. » ………. » ….. «» …. » …. 347
9.8. Упражнения ………….. » ………………………………………………………… » ………….. » ………. «.» …….. 347
Перестановки ……………………… » ………. » ………………. «.» ………….. » …………………… » ……. «.»»347
Перебор с возвратом …. » ………. » .. » ……………………………… » ……. «» …………… » .. » … «» ……. «348
Игры и головоломки …………… » …………………………………………………… » …………………… » … 349
Комбинаторная оптимизация ……………………….. » ………………… » …………………….. «» ………. 350
Задачи, предлагаемые на собеседовании .»»»»»».»».»»»».»»»».»»»».»»» «»»»»»»»»»».350
LeetCode ……………………………………………………………………………………………………………….. 351
HackerRank …….. » ………………. » ………. » ………. «» ………………. » ………………………………………. 35 l
Задачи по программированию …… » …………….. » ……………………………………………………….. 351
Глава

1О. Динамическое

программирование

……………………………………………….. 352

10.1. Кэширование и вычисления»»»»»»»»»»»»»»»»»»»»»».»»».»»».»»»»»»»»»»».»»»»»».353
10.1.1. Генерирование чисел Фибоначчи методом рекурсии «»»».:»»»»»»»»»»»»»»».353
10.1.2. Генерирование чисел Фибоначчи посредством кэширования»»»»»»»»»»»»»»354
10.1.3. Генерирование чисел Фибоначчи посредством динамического
программирования ………………………………. » …………… » …………………… » ………………………. 356
10.1.4. Биномиальные коэффициенты «»»»»»»»»»»»»»».»»»».»»»»»» «»»»»».»»»»»».358
10.2. Поиск приблизительно совпадающих строк «»»»»»»»»:»»»»»»»»»»»»»»»»» «»»»»»».360
10.2.1. Применение рекурсии для вычисления расстояния редактирования»»»»»»»».361
10.2.2. Применение динамического программирования для вычисления
расстояния редактирования ……………………….. » ……………………………………….. » …………… 362
10.2.3. Восстановление пути ………….. » ………………………………………………….. » ………….. «.364
10.2.4. Разновидности расстояния редактирования … » ….. » … » ………………………… «» …… 366

Оглавление

12

1О .3.
10.4.
10.5.
10.6.
10.7.
10.8.

Самая длинная возрастающая подпоследовательность …………………………………………… 3 70

10.9.

Ограничения динамического программирования: задача коммивояжера ………………… 387

История из жизни. Сжатие текста для штрихкодов ………………………………………………… 372
Неупорядоченное разбиение или сумма подмножества ………………………………………….. 376

История из жизни: Баланс мощностей …………………………………………………………………… 378
Задача упорядоченного разбиения

………………………………………………………………………… 381

Синтаксический разбор ………………………………………………………………………………………… 384
ОСТАНОВКА для РАЗМЫШЛЕНИЙ: Экономичный синтаксический разбор …………………… 386

10.9.1.
10.9.2.

Вопрос правильности алгоритмов динамического программирования …………. 388
Эффективность алгоритмов динамического программирования

………………….. 389
Prolog …………………….. 390
Замечания к главе ………………………………………………………………………………………………………… 393
10.11. Упражнения ……………………………………………………………………………………………………….. 394
Простые рекуррентные соотношения ………………………………………………………………….. 394
Расстояние редактирования

………………………………………………………………………………… 394
«Жадные» алгоритмы …………………………………………………………………………………………. 396
Числовые задачи ………………………………………………………………………………………………… 397
Задачи на графы ………………………………………………………………………………………………… 399
Задачи по разработке …………………………………………………………………………………………. 399
Задачи, предлагаемые на собеседовании ……………………………………………………………. .402
LeetCode ……………………………………………………………………………. » …………………………… .402
HackerRank ………………………………………………………………………………………………………… 402
Задачи по программированию …………………………………………………………………………… .402
10.1 О.

История из жизни. Динамическое программирование и язык

Глава

11.1.

11. NР-полнота ………………•…………………………………………………………………… 403

Сведение задач ……………………………………………………………………………………………………. .403

11 .1.1.
11.1.2.
11.2.

Ключевая идея ……………………………… :: ………………………………………………………… .404
Задачи разрешимости

……………………………………………………………………………….. .405

Сведение для создания новых алгоритмов ……………………………………………………………. .406

11.2.1. Поиск ближайшей пары …………………………………………………………………. :……….. .406
11.2.2. Максимальная возрастающая подпоследовательность ……………………………….. .407
11.2.3. Наименьшее общее кратное ………………………………………………………………………. .408
11.2.4. Выпуклая оболочка (*) ……………………………………………………………………………… .409
11.3. Простые примеры сведения сложных задач ……………………………………………… :…………. .41 О
11.3.1. Гамильтонов цищ…………………………………………………………………………………….. .410
11.3.2. Независимое множество и вершинное покрытие ……………………………………….. .412
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Сложность общей задачи календарного
планирования

…………………………………………………………………………………………… .413
…………………………………………………………………………………………… 415
11.4. Задача выполнимости булевых формул ………………………………………………………………… .416
11.4.1. Задача выполнимости в 3-конъюнктивной нормальной форме
(задача 3-SAT) ……………………………………………………………………………………………………… 417
11.5. Нестандартные сведения задачи SA Т ……………………………………………………………………. 419
11.5 .1. Вершинное покрытие ……………………………………………………………………………….. .419
11.5.2. Целочисленное программирование ……………………. » ……………………………………. .421
11.6. Искусство доказательства сложности ……………………………………………………………………. 423
11. 7. История из жизни. Наперегонки со временем ………………………………… «…………………… .425
11.8. История из жизни. Полный провал ………………………………………………………………………. .427
11.3 .3.

Задача о клике

13

Оглавление

NP …………………………………………………………………… 430
11.9.1. Верификация решения и поиск решения ……………………………………………………. .430
11.9.2. Классы сложности Р и NP …………………………………………………………………………. 431
11. 9 .3. Почему задача выполнимости является сложной? ……………………………………… .432
11.9.4. NР-сложность по сравнению с NР-полнотой ….~ …………………………………………. .432
Замечания к главе ………………………………………………………………………………………………………… 433
11.10. Упражнения ………………………………………………………………………………………………………. .434
Преобразования и выполнимость ……………………………………………………………………….. 434
Базовые сведения ……………………………………………………………………………………………… .435
Нестандартные сведения …………………………………………………………………………………… .437
Алгоритмы для решения частных случаев задач …………………………………………………. .438
Р или NP? …………………………………………………………………………………………………………. .439
LeetCode ……………………………………………………………………………………………………………. 439
HackerRank ……………………………………………………………………………………………………….. .439
Задачи по программированию ……………………………………………………………………………. 439
11.9.

Сравнение классов сложности Р и

Глава

12.1.
12.2.

12. Решение

………………………………………………………………………….. .440

Аппроксимация верщинного покрытия ………………………………………………………………… .441

12.2.1.
12.3.

сложных задач ………………………………………………………………… 440

Аппроксимирующие алгоритмы

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Вершинное покрытие в остатке …………………………….. .443
Рандомизированный эвристический алгоритм вершинного покрытия ………… .444

Задача коммивояжера в евклидовом пространстве

12.3 .1.

……………………………………………….. .445

Эвристический алгоритм Кристофидеса ……………………………………………………. .446

……………………………………………………………………… .448
k-SAT ……………………………………………………………………… .448
Максимальный бесконтурный подграф ……………………………………………………… .449
12.5. Задача о покрытии множества ……………………………………………………………………………… .449
12.6. Эвристические методы поиска …………………………………………………………………………….. .451
12.6.1. Произвольная выборка ……………………………………………………………………………… .452
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Выбор пары …………………………………………………………. .454
12.6.2. Локальный поиск ………………………………………………………………………………………. 455
12.6.3. Имитация отжига ……………………………………………………………………………………… .459
Реализация ………………………………………………………………………………………………… 462
12.6.4. Применение метода имитации отжига ………………………….. :………………………….. .463
Задача максимального разреза ………………………………………………………………….. .463
Независимое множество ……………………………………………………………………………. 463
Размещение компонентов на печатной плате …………………………………………….. .464
12. 7. История из жизни. Только это не радио ……………………………………………………………….. .465
12.8. История из жизни. Отжиг массивов ……………………………………………………………………… .468
12.9. Генетические алгоритмы и другие эвристические методы поиска …………………………. .4 71
12.9.1. Генетические алгоритмы …………………………………………………………………………… .471
12.10. Квантовые вычисления ……………………………………………………………………………………… .472
12.10.1. Свойства «квантовых» компьютеров ……………………………………………………….. .4 73
12.10.2. Алгоритм Гровера для поиска в базе данных ……………………………………………. .475
Решение задачи о выполнимости …………………………………………………………….. .475
12.10.3. Более быстрое «преобразование Фурье» ………………………………………………….. .476
12.10.4. Алгоритм Шора для разложения целых чисел на множители ……………………. .4 77
12.10.5. Перспективы квантовых вычислений ……………………………………………………….. 479
Замечания к главе ………………………………………………………………………………………………………… 481

12.4.

Когда среднее достаточно хорошее

12.4.1.
12.4.2.

Задача максимальной

Оглавление

14

12.11.

Упражнения ……………………………………………………………………………………………………….. 482
Частные случаи сложных задач

…………………………………………………………………………. .482
………………………………………………………………………… .482
483
………………………………………………………………………………
оптимизация
Комбинаторная
«Квантовые» вычисления ………………………………………………………………………………….. .484
LeetCode …………………………………………………………………………………………………………… .484
HackerRank ……………………………………………………………………………………………………….. .484
Задачи по программированию ……………………………………………………………………………. 484

Аппроксимирующие алгоритмы

Глава

13 .1.
13.2.

13. Как

Подготовка к собеседованию в технологических компаниях …………………………………. .489

ЧАСТЬ
Глава

14.1.

11. КАТАЛОГ

14. Описание

АЛГОРИТМИЧЕСКИХ ЗАДАЧ

…………………… «……….. 493

каталога ………………………………………………………………………… 495

Предостережения …………………………………………………………………………………………………. 496

Глава

15.1.
15 .2.
15.3.
15.4.
15.5.
15.6.

разрабатывать алгQритмы? ………………………… » ………….•………….. 485

Список вопросов для разработчика алгоритмов ……………………………………………………. .486

15.

Структуры данных ………. «…………………………………………. «…. «…………… 497

Словари ………………………………………………………………………………………………………………. .497
Очереди с приоритетам и ………………………………………………………………………………………. 503

Суффиксные деревья и массивы

…………………………………………………………………………… 507
Графы ………………………………………………………………………………………………………………….. 511
Множества …………………………………………………………………………………………………………… 515
Кd-деревья …………………………………………………………………………………………………………… 519

Глава

16. Численные задачи … «»»»» …. «……. «»» .. «»».»»»»……….. «.»….. «….. «…… 525

16.1. Решение системы линейных уравнений ………………………………………………………………… 526
16.2. Уменьшение ширины ленты матрицы …………………………………………………………………… 530
16.3. Умножение матриц ………………………………………………………………………………………………. 532
16.4. Определители и перманенты ………………………………………………………………………………… 535
16.5. Условная и безусловная оптимизация …………………………………………………………………… 538
16.6. Линейное программирование ……………………………………………………………………………….. 542
16.7. Генерирование случайных чисел …………………………………………………………………………… 547
16.8. Разложение на множители и проверка чисел на простоту ………………………………………. 552
16.9. Арифметика произвольной точности …………………………………………………………………….. 555
16.10. Задача о рюкзаке ………………………………………………………………………………………………… 560
16.11. Дискретное преобразование Фурье ……………………………………………………………………… 565
Глава

17. Комбинаторные задачи» … «»».»».»»».»»… «.»»»»………….. «… «» ….. «…. 569

17.1. Сортировка ………………………………………………………………………………………………………….. 570
17 .2. Поиск ………………………………………………………………………………………………………………….. 575
17.3. Поиск медианы и выбор элементов ………………………………………………………………………. 579
17.4. Генерирование перестановок ………………………………………………………………………………… 582
17.5. Генерирование подмножеств ………………………………………………………………………………… 587
17 .6. Генерирование разбиений …………………………………………………………………………………….. 590
17.7. Генерирование графов ………………………………………………………………………………………….. 595
17.8. Календарные вычисления …………………………………………………………………………………….. 599
17.9. Календарное планирование …………………………………………………………………………………… 601
17.10. Выполнимость ……………………………………………………………………………………………………. 605

15

Оглавление

Глава

18. Задачи

на графах с полиномиальным временем исполнения ………. 609

18.1. Компоненты связности …………………………………………………………………………………………. 61 О
18.2. Топологическая сортировка ………………………………………………………………………………….. 613
18.3. Минимальные остовные деревья …………………………………………………………………………… 617
18.4. Поиск кратчайшего пути ………………………………………………………………………………………. 622
18.5. Транзитивное замыкание и транзитивная редукция ……………………………………………….. 627
18.6. Паросочетание …………………………………………………………………………………………………….. 630
18.7. Задача поиска эйлерова цикла и задача китайского почтальона ……………………………… 634
18.8. Реберная и вершинная связность …………………………………………………………………………… 637
18.9. Потоки в сети ………………………………………………………………………………………………………. 641
18.10. Рисование графов ………………………………………………………………………………………………. 644
18.11. Рисование деревьев ……………………………………………………………………………………………. 649
18.12. Планарность ………………………………………………………………………………………………………. 652
Глава

19.

NР-сложные задачи на графах ………………………………………………………. 655

19.1. Задача о клике ……………………………………………………………………………………………………… 655
19.2. Независимое множество ………………………………………………………………………………………. 658
19.3. Вершинное покрытие …………………………………………………………………………………………… 661
19.4. Задача коммивояжера …………………………………………………………………………………………… 664
19.5. Гамильтонов цикл ………………………………………………………………………………………………… 668
19.6. Разбиение графов …………………………………………………………………………………………………. 672
19.7. Вершинная раскраска …………………………………………………………………………………………… 675
19.8. Реберная раскраска ………………………………………………………………………………………………. 679
19.9. Изоморфизм графов …………………………………………………………………………………………….. 680
19.1 О. Дерево Штейнера ……………………………………………………………………………………………….. 685
19.11. Разрывающее множество ребер или вершин ………………………………………………………… 689
Глава

20.

Вычислительная геометрия ………………………………………………………….. 693

20.1. Элементарные задачи вычислительной геометрии …………………………………………………. 693
20.2. Выпуклая оболочка ………………………………………………………………………………………………. 698
20.3. Триангуляция ………………………………………………………………………………………………………. 702
20.4. Диаграммы Вороного …………………………………………………………………………………………… 706
20.5. Поиск ближайшей точки ………………………………………………………………………………………. 709
20.6. Поиск в области …………………………………………………………………………………………………… 713
20. 7. Местоположение точки ………………………………………………………………………………………… 716
20.8. Выявление пересечений ……………………………………………………………………………………….. 720
20.9. Разложение по контейнерам …………………………………………………………………………………. 724
20.1 О. Преобразование к срединной оси …………………………………………………….. :………………… 728
20.11. Разбиение многоугольника на части ……………………………………………………………………. 731
20.12. Упрощение многоугольников ……………………………………………………………………………… 734
20.13. Выявление сходства фигур …………………………………………………………………………………. 73 7
20.14. Планирование перемещений ……………………………………………………………………………….. 740
20.15. Конфигурации прямых ……………………………………………………………………………………….. 744
20.16. Сумма Минковского …………………………………………………………………………………………… 747
Глава

21.1.
21.2.
21.3.

21. Множества

и строки

……………………………………………………………………… 750

Поиск покрытия множества ………………………………………………………………………………….. 750
Задача укладки множества ……………………………………………………………………………………. 755
Сравнение строк

………………………………………………………………………………………………….. 758

Оглавление

16

21.4.
21.5.
21.6.
21. 7.
21.8.
21.9.

Нечеткое сравнение строк»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» 761

Сжатие текста …………….. » ………………………. «» ………. » … » … «».» ………. » .. » … «» ….. » ………. 766

Криптография …………………. » … » ………….. » ………… » … » … » ….. «.» ….. » … » .. » … » ……………… 770

Минимизация конечного автомата.»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»» 776
Максимальная общая подстрока»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»». 779
Поиск минимальной общей надстроки «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»». 782

Глава

22.1.

22. Ресурсы ………………………………………………………………………………………….. 786

Программные системы …. » ………………………. » ………. «» …….. » ……. » … «.».».»».» … » ……. «. 786

Библиотека

LEDA «»»»»»»»»».»»»»»»»»»»»»»»»»».;»».»»»».»»»»»»».»»»».». 786
CGAL …………………………. » ….. » … » ….. «» … » … «» ……….. «.» … «.» .. » .. » 787
Библиотека Boost ………. » ……. «» … » ……. » … «» … » …….. «» ….. «» …… » … «.» … » ……. » 787
Библиотека Netlib .. » ……… » ………… » ………… «.»»» … » … » ….. » …… «»» … «.»».»» ….. 788
Коллекция алгоритмов ассоциации АСМ «».».»»»»»»»»»»».»»»»»»»»»»»»»». 788
Сайты GitHub и SourceForge «»»»»»»»»»».»».»»»»»»»»»»»»»»»»»»»»»»»»»». 788
Система Stanford GraphBase «»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» 789
Пакет ComЬinatorica …. » ….. » ….. » … «»» ….. «.»» … » … «» …….. » …. «»»» ……. «»» ……. 789
Программы из книг»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»». 789
Книга «Programming Challenges» …………….. » … » ….. » … » … » … » …. «» .. » ……. «»»»» 790
Книга «ComЬinatorial Algorithms for Computers and Cacuators» «»»»»»»».:»». 790
Книга «Computational Geometry in С»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»».»» 790
Книга «Algorithms in С++».» ………… » ………………….. » …….. » ……… » … » ……… «» …. 790
Книга «Discrete Optimization Algorithms in Pascal»»»»»»»»»»»»»»»»»»»»»»»» 791
22.2. Источники данных …….. » ….. » ……. » ….. » ………….. «».» .. » … » … » .. » … » ……… «.»» … «»»».» … 791
22.3. Библиографические ресурсы ……. «» ……. » ….. » ….. » ….. » ………………… » .. » … «» … «.» ………. 791
22.4. Профессиональные консалтинговые услуги «»»»»»»»»»»»»»»»»»»».»»»»»»»»»»»»». 792
22.1.1.
22.1.2.
22.1.3.
22.1.4.
22.1.5.
22.1.6.
22. 1. 7.
22.1.8.
22.1. 9.

Глава

23.

Библиотека

Список литературы ……………………………………………………………………….. 793

Приложение. Описание электронного архива ………………………………………………. 838
Предметный указатель

…………………………………………………………………………………. 839

Предисловие

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

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

как на студентов, так и на профессионалов. Книга состоит из двух частей. Часть

!

представляет собой общее введение в технические приемы разработки и анализа ком­
пьютерных алгоритмов. Часть

II

предназначена для использования в качестве спра­

вочного и познавательного материала и состоит из каталога алгоритмических ресурсов,

реализаций и обширного списка литературы.

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

книги «Алгоритмы. Руководство по разработке». За время с выхода в свет первого из­
дания книги в

1997

г. было продано свыше

60

тысяч ее экземпляров в различных фор­

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

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

2008

г. многое изменилось в этом мире. По мере того как компании по

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

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

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

1970

г., то получается, что около

20%

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

Поэтому настало время для нового издания книги, отражающего изменения, произо­
шедшие в алгоритмическом и промышленном мире, а также учитывающего отзывы и

18

Предисловие

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

+

в первой части книги («Практическая разработка алгоритмов») расширить такие
важные

предметы,

типа «разделяй

и

как

хеширование,

властвуй»,

рандомизированные

алгоритмы,

аппроксимирующие алгоритмы,

алгоритмы

а также добавить

предмет квантовых вычислений;

+

во второй части книги («Каталог алгоритмических задач») обновить справочный
материал для всех задач каталога;

+

в целом воспользоваться последними достижениями в области цветной печати, что-

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

+

Каталог алгоритмических задач. Не так-то просто узнать, что уже известно о стоя­
щей перед вами задаче. Именно поэтому в книге имеется каталог

75

наиболее важ­

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

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

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

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

GitHub,

которые прявились

после выхода предыдущего издания.

+

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

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

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

+

Онлайновый компонент. На моем веб-сайте

(www.algorist.com)

в полном объеме

представлены конспекты лекций, а также Wiki-энциклопедия решений задач.

Этот

веб-сайт был обновлен совместно с книгой. Мои видеолекции по алгоритмам на
У ouTube получили свыше

900 тысяч

просмотров.

Важно обозначить то, чего нет в этой книге. Я не уделяю большого внимания матема­
тическому обоснованию алгоритмов и в большинстве случаев ограничиваюсь нефор­
мальными рассуждениями. Не найдете вы в этой книге и ни одной теоремы. Более под­

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

Предисловие

19

Преподавателю
Эта книга содержит достаточно материала для курса «Введение в алгоритмы». Предпо­
лагается, что читатель обладает знаниями, полученными при изучении таких курсов,
как «Структуры данных» или «Теория вычислительных машин и систем».
На сайте

www.algorist.com

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

преподавания материала этой книги. Кроме того, я выложил аудио- и видеолекции
с использованием этих слайдов для преподавания полного курса продолжительностью

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

С целью облегчить использование книги в качестве учебника она была полностью
переработана с учетом следующих моментов:

+

Новый материш~.
Чтобы отразить последние достижения в области разработки алгоритмов, я добавил
новые главы по рандомизированным алгоритмам, алгоритмам типа «разделяй и вла­

ствуй» и аппроксимирующим алгоритмам. Я также более углубленно рассмотрел
такие темы, как хеширование. Но я также прислушался к читателям, которые умо­

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

+

10% превышало

объем предыдущего издания.

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

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

+

Больше ресурсов для собеседований.

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

и

Hackerrank.

LeetCode

Также добавлен новый раздел с советами, как лучше всего подгото­

виться к собеседованию при приеме на работу.

+

Остановки для размышлений.

Каждую лекцию своего курса я начинаю с «Задачи дня», в которой иллюстрирую
свой процесс мышления при решении домашнего задания по теме лекции со всеми

неудачными начальными попытками решения и всяким таким прочим. Это издание

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

+

Переработанные и новые домашние задания.
Это издание книги «Алгоритмы. Руководство по разработке» содержит больше и
лучшего качества упражнения для домашней работы, чем предыдущее. Я добавил

20

Предисловие

свыше сотни новых интересных задач, удалил некоторые менее интересные, а также

уточнил упражнения, которые сбивали с толку или были не совсем понятны.

+

Новый формат кода.
Во втором издании использовались реализации алгоритмов на языке С, которые
заменяли или дополняли описания на псевдокоде. Читатели в целом восприняли их
положительно,

хотя

кое-кто

и

подверг критике определенные аспекты

моего

про­

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

+

Цветные рисунки.

Моя другая книга

«The Data Science Design Manua» 1

была издана с цветными ри­

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

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

ПРИМЕЧАНИЕ ОТ РУССКОЙ РЕДАКЦИИ
Стремясь сделать эту книгу не слишком дорогой для российского читателя, будь она вы­
пущена полноцветной, мы избрали для ее издания компромиссный вариант: все рисунки
книги, а также листинги программ, содержащие выделения цветом, собраны в отдельные
файлы электронного архива, скачать который можно с FТР-сервера издательства «БХВ»
по ссылке

https://zip.bhv.ru/9785977567992.zip,

а также со страницы книги на сайте

https://Ьhv.ru/. Кроме того, цветные рисунки, содержащие цветовую информацию, важ­
ную для понимания материала книги, вынесены на цветную вклейку. Ссылки на такие

рисунки помечены в тексте книги префиксом «ЦВ»

например: Рис. ЦВ-1.1 (см. при­

ло.жение ).

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

наших двух детей, Бонни и Эбби, которые уже больше не дети. Мой отец отошел в мир

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

Я бы хотел поблагодарить нескольких людей за их непосредственный вклад в новое

издание. Майкл Алвин

Barker)

и Джек Женг

(Michael Alvin), Омар Амин (Omar Amin), Эмили Баркер (Emily
(Jack Zheng) сыграли неоценимую роль в создании инфраструкту­

ры сайта и при решении разных вопросов по подготовке рукописи. Для предыдущих

изданий этими

вопросами занимались Рикки Брэдли

(Ricky Bradley), Эндрю Гон
(Betson Thomas) и Дарио Влах
мире читатель Роберт Пиче (Robert Pich’e) из
студенты Питер Даффи (Peter Dufty), Олеся Елфимова

(Andrew Gaun), Жонг Ли (Zhong Li),
(Dario Vlah). Самый внимательный в
Университета города Тампере и

1

Бетсон Томас

В русском переводе: «Наука о данных. Учебный курс» (изд-во «Вильяме», 2020). —

Прим. ред.

Предисловие

(Olesia Elfimova)

21
и Роберт Матцибеккер

(Robert Matsibekker)

из Университета Стоуни

Брук вычитали ранние версии рукописи этого издания и убрали из него множество

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

Springer-Verlag

Уэйну Уиллеру

(Wayne Wheeler)

и Симону Ризу

(Simon Rees).
Многие упражнения были созданы по подсказке коллег или под влиянием других ра­
бот. Восстановление первоначальных источников годы спустя является задачей не из

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

Большую часть моих знаний об алгоритмах я получил, изучая их совместно с моими

аспирантами. Многие из них: Йо-Линг Лин (Yaw-Ling Lin), Сундарам Гопалакришнан
(Sundaram Gopalakrishnan), Тинг Чен (Тing Chen), Фрэнсин Иване (Francine Evans), Ха­
ральд Рау (Harald Rau), Рики Брэдли (Ricky Bradley) и Димитрис Маргаритис (Dimitris
Margaritis)- являются персонажами историй, приведенных в книге. Мне всегда было
приятно работать и общаться с моими друзьями и коллегами из Университета Стоуни

(Estie Arkin), Майклом Бэндером (Michael Bender), Джингом
(Jing Chen), Резаулом Чоудхури (Rezaul Chowdhury), Джи Гао (Jie Gao), Джо
Митчеллом (Joe Mitchell) и Робом Патро (Rob Patro).

Брук: Эсти Аркином
Ченом

Ограничение ответственности
Традиционно вину за любые недостатки в книге великодушно принимает на себя ее
автор. Я же делать этого не намерен. Любые ошибки, недостатки или проблемы в этой
книге являются виной кого-то другого, но я буду признателен, если вы поставите меня
в известность о них, с тем, чтобы я знал, кто виноват.
Стивен С. Скиена
Кафедра вычислительной техники
Университет Стоуни Брук

Стоуни Брук, Нью-Йорк 11794-2424

http://www.cs.sunysb.edu/-skiena
Август 2020 г.

ЧАСТЬI

Практическая

разработка алгоритмов

ГЛАВА

1

Введение

в разработку алгоритмов
Что такое алгоритм? Это процедура выполнения определенной задачи. Алгоритм явля­
ется основополагающей идеей любой компьютерной программы.

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

всего множества экзеJиnляров, которые должен обработать алгоритм, и выхода, т. е.
результата, получаемого после обработки одного из этих экземпляров. Описание одно­

го из экземпляров задачи может заметно отличаться от формулировки общей задачи.
Например, постановка задача сортировки делается таким образом:
Задача. Сортировка.

Вход. Последовательность из п элементов: а 1 , «» ап.
Выход. Перестановка элементов входной последовательности таким образом, что

для ее членов справедливо соотношение: а’ 1 ::::: а’ 2 ::::: «. ::::: а’п-I

:::::

а’п·

— например, {Mike, ВоЬ,
{ 154, 245, 568, 324, 654, 324 }. Первый

Экземпляром задачи сортировки может быть массив имен

Sally, Jil, Jan}

Или набор чисел

шаг к решению

например,

определить, что мы имеем дело с общей задачей, а не с частным слу­

чаем.

Алгоритм

это процедура, которая принимает любой из возможных входных экземп­

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

из таких алгоритмов можно привести

метод сортировки вставка-.щ.

Сортировка этим методом заключается во вставке в требуемом порядке элементов из
неотсортированной части списка в отсортированную часть, первоначально содержа­

щую один элемент (являющуюся, таким образом, тривиально созданным отсортиро­
ванным списком). На рис.

1.1

показано применение этого алгоритма для сортировки

определенного экземпляра- строки
А в листинге

1.1

INSERTIONSORT.

представлена его реализация на языке С.

void insertion_sort(item_type s[], int n)
/* Счетчики */
int i,j;
for (i=l; iO) && (s [j] < s [j-1])) {
swap ( &s [ j], &s [j-l] ) ;

Часть

26

/.

Практическая разработка алгоритмов

j-i;

Обратите внимание на универсальность этого алгоритма. Его можно применять как для

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

( п3

при условии, чтоj(п) ::=:

Из этих неравенств получа­

S c1g(n) и g(n) S c2h(n) при п > п 1 и п > п 2 соответственно.
ем:j(п) S c1g(n) S c1c2h(n) при п > nз = max(n1,n2). •

Оценка эффективности

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

меров

2.5.1.

возможно, даже более подробно, чем необходимо.

Сортировка методом выбора

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

отсортированной части массива. Процедура повторяется до тех пор, пока все элементы

массива не будут отсортированы. Графическая иллюстрация работы алгоритма пред­
ставлена на рис.

2.4,

а соответствующий код на языке С

в листинге

2.1.

f fhle(п). Таким образом, формула времени исполнения для наи-

худшего случая упрощается дальше до О(п

+ пт —

т

2

).

Еще два замечания. Обратите внимание, что п :=:; пт, поскольку для любой представ­
ляющей интерес строки поиска т ~

1.

Таким образом, п

+ пт =

Е>(пт), и мы можем

2

опустить дополняющее п, упростив формулу анализа до О(пт — т ).

Кроме того, заметьте, что член «-m 2» отрицательный, вследствие чего он только
уменьшает значение выражения внутри скобок. Так как О-большое задает верхнюю
границу, то любой отрицательный член можно удалить, не искажая оценку верхней

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

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

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

но если у вас недостаточно практики и вы не понимаете, почему

время исполнения этого алгоритма равно

O(j(n)),

то сначала распишите подробно алго­

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

Доказываем время исполнения по Е>-большое
Предоставленный анализ выдает квадратичное время верхнего предела времени ис­

полнения этого простого алгоритма сопоставления с образцом. Чтобы доказать обозна-

Часть

70

/.

Практическая разработка алгоритмов

чение 0-большое, нужно привести пример такого алгоритма, время исполнения кото­
рого действительно занимает время Q(тп). Рассмотрим, что происходит при поиске

образца подстроки р = аааа
в тексте

t=

аааа

.. . аааа,

аааЬ, состоящей из т

— 1 букв

а и буквы Ь в конце,

состоящем из п букв а.

При каждом размещении подстроки образца на строке текста цикл while успешно со­

поставит первЬ1е т

— 1 символов перед тем, как потерпеть неудачу с последним симво­
t в п — т + 1 возможных

лом. Подстроку образца р можно разместить в строке поиска

позициях, не выходя за конец этой строки, поэтому время исполнения будет
(п

т

+ l)(m) =

тп

т2

+т =

Q(mn).

Таким образом, этот алгоритм поиска строки имеет наихудший случай времени испол­
нения Е>(пт). Тем не менее существуют и более быстрые алгоритмы. В разд. 6.

7

мы

рассмотрим один из таких алгоритмов с линейным ожидаемым временем исполнения.

2.5.4.

Умножение матриц

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

женными операциями суммирования . Рассмотрим задачу умножения матриц:
Задача. Умножить матрицы.
Вход. Матрица А (размером х х у) и матрица В (размером у х

Выход. Матрица С размером х х
строки

i матрицы А

z,

где

C[i]U]

z).

является скалярным произведением

и столбцаj матрицы В.

Умножение матриц является одной из основных операций в линейной алгебре (пример
задачи на умножение матриц рассматривается в разд. 16.3). А в листинге
ся

пример реализации

2.4

приводит­

простого алгоритма для умножения матриц с использованием

трех вложенных циклов.

1

Листинг 2.4. Умножение матриц

for (i = 1; i rows; i++)
for (j = 1; j columns; j ++ ) {
c->m[i] [ j ] = О;
for (k = 1; k rows; k++)
c->m[i] [j] += a->m[i] [ k] * b->m[k] [ j] ;

Каким образом нам подойти к анализу временной сложности этого алгоритма? К этому

времени вы должны распознать, что три вложенных цикла означают О(п но давайте
будем точными. Количество операций умножения М(х, у, z) определяется такой фор­
мулой:

M(x,y, z)=

х

у

z

i= l

j =I

k=I

L L L1.

Глава

2.

Анализ алгоритмов

71

Суммирование выполняется справа налево. Сумма

z

единиц равна

z,

поэтому можно

написать:
х

M(x,y,z) =

у

L

Iz.

i=l

j=l
х

Сумма у членов z вычисляется так же просто. Она равнауz, тогда M(x,y,z) = LYZ.
i=l

Наконец, суммах членов

yz равна xyz.

Таким образом, время исполнения этого алгоритма для умножения матриц равно

O(xyz). В общем случае, когда все три измерения матриц одинаковы, это сводится
к О(п Этот же анализ применим и к нижней границе Q(п т. к. количество итераций
циклов зависит от размерности матриц. Простое умножение матриц оказывается куби­

ческим алгоритмом со временем исполнения, равным 0(п3). При всем этом существу­
ют и более быстрые алгоритмы, несколько примеров которых приводятся в разд. 16.3.

2.6.

Суммирование

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

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

привести такую формулу:
п

IЛi)=f(I)+ /(2)+ … + f(n).
/=l

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

I I =n.
/=l

Для четного п сумму первых

.i-e

и (п

— i+

n=2k

целых чисел можно выразить, объединив парами

1)-е целые числа следующим образом:
п

Li =
i=l

k

L:(i + (2k-i + 1)) = k(2k + 1) = п(п + 1)/2.
i:.l

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

Очень пригодится в области анализа алгоритмов умение распознавать два основных
класса формул суммирования:

Часть

72

+

Практическая разработка алгоритмов

/.

сумма степени целых чисел.

Арифметическую прогрессию в виде формулы
п

S(п) = ~> = п(п + 1)/2
i=l

можно встретить в анализе алгоритма сортировки методом выбора. По большому

счету важным фактом является наличие квадратичной суммы, а не то, что константа
равняется

1

/2.

В общем, для р ~О:
п

S(п, р)

= ~>Р = E>(np+I ).

Таким образом, сумма квадратов кубичная, а сумма кубов

«четверичная» (если

вы не против употребления такого слова).
Для р

< -1

сумма S(п, р) всегда стремится к константе,

когда п-щJ, а для р ~ О она

расходится. Между этими двумя случаями представляет интерес случай гармониче­
ских чисел:п

Н(п) = ~)/i = E>(logп).
i=l

+

сумма геометрической прогрессии.

В геометрических прогрессиях индекс цикла играет роль показателя степени, т. е.
п

G(n,a) = L:a’ = a(an+I -1)/(а -1).
i=O

Сумма прогрессии зависит от ее знаменателя, т. е. от числа а. При

lal <

1 сумма G(n,

а)

стремится к константе, когда п …—+ оо.

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

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

Например,

1 + 1/2 + ·1;4 + 1/8 + «.::; 2 независимо от количества элементов последо­

вательности.

При а
та

> 1 сумма стремительно возрастает при
1+2+4+8+16+32=63.

добавлении каждого нового элемен­

например:

В самом деле, для а> 1 G(n, а)= Е>(а» + 1).
ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Формулы факториала

ЗАДАЧА. Докажите методом индукции, что

L ;= i х i != (п + 1) !- 1.
1

РЕШЕНИЕ. Индукционная парадигма прямолинейна: сначала подтверждаем базовый
случай. При п

сумма будет пустая, что согласно определению равно О. Альтерна­

тивно, можно использовать п =

1:

1

Lixi!=l=(l+l)!-1=2-l=l.
i=I

Глава

2.

Анализ алгоритмов

73

Теперь допускаем, что это утверждение верно для всех чисел вплоть до п. Для доказа­

тельства общего случая п

+1

видим, что если мы вынесем наибольший член из-под

знака суммы:
п

п~

,Lixi!=(n+l)x(n+l)!+ ,Lixi!,
1=1

i=l

то получим левую часть нашего индуктивного допущения. Заменяя правую часть, по­
лучаем:

n+l

,Lixi!=(n+l)x(n+l)!+(n+l)!-1=
i=l

=(n+l)!x((n+l)+l)-1 =
=(п+2)!-1.

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

2″ 7.

Логарифмы и их применение

Слово «логарифм»

почти анаграмма слова «алгоритм». Но наш интерес к логариф­

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

нием

«log» —

единственное место, где вы сталкиваетесь с логарифмами в повседнев­

ной жизни. Также возможно, что вы уже не помните назначение этой кнопки. Лога­

рифм

это функция, обратная показательной. То есть выражение ьх =у эквивалентно

выражению х = ogьvY· Более того, из этой равнозначности следует, что Ь 108ьУ =у.
Показательные функции возрастают чрезвычайно быстро, что может засвидетельство­
вать любой, кто когда-либо выплачивал долг по кредиту. Соответственно, функции,
обратные

показательным,

т. е.

логарифмы,

возрастают довольно

медленно.

Лога­

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

2.7.1.

Логарифмt:.1 и двоичнt:.1й поиск

Двоичный поиск является хорошим примером алгоритма с временной логарифмиче­
ской сложностью

0(ogn).

Чтобы найти определенного человека по имени р в теле­

фонной книге 2 , содержащей п имен, мы сравниваем имя р с выбранным именем посе­
редине книги (т. е. с п/2-м именем)

скажем,

Monroe, Marilyn.

Независимо от того,

находится ли имя р перед выбранным именем (например, Dean, James) или после неrо
(например,

Presley, Elvis),

после этого сравнения мы можем отбросить половину всех

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

log2n.

Таким образом, чтобы найти любое имя

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

2

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

74

Часть

/.

Практическая разработка алгоритмов

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

найти определенного человека в неотсортированном телефонном справочнике.

2. 7.2. Логарифмы

и деревья

Двоичное дерево высотой в один уровень может иметь две концевые вершины (листа),
а дерево высотой в два уровня может иметь до четырех листов. Какова высота

h

дво­

ичного дерева, имеющего п листов? Обратите внимание, что количество листов удваи­

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

симость количества листов п от высоты дерева h выражается формулой п = 2h, откуда
следуе:г, что h = og2n.
Теперь перейдем к общему случаю. Рассмотрим деревья, которые имеют

d потомков
d = 2). Такое дерево высотой в один уровень может иметь
d количество листов, а дерево высотой в два уровня может иметь d2 количество листов.
Количество листов на каждом новом уровне можно получить, умножая на d количество
(для двоичных деревьев

листов предыдущего уровня. Таким образом, количество листов п выражается форму­

лой п = dh, т. е. высота находится по формуле h = o&fn (рис. 2.6).

о о о
о о 1
о
о


1 1

1 о о
1 о 1
1 1о

1 1 1
Рис. 2.6. Дерево высотой h и с количеством потомков d для каждого узла имеет dh листьев.
В нашем случае h = 3, d = 3 (слева). Количество комбинаций битов (справа)
растет экспоненциально с увеличением длины комбинации. Эти комбинации можно описать

как пуrи от корня к листу в двоичн0Jv1 дереве высотой

h= 3

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

вья лежат в основе всех быстро обрабатываемых структур данных ..

2. 7.3.

Логарифмы и биты

Предположим, имеются две однобитовые комбинации (О и
комбинации

(00, 01 , 10

и

11)

1),

четыре двухбитовые

и восемь трехбитовых комбинаций (см. рис.

2.6,

справа).

Сколько битов w потребуется, чтобы представить любую из п возможных разных ком­
бинаций, будь то один из п элементов или одно из целых чисел от О доп

— 1?

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

w.

Так как количество разных бито­

вых комбинаций удваивается с добавлением каждого бита, то нам ‘:’УЖНО по крайней

мере w битов, где

2w = п,

т. е. нам нужно w

= log2 п битов.

Глава

75

2. Анализ алгоритмов

2.7.4.

Логарифмы и умножение

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

особенно для возведения в степень. Вспомните, что

log,, (.ху) = log,, (х) + log,, (у),

т. е.

логарифм произведения равен сумме логарифмов сомножителей. Прямым следствием
этого является формула

log 0 пь

Выясним, как вычислить

i

= b·log

0

n.

для любых а и Ь, используя функции ехр(х) и ln(x) на кар­
= ех и ln(x) = log., (х). Таким образом,

манном калькуляторе, зная, что ехр(х)

аь

= exp(ln(aь)) = exp(Ьlna).

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

из этих функций.

2.7.5. Быстрое возведение в степень
Допустим, что нам нужно вычислить точное значение ап для достаточно большого
значения п. Такие задачи в основном возникают в криптографии при проверке числа на
простоту (см. разд.

16.8).

Проблемы с точностью не позволяют нам воспользоваться

ранее рассмотренной формулой возведения в степень.
Самый простой алгоритм выполняет п

можно

указать

п = Lп 12

лучший

J+ Г п / 2 l .

ап = a(aln

12

способ

— 1

решения

операций умножения (ах ах

этой

Если п четное, тогда ап =

задачи,

( ап 12 ) 2 •

во

приняв

х а). Но

внимание,

что

А если п нечетное, то тогда

J)2. В любом случае значение показателя степени было уменьшено наполо­

вину, а вычисление сведено самое большее к двум операциям умножения. Таким обра­

зом, для вычисления конечного значения будет достаточно

O(lgn)

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

операций умноже­

2.5.

function power(a, n)
if (n = 0) return(l)
х

= power (а, Ln/2 j)

if (n is even) then return (х 2 )
else return (а х х 2 )
Этот простой алгоритм иллюстрирует важный принцип: разделяй и властвуй. Разделе­
ние задачи на (по возможности) равные подзадачи всегда окупается. Когда значение п
отлично от

2,

то входные данные не всегда можно разделить точно пополам, но разни­

ца в один элемент между двумя половинами, как показано здесь, не вызовет серьезного

нарушения баланса.

76

Часть

2.7.6.

/.

Практическая разработка алгоритмов

Логарифмы и сложение

Гармоническое число представляет собой особый случай суммы степени целых чисел,

а именно Н(п) =

S(n, -1). Это сумма обратных величин первых п последовательных

чисел натурального ряда:

Н(п)

= ~)! i = открыл асимптотический рост. Ника­

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

столкнулся с проблемами, как только число п стало достаточно большим.

Нам нужна более быстрая программа, чтобы дойти до одного миллиарда. Можете ли

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

Поскольку я люблю такой тип задач
работы программ,

разработку алгоритмов для ускорения времени

я согласился подумать над этим и принялся за работу.

Я начал с просмотра программы, написанной моим посетителем. Он создал массив

всех 0(п 113 ) пирамидальных чисел от 1 доп включительно 4 • Для каждого числа k в этом

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

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

45% целых

чисел можно выразить

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

55%

чисел можно

представить в виде суммы четырех пирамцдальных чисел и, как правило, разными спо­

собами. Известно только

241

целое число, представляемое суммой пяти пирамидаль­

ных чисел, самое большое из которых равно

343 867.

Для примерно половины из п чи­

сел этот алгоритм проверял все трехэлементные комбинации и, по крайней мере, неко­
торые четырехэлементные. Таким образом, общее время исполнения этого алгоритма
было как минимум О(п х (п 113 ) 3 ) = О(п 2 ), где п = 1 ООО ООО ООО. Неудивительно, что на
больших числах (превышающих

100 ООО)

эта программа замедляла работу.

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

k

нам нужно наименьшее множество пирамидальных чисел, сумма которых

в точности равна

4

k.

Эта задача называется задачей о. рюкзаке и рассматривается

Почему п 113 ? Вспомните, что пирамидальные числа выражаются формулой (т 3 — т)/6. Наибольшее число

т, при котором результат не превышает п, приблизительно равно ~’ поэтому количество таких чисел
выражается формулой 0(п 113 ).

Глава

2.

в разд.
сел,

Анализ алгоритмов

16.1 О.

В нашем случае весу предметов соответствует набор пирамидальных чи­

не превышающих п,

ровно

81

k предметов

с тем дополнительным

ограничением, что рюкзак вмещает

(т. е. чисел, в нашем случае).

В стандартном подходе к решению задачи о рюкзаке заранее вычисляются суммы

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

k

в виде суммы трех чисел, то можем перефразировать по­

становку задачи и спросить, можно ли выразить число

в в~де суммы одного числа

k

и одной из сумм в нашей таблице.
Поэтому мне нужно было составить таблицу всех целых чисел

меньших чем п,

которые можно выразить в виде суммы двух из

1816 нетривиальных пирамидальных
чисел, меньших чем 1 ООО ООО ООО. Таких чисел может быть самое большее 1816 2 =
= 3 297 856. Более того, если мы уберем повторяющиеся суммы и суммы, большие чем
целевое число, у нас останется меньше чем половина чисел. Создание отсортированно­
го массива этих чисел не составит никакого труда. Назовем эту отсортированную

структуру данных таблицей сумм двух пирамидш~ьных чисел (two-taЬe).
Поиск минимального разложения заданного числа
ли оно одним из

1816

k

начинается с проверки, является

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

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

было всего лишь проверить, что
чисел при

1 S i S 1816.

k в виде суммы трех пирамидальных чисел, нужно
k — p[i] находится в таблице сумм двух пирамидальных

Эту проверку можно было быстро выполнить посредством дво­

ичного поиска. Чтобы проверить, можно ли выразить число

k

в виде суммы четырех

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

находится

в таблице сумм двух пирамидальных чисел для любого

почти лю­

бое число

k

k — two[i]
1 S i S itwoj. Но т. к.

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

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

оценивается как О(п

k

суммой трех пирамидальных чисел,

1/3

4/3

lgn), а для всего множества п целых чисел — как О(п lgn). По
сравнению с временной сложностью О(п 2 ) алгоритма клиента для п = 1 ООО ООО ООО,
мой алгоритм был в 30 ООО раз быстрее.
Первый прогон реализации этого алгоритма на моем далеко не новом компьютере

Sparc ELC

занял около

20

минут для п =

1 ООО

ООО. После этого я экспериментировал

с представлением наборов чисел с разными структурами данных и с разными алгорит­

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

с

разновидностями

(см. разд.

17.2).

двоичного

поиска,

такими

как

интерполяционный

поиск

Наградой за эту работу стало менее чем трехминутное время исполне­

ния программы на множестве чисел п =

1 ООО

ООО, что в шесть раз лучше времени ис­

полнения первоначальной программы.

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

для любого числа

k,

когда

k — 1 было

1-

это пирамидальное число, то

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

82

Часть

/.

Практическая разработка алгоритмов

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

ма общее время исполнения программы было сокращено еще на

Наконец, с по­

10%.

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

10% с

времени исполнения.

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

5.8.

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

1, 113

секунды. Включив опти­

мизатор компилятора, удалось сократить этот показатель до всего лишь

секунды.

0,334

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

25

лет

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

( 17 4

минуты и

28,4 секунды), работая лишь в однопоточном режиме. Более того, этот код исполняется
за 9 часов, 37 минут и 34,8 секунды на убогом ноутбуке MacBook Apple, клавиши ко­
торого выпадают, когда я набираю на нем материал этой книги.

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

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

30

тысяч раз. Суперкомпьютер мое­

го заказчика, стоивший миллион долларов, был оснащен (в то время)

16 процессорами,

каждый из которых мог выполнять целочисленные вычисления в пять раз быстрее, чем
мой настольный КО\Шьютер стоимостью в

3000 долларов.

Но при использовании всей

этой техники скорость выполнения моей программы возросла менее чем в

100 раз.

Очевидно, что в рассматриваемом случае применение более эффективного алгоритма
имеет преимущество над использованием более мощного оборудования, что справед­

ливо для любой задачи с достаточно большим вводом.

2.10.

Анализ высшего уровня(*)

В идеальном случае мы все умели бы свободно обращаться с математическими мето­

дами асимптотического анализа. Точно так же в идеале мы все были бы богатыми
и красивыми. А поскольку жизнь далека от идеала, математические методы асимпто­
тического анализа требуют определенных знаний и практики.

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

он не использу­

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

поспособствует пониманию некоторых функций временной сложности, рассматривае­
мых во второй ее части.

Глава

2.

Анализ алгоритмов

83

2.10.1. Малораспространенные функции
Основные классы функций временной сложности алгоритмов были представлены
в разд.

2. 3.1.

Но в расширенном анализе алгоритмов также возникают и менее распро­

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

из таких функций и их краткое описание.

+

Обратная функция Аккермана: .fin) = а(п).
Эта функция появляется в подробном анализе нескольких алгоритмов, в особенно­
сти в подробном анализе структуры данных объединение
мой в разд.

8.1.3.

поиск, рассматривае­

Будет достаточно воспринимать эту функцию как технический

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

чие от функции-константы, .fin)

= 1, а(п) достигает бесконечности

при

n—+oo,

но она

определенно не торопится сделать это. Для любого значения п в физической все­
ленной значение а(п) будет меньше

5,

т. е. а(п)

< 5.

+ f(n) = loglogn.
Смысл функции «log log» очевиден по ее имени —

это логарифм логарифма числа п.

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

lg п

элементов.

+ f(n)=logn/loglogn.
Эта функция возрастает немного медленнее, чем функция

log п,

т. к. она содержит

в знаменателе еще более медленно растущую функцию.

Чтобы понять, как возникла эта функция, рассмотрим корневое дерево с количест­
вом потомков

d,

имеющее п листьев. Высота двоичных корневых деревьев, т. е.

деревьев, у которых

d = 2,

определяется следующей формулой:

п = 2h ~ h = lg п,
получающейся в результате логарифмирования обеих частей равенства. Рассмотрим

d = log п.
(logn)—+h = logn/log logn.

теперь высоту дерева, когда количество потомков равно
определяется такой формулой: п =

Тогда высота

+ f(n)=log 2 n.
Это произведение двух логарифмических функций, т. е.

(logn)

х

(logn).

Такая

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

1 до, например, п. Для представления каждого из этих
целых чисел требуется lg(n 2) = 2 lg п битов, а поскольку количество чисел в множе­
стве поиска равно lg п, то общее количество битов будет равно 2 lg 2 п.
целым числом в диапазоне от

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

Часть

84

+

/. Практическая разработка алгоритмов

f(n)=.Jn.
Функция квадратного корня встречается не так уж редко, но представляет класс

«сублинейных полиномов», поскольку .Jn = п 112 • Такие функции возникают при по­
строении d-мерных

сеток,

содержащих

п

точек.

п 113

Площадь

квадрата размером

.Jn х .Jn равна п, а объем куба размером
х
х
также составляет п. В об­
щем, объем d-мерного гиперкуба со стороной n 11d составляет п.

+

п 113

п 113

f(n) = n(I+, и мы можем выбирать любое значение для с. При с= 2
время исполнения будет 4п 312 , или О(п 312 ). При с= 3 время исполнения будет 8п 413 ,
или О(п 413 ), что уже лучше. Действительно, чем больше значение с, тем лучше ста­
новится показатель степени.

Но с нельзя сделать как угодно большим, до того как член 2с станет доминировать.

Вместо этого мы обозначаем время исполнения этого алгоритма как О(п 1 + е) и пре­
доставляем пользователю определить наилучшее значение для Е.

2.10.2.

Пределы и отношения доминирования

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

g(n),

.fln)

доминирует над

если limп_,oog(n)ij(n) =О.

Рассмотрим это определение в действии. Допустим, что .fln) = 2п 2 и g(n) = п 2 • Очевид­
но, что.flп) > g(n) для всех п, но не доминирует над ней, т. к.

lim g(n)

Х~

j(n)

=lim~=lim_!_:;t:O.
Х~ 2п2

Х~

2

Этого следовало ожидать, т. к. обе функции принадлежат к одному и тому же классу
0(п 2 ). Теперь рассмотрим функции.flп) п 3 и g(n) п2 • Так как

=

=

lim g(n) = lim~= lim_!_=O
j (n) x—too n 3 x—too n

x—too

то доминирует многочлен более высокого уровня. Это справедливо для любых двух

многочленов, т. е. па доминирует над пь, если а> Ь, т. к.
ь

lim !!_ = lim пь-а ~О.
X—tct:

па

X—t::t’)

Таким образом, п 1 ,2 доминирует над п 1 • 1999999 .

Глава

2.

Анализ алгоритмов

85

Перейдем к показательным функциям:f{п)

= 3n и g(n) = 2п.

Так как

lim g(n) = lim!_ = lim(3-)» =О

х->»‘

j (n)

х->оо З»

х->оо

3

значит, доминирует функция с большим основанием.
Возможность доказать отношение доминирования зависит от возможности доказать

пределы. Давайте рассмотрим одну важную пару функций. Любой многочлен (скажем,

j(n) = пЕ) доминирует над логарифмическими функциями (например, g(n)
как п = ign то

= lgn).

Так

Теперь рассмотрим следующее тождество:

lim g(n)
f(n)

= lgn / 2′ 1g».

Х->>:

В действительности при

n—>oo

оно стремится к нулю.

ПОДВЕДЕНИЕ итогов
Рассматривая совместно функции, приведенные в этом разделе, и функции, обсуждаемые
в разд.

2. 3.1,

мы видим полную картину порядка доминирования функций:

п! » с»» п 3 » п 2 » п 1 + < » п log п » п » J;; » log2n » log п » log n/log log п » »
» » log log п » а(п) » 1.

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

рассматривается на более глубоком уровне в книгах
В книге

[GKP89]

[CLRS09)

дается интересное и всестороннее изложение математического аппа­

рата для анализа алгоритмов. В книге

[NZM9 l]

дается мое любимое введение в теорию

чисел, включая проблему Уоринга, упомянутую в разд.

2. 8.

Понятие доминирования также порождает обозначение

что

j(n)

= o(g(n))

и [КТОб).

тогда и только тогда, когда

g(n)

«Little Oh»

(о-малое). Говорят,

доминирует над f{n). Среди прочего

обозначение о-малое полезно для формулировки задач. Требование представить алго­
ритм с временной сложностью о(п 2 ) означает, что нам нужен алгоритм с функцией
временной сложности лучшей, чем квадратичная, и что будет приемлемой временная
сложность, выражаемая функцией O(n 1•999 Jog 2n).

Часть

86

2.11.

/.

Практическая разработка алгоритмов

Упражнения

Анализ программ

1. [3}

Какое значение возвращает следующая функция? Ответ должен быть в форме функ­

ции числа п. Найдите время исполнения в наихудшем случае, используя обозначение
О-большое.

Mystery(n)
r = О
for i = 1 to п — 1 do
for j = i + 1 to п do
for k = 1 to j do
r = r + 1
return ( r)

2. [3}

Какое значение возвращает следующая функция? Ответ должен быть в форме функ­

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

О-большое.

Pesky(n)
r = О
for i = 1 to n do
for j
1 to i do
for k
j to i + j do
r = r + 1
return(r)

3. [5]

Какое значение возвращает следующая функция? Ответ должен быть в форме функ­

ции числа п. Найдите время исполнения в наихудшем случае, используя обозначение
О-большое.

Pestiferous(n)
r

=

О

for i = 1
for j
for k
for
r
return(r)

4. [8]

to n do
1 to i do
j to i +
do
l
1 to i + j — k do
= r + 1

Какое значение возвращает следующая функция? Ответ должен быть в форме функ­

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

О-большое.

Conundrum(n)
r =

О

for i = 1
‘for j
for k
r =
return(r)

to n do
i + 1 to n do
= i + j — 1 to n do
r + 1

Глава

5. [5]

2.

87

Анализ алгоритмов

Рассмотрим следующий алгоритм: (команда

print отображает одИн символ звездоч­

ки(*), а командах =2х удваивает значение переменной х):

for k = 1 to n:
х = k
while (х < n) :
print ‘*’
х

=

Пустьj{п) представляет временную сложность этого алгоритма (или, равнозначно, коли­
чество раз вывода на печать символа
и П(ft..n)), в идеале сходящиеся на

*).
0(f(n)).

Предоставьте правильные границы для O(ft..n))

6. [5] Допустим, что для вычисления многочлена р(х)

=

а,,хп + ап- 1 хп

1

+ … + а 1 х + а0 ис­

пользуется следующий алгоритм:
р =

аО;

xpower = 1;
for i = 1 to п do
xpower =
р =

х

+ ai

р

· xpower;

*

xpower

Сколько операций умножения выполняется в наихудшем случае? А сколько операций
сложения?

7.

Сколько операций умножения выполняется в среднем?

Можно ли улучшить этот алгоритм?

[З] Докажите правильность следующего алгоритма для вычисления максимального зна­
чения в массивеА[l

.. п]:

max(A)
т

= A[l]

for i = 2 to п do
if A[i] >
return (m)

т

then

т

= A[i]

Упражнения по асимптотическим обозначениям
8.

[З] Верно или неверно?

а) 2n + 1 = 0(2п);

Ь) 2 2 п

9.

= 0(2п).

[З] Для каждой из следующих пар функций функция j{n) является членом одного из
множеств функций

O(g(n)), Q(g(n)) или 0{g{n)) Определите, членом какого множества

является функция в каждом случае, и вкратце обоснуйте свой вывод:

a)j{n) = logn 2 ; g(n) = ogn + 5;

Jn; g(n)

ogn2 ;

b)j{n)

=

c)j{n)

= log2 п; g(n) = ogn;

d)j{n)

=

e)j{n)

= п logn + п; g(n) = logn;

п; g(n)

=

=

og 2 п;

Часть

88
t)j(n)

= 10; g(n) = log!O;

g)j(n)

=

2п; g(n)

=

/.

Практическая разработка алгоритмов

10п 2 ;

h)j(n) = 2п; g(n) = 3п.
10.

{З} Для каждой из следующих пар функцийj(п) и

ражения:j(п)

g(n) определите, справедливы ли вы­

= O(g(n)) и g(n) = O(f(n)) или оба:

а)j(п)

= (п 2 — п)/2; g(n) = 6п;

b)j(n)

= п + 2 Jn; g(n) = п 2 ;

c)j(n)

= п log п; g(n) = п Jn 12;

d)j(n)

= п + logn; g(n) = Jn;

e)j(n)

= 2(logn) 2 ; g(n) = logn + 1;

t)j(n)

= 4п log п + п; g(n) = (п 2 — п)/2.

11. [5] Какие асимптотические
il(g(n)) или 8(g(n))?

границы справедливы для следующих функцийj(п):

O(g(n)),

a)j(n) = 3п 2 , g(n) = п 2 ;
b)j(n)

= 2п4

c)j(n)

= log п, g(n) = log п +

d)j(n) =
e)j(n)

2klogn,

3п2 + 7, g(n) = п5 ;
1/п;

g(n) = nk;

= 2п, g(n) = 2 2 п.

12. {З} Докажите, что п3 — 3п 2 — п + 1 = 8(п 3 ).
13. {З} Докажите, что п2

=

0(2п).

14. [З] Докажите или опровергните, что 8(п2 ) = 8(п2 + !).
15.

{З} Предположим, что имеется пять алгоритмов с указанными далее временами испол­
нения. (Пусть это точные времена исполнения.) Насколько медленнее будут эти алго­

ритмы исполняться, если (а) удвоить размер ввода или (Ь) увеличить размер ввода на
один элемент?

а) п2 ;
Ь) п 3 ;
с) 100п 2 ;

d)

п

log п;

е) 2п.

16.

{З} Предположим, что имеется шесть алгоритмов с указанными далее временами ис­

полнения. (Допустим, что это точное количество операций, выполняемых как функция

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

а) п 2 ;
Ь) п3 ;

Глава

2. Анализ алгоритмов

89

с) l00n 2 ;

d)

п

log п;

е) 2п (f) 2 2″.

17. [3]

Для каждой из следующих пар функций.f(п) и g(n) найдите положительную констан­

ту с, при которой.f(п)::; с · g(n) для всех п

> l:

a).f(n) = п2 + п + l, g(n) = 2п 3 ;
b)j(n) =
c)f(n)

18. [3]

п Гп + п2 , g(n) = п2 ;

= п 2 — п + l, g(n) = п2 /2.

Докажите, что если.fl(п)

19. [3] Докажите, что
= Щg1(п) + g1(n)).

= O(g1(n)) иfz(n) = O(gz(n)), тогда.fl(п) +h(n) = O(g1(n) + gz(n)).

если

= Щg 1 (п))

fi(n)

и

fz(n) = Щg2 (п)),

20. {3]

Докажите, что еслиfi(п)

= O(g1(n)) иfz(n) = O(g2(n)),

21. [5]

Докажите, что для всех

k?.

тогдаjj(п)

тогда

+fz(n) =

·fz(n) = O(g1(n) · g2(n)).

О и всех множеств вещественных констант

ао} верно: aknk + ak 1nk- 1+ … + а 1 п + а0

fi(n)

{щ,

ан,

«.,

а1,

= O(nk).

22. [5] Докажите, что для любых вещественных констант а и Ь, Ь >О верно (п + а)ь = 8(пь).
23. [5]

Упорядочите приведенные в таблице функции в возрастающем порядке. При нали­

чии двух или более функций одинакового порядка укажите их.

24. [8]

п

2n

ngn

lnn

п-п 3 + 7п5

gn

Гп

еп

п2 + gn

п2

2n-I

пз

(lg п)2

п!

lg lgn
п1

+ ‘,

где О< i::

(п

log п).

показано дерево решений для сортировки вставками трех элементов. Чтобы

понять его работу, рассмотрим сортировку вставками входного множества а =
Поскольку

al ::::

(3, 1, 2).

а2, чтобы упорядочить эти два элемента, их нужно поменять местами.

Затем алгоритм сравнивает последний элемент упорядоченного массива (исходное
входное значение

al)

со значением а3. Если

al ::::

а3, то в последнем сравнении значе­

ния аЗ с первым элементом отсортированной части (исходное входное значение а2)
решается, куда поместить значение а2 в упорядоченном списке

на первое или второе

место.

F

( l,2,3)

(l,3,2)
Рис.

4.9.

(2,3, l)

(3, 1,2)

(З,2,1)

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

входную перестановку, а путь от корня к концевому узлу описывает последовательность

сравнений, выполняемых алгоритмом для ее упорядочивания

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

ровки, включая определение уникальности элемента, поиск наиболее часто встречаю-

Часть

170

/.

Практическая разработка алгоритмов

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

цей. В главе

11

мы рассмотрим другой подход к доказательству маловероятности суще­

ствования быстрых алгоритмов.

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

может

не

повезти,

и

в

случае

наихудшего

случая

невезения

время

ис­

полнения любого рандомизированного алгоритма для одной из таких задач будет

.Q(n log п).

4.8.

История из жизни. Скиена в суде

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

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

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

доходчиво объяснять людям сложные понятия 6 • Участие в этом деле обещало стать
увлекательной возможностью узнать, как действительно работают быстрые програм­
мы сортировки. Я полагал, что смогу ответить на вопрос, какой из алгоритмов сорти­
ровки в памяти является самым быстрым. Будет это пирамидальная сортировка или

быстрая сортировка? Какие особенности алгоритмов способствовали сведению к ми­
нимуму количества сравнений в практических приложениях?

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

намного больших, чем те, что

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

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

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

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

ных для чтения/записи и координация этих операций, чтобы алгоритм сортировки ни­
когда не простаивал, ожидая данные.

6

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

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

Глава

4.

Сортировка и поиск

171

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

Minutesort.

Перед участни­

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

55 терабайт
из 512 узлов,

Tencent Sort,

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

20

ядрами и

512

Гбайт

оперативной памяти. Информацию о текущих рекордах сортировки можно получить на
веб-сайте

Sort Benchmark

по адресу:

http://sortbenchmark.org/.

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

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

верхнего блока каждого из

k

отсортированных списков. Последовательно снимая

с этой пирамиды верхний элемент и сливая вместе эти

k списков,

алгоритм создает об­

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

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

ных

k списков,

загружается следующий верхний блок данных из этого списка.

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

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

Minutesort

в качестве

входных данных для сортировки используются произвольно генерируемые записи раз­

мером в

100

байтов. Сортировка подобных записей существенно отличается от сорти­

ровки имен или целых чисел, поскольку имена

ры фамилии

Shifflett, —

например, множественные экземпля­

не распределены случайным образом. Например, при сорти­

ровке этих записей широко применяется прием, при котором от каждого элемента

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

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

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

нии душевного состояния, и обычно завершаются только тогда, когда обе стороны

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

тельности внешних средств хранения данных при обработке очень больших наборов

Часть

172

/.

Практическая разработка алгоритмов

данных алгоритмами с низкой временной сложностью (например, линейной или рав­

ной Е>(п

log п).

В подобных случаях даже такие постоянные множители, как

5

или

1О,

могут означать

разницу между возможностью и невозможностью сортировки.

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

Замечания к главе
В этой главе мы не рассмотрели несколько интересных алгоритмов сортировки, вклю­
чая ш~горитм сортировки методом Шелла

(shellsort),

представляющий собой более

эффективную версию алгоритма сортировки вставками, и ш~горитм поразрядной сор­
тировки

(radix sort),

являющийся эффективным алгоритмом для сортировки строк.

Узнать больше об этих и всех других алгоритмах сортировки можно в книге

[Knu98).

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

ком

большие объемы дополнительной

(Kronrod) для

памяти.

В

частности,

слияния в памяти рассматривается в книге

алгоритм

Рандомизированные алгоритмы рассматриваются более подробно в книгах

[MUI 7).

Кронрода

[Knu98].

Задача подбора болтов и гаек была впервые представлена в книге

[MR95) и
[Raw92].

Сложный, но детерминированный алгоритм для ее решения с временной сложностью

Е>(п

log п)

4.9.

рассматривается в книге

[KMS98].

Упражнения

Применение сортировки: сортировка чисел

1. [3]

Вам нужно разделить 2п игроков на две команды по п игроков в каждой. Каждому

игроку присвоен числовой рейтинг, определяющий его игровые способности. Нужно

разделить игроков наиболее несправедливым способом

т. е. создать самое большое

неравенство игровых способностей между командой А и командой В. Покажите, как

можно решить эту задачу за время О(п

2. [3]

log п).

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

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

S

= {б,

13, 19 ,3, 8}

а) Пусть

S-

разность

19-3

максимальна, а разность

8- 6 — минимальна.

неотсортированн_ый массив п целых чисел. Предоставьте алгоритм для

поиска пары элементов х, у Е

S

с наибольшей разностью

ритма в наихудшем случае должно быть равным О(п).

lx — yl.

Время исполнения

алго­

Глава

4.

Сортировка и поиск

Ь) Пусть

173

отсортированный массив п целых чисел. Предоставьте алгоритм для по­

S-

иска пары элементов х, у Е

S

с наибольшей разностью 1.х-

ритма в наихудшем случае должно быть·равным
с) Пусть

S-

yl.

неотсортированный массив п целых чисел. Предоставьте алгоритм для

поиска пары элементов х, у Е

S

с наименьшей разностью 1.х

— yl

ния алгоритма в наихудшем случае должно быть равным О(п

d)

Пусть

Время исполнения алго­

0(1).
для х

f. у.

Время исполне­

log п).

отсортированный массив п целых чисел. Предоставьте алгоритм для по­

S-

иска пары элементов х, у Е

наименьшей разностью 1.х

— yl

для х

f. у.

Время исполнения

алгоритма в наихудшем случае должно быть равным О(п).

3. [3}

Для входного списка из 2п действительных чисел разработайте алгоритм с временем

исполнения О(п

log

п), который разбивает эти числа на п пар таким образом, чтобы ми­

нимизировать максимальную сумму значений в парах. Например, рассмотрим множество

чисел

{1, 3, 5, 9}. Эти числа можно разбить на следующие наборы: ({1; 3},{5, 9}),
({1, 5},{3, 9}) и ({1, 9},{3, 5}). Суммы значений пар в этих наборах равны (4, 14), (6, 12)
и (1 О, 8). Таким образом, в третьем наборе максимальная сумма равна 1О, что является
минимумом для всех наборов.

4. [3]

Допустим, что у нас есть п пар элементов, где первый член пары является числом, а

второй

одним из трех цветов: красный, синий или желтый. Эти пары элементов отсор­

тированы по числу. Разработайте алгоритм с временем исполнения О(п) для сортировки
пар элементов по цвету (красный предшествует синему, а синий

желтому) таким об­

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

(1,

синий),

ный) после сортировки будет такой:

(6,
5. [3}

(3,

(3,

красный),

красный),

(9,

(4,

синий),

красный),

(6, желтый), (9, крас­
(1, синий), (4, синий),

желтый).

Модой набора чисел называют число с наибольшим количеством вхождений в набор.

Например, модой набора

{4, 6, 2, 4, 3, 1}

является число

4.

Разработайте эффективный

алгоритм поиска моды набора из п чисел.

6. [3}

Дано: два набора элементов:

с временем исполнения О(п

из набора

log

S 1 и S2

(оба размером п) и число

z.

Опишите алгоритм

п) для определения, существует ли пара элементов: один

а другой из набора Sъ сумма которых равна х. (Чтобы решение было зачте­

S 1,

но частично, алгоритм может иметь время исполнения 8(п 2 ).)

7. [5]

Разработайте эффективный алгоритм, принимающий в качестве ввода массив значе­

ний (неотрицательных целых чисел) количеств цитирования исследовательских работ
и вычисляющий h-индекс для каждого исследователя. Согласно определению ученый

имеет индекс

h,

если

h

из его/её п статей цитируются как минимум

время как оставшиеся (п —

8. [3}

h)

статей цитируются не более чем

h раз

h

раз каждая, в то

каждая.

Предложите краткое описание метода для решения каждой из следующих задач.

Укажите степень сложности в наихудшем случае для каждого из ваших методов.

а) Вам дали огромное количество телефонных счетов и столь же огромное количество

чеков по оплате этих счетов. Узнайте, кто не оплатил свой телефонный счет.
Ь) Вам дали распечатанный список всех книг школьной библиотеки, в котором указаны
название каждой книги, ее автор, идентификационный номер и издательство. Также вам
дали список

30

издательств. Узнайте, сколько книг в библиотеке были выпущены каж­

дым издательством.

Часть

174

/.

Практическая разработка алгоритмов

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

Имеется набор S, содержащий п целых чисел, и целое число Т. Разработайте алго­
ритм с временем исполнения O(nk 1log п) для определения, равна ли сумма k целых чи­

9. [5]

сел из

1О. [3]

S целому

числу Т.

Имеется набор

S,

содержащий п действительных чисел, и действительное число х.

Разработайте эффективный алгоритм для определения, содержит ли набор

S

два таких

элемента, сумма которых

равна х.

а) Допустим, что набор

S

не отсортирован. Разработайте алгоритм для решения задачи

S

отсортирован. Разработайте алгоритм для решения задачи за

за время О(п

log п).

Ь) Допустим, что набор
время О(п).

11. [8]

Разработайте алгоритм с временем исполнения О(п), позволяющий найти все эле­

менты, входящие больше чем п/2 раза в список из п элементов. Затем разработайте
алгоритм с временем исполнения О(п), позволяющий найти все элементы, входящие
в список из п элементов больше чем п/4 раза.

Применение сортировки: интервалы и множества.
12. [3]

Разработайте эффективный алгоритм для вычисления объединения множеств А и В,

где п

= max(IAl,IBI).

Выход должен быть представлен в виде массива отдельных элемен­

тов, образующцх объединение множеств.

а) Допустим, что А и В- неотсортированные массивы. Разработайте алгоритм для
решения задачи за время О(п

log п).

Ь) Допустим, что А и В- отсортированные массивы. Разработайте алгоритм для реше­
ния задачи за время О(п).

13. [5]

Камера над дверью отслеживает врем~ входа а; и время выхода Ь; (предполагается,

что Ь;

>

а;) каждого из п участников вечеринки. Разработайте алгоритм с временной

сложностью О(п

log

п), который анализирует эти данные, чтобы определить период

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

14. [5]

Имеется список/, содержащий п интервалов в виде пар (х;, у,). Разработайте алго­

ритм с временной сложностью О(п

log

п) в наихудшем случае, возвращающий список,

в котором перекрывающиеся интервалы объединяются в один. Например, для входного
множества/=

15. [5]

{(1, 3), (2, 6), (8, 1О), (7, 18)}

Имеет набор

S

log

{(1, 6), (7, 18)}.

из п интервалов на линии, где i-интервал определяется его левой и

правой крайними точками
О(п

результат должен быть

(/;, r,).

Разработайте алгоритм с временной сложностью

п) для определения точки р на линии, которая находится в наибольшем количе­

стве интервалов. Например, набор из четырех интервалов

(15, 70)}

S = {(1 О, 40), (20, 60), (50, 90),
= 50 являет­

не содержит такой точки для всех четырех интервалов, но точка р

ся общей для трех интервалов. Можно предполагать, что конечная точка интервала на­
ходится в этом интервале.

16. [5]

Имеется набор

крайними точками

S из п
(/;, r;).

отрезков линии, где отрезок

S,

определяется левой и правой

Разработайте эффективный алгоритм для выборки наимень-

Глава

4.

175

Сортировка и поиск

шего количества сегментов, объединение которых полностью покрывает интервал от О
дот.

Пирамиды
17. [3} Разработайте алгоритм для поиска k наименьших элементов в неотсортированном
наборе из п целых чисел за время О(п + kog п).
18. {5} Разработайте алгоритм с временем исполнения О(п log k) для слияния k отсортиро­
ванных списков с общим количеством п элементов в один отсортированный список.

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

19. [5]

O(kn).)

Вы можете сохранить набор из п чисел в виде невозрастающей бинарной пирамиды

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

зовать. Обоснуйте свои ответы.
а) Найти наибольший элемент.
Ь) Удалить элемент.

с) Сформировать структуру.

d)

Найти наименьший элемент.

20. [5]
а) Разработайте эффективный алгоритм для поиска второго по величине элемента из
п элементов. Задача решается меньше чем за 2п

— 3

сравнений.

Ь) Затем разработайте эффективный алгоритм для поиска третьего по величине элемен­
та из п элементов. Сколько операций сравнения выполняет ваш алгоритм в наихудшем
случае? Приходится ли вашему алгоритму в процессе работы находить максимальный

и второй по величине элементы?

·

Быстрая сортировка
21. {3] Используя применяемый в бЬJстрой сортировке принцип разбиения основной задачи
на меньшие подзадачи, разработайте алгоритм для определения срединного элемента

(median)

массива п целых чисел с ожидаемым временем исполнения О(п). (Подсказка:

нужно ли исследовать обе стороны раздела?)

22. [3] Срединным (median) элементом п значений является Г п / 2 l -e наименьшее зна­
чение.

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

Ь) Допустим, что алгоритм быстрой сортировки всегда выбирает в качестве элемента­

разделителя Г п ! Зl-е наименьшее значение текущего подмассива. Сколько операций
сравнений выполнит алгоритм быстрой сортировки в наихудшем случае при таком
условии?

176

Часть

23. [5]

/.

Практическая разработка алгоритмов

Дан массив А из п элементов, каждый из которых окрашен в один из трех цветов:

красный, белый или синий. Нужно отсортировать элементы по цвету в следующем по­

рядке: красные, белые, синие. Разрешены только две операции:

examine (А, i) —

swap (А, i, j) —

возвращает цвет i-го элемента массива А;
меняет местами i-й иj-й элементы массива А.

Создайте эффективный алгоритм сортировки элементов в указанном порядке за линей­
ное время.

24. [3]

Предоставьте эффективный алгоритм для упорядочивания п элементов таким обра­

зом, чтобы все отрицательные элементы находились перед всеми положительными эле­
ментами. Использование вспомогательного массива для временного хранения элемен­
тов не разрешается. Определите время исполнения вашего алгоритма.

25. [5]

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

скажем:

друг с другом при использовании метода быстрой сортировки

26. [5]

z; и z1. Каким может быть наибольшее количество сравнений этих элементов

(quicksort)?

Глубина рекурсии быстрой сортировки определяется как максимальное количество

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

27. [8] Перестановку р целых
[1, … , п]. Доступна только

чисел от

1 доп

док элементов подпоследовательности р;,
дочить перестановку

нужно упорядочить в возрастающем порядке

операция reverse (р, i, j), которая меняет на обратный поря­

[1, 4, 3, 2, 5]

… , р1

в перестановке. Например, чтобы упоря­

достаточно одной смены порядка элементов на об­

ратный (второго по четвертый элемент).

Докажите возможность упорядочивания любой перестановки, используя О(п) ревер­
сирований.

Теперь предположим, что затратность операции reverse (р, i, j) равна количеству

элементов подпоследовательности:

V- il + 1. Разработайте алгоритм для сортировки

перестановки р с затратностью, равной О(п

log2

п). Проанализируйте время испол­

нения и затратность своего алгоритма и предоставьте доказательство его правильно­
сти.

Сортировка слиянием
28. [5]

Алгоритм сортировки слиянием модифицирован следующим образом: входной мас­

сив разбивается не на две, а на три части, рекурсивно сортируется каждая третья часть

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

время исполнения в наихудшем случае модифицированного таким образом алгоритма
сортировки слиянием?

29. [5]

Дано

k

упорядоченных массивов, каждый из которых содержит п элементов. Требу­

ется объединить их в один массив, содержащий
шению этой задачи

kn

элементов. Один из подходов к ре­

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

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

такого алгоритма?

Глава

4.

30. [5]

Сортировка и поиск

177

.Снова рассмотрим задачу слияния

k

в один упорядоченный массив размером в

массивов, каждый размером в п элементов,

kn

элементов. Но на этот раз для слияния ис­

пользуем алгоритм, который сначала разбивает

k

массивов на

k/2

пар массивов, а затем

посредством процедуры слияния объединяет каждую пару, создавая

k/2

массивов раз­

мером 2п каждый. Этот шаг повторяется до тех пор, пока не останется один упорядо­
ченный массив размером
симости от значений п и

kn.

Каким будет время исполнения такого алгоритма в зави­

k?

Другие алгоритмы сортировки

31. [5]

Устойчивыми называются такие алгоритмы сортировки, которые оставляют эле­

менты с одинаковыми ключами в таком же порядке, в каком они находились до сорти­

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

32. [5}

Сортировка массива волной

(wiggle sort):

требуется переупорядочить неотсортиро­

ванный массив А таким образом, чтобы А[О]

< A[l] > А[2] < А[3] … Например, одно из
возможных решений для ввода [3, 1, 4, 2, 6, 5] будет [ 1, 3, 2, 5, 4, 6]. Возможно ли вы­
полнить такую сортировку за время О(п), используя только 0(1) памяти?
33.

[З} Продемонстрируйте, что п положительных целых чисел в диапазоне от
отсортировать за время О(п

34. [5}

log k).

Интересен случай

Нам нужно отсортировать последовательность

l

до

k

можно

k « п.

S

из п целых чисел, содержащую

много дубликатов. Количество различных целых чисел в

S равно O(og

п). Разработайте

алгоритм для сортировки таких последовательностей с временем исполнения в наихуд­

шем случае О(п

log log п).

35. [5} Пусть А[1 … п]- массив, в котором первые

n-Fn

элементов уже отсортированы.

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

og п.

36. [5] Допустим, что массив А [ … п] может содержать числа из множества { 1, … , п 2 }, но
в действительности содержит самое большее log log п этих чисел. Разработайте алго­
ритм для сортировки массива А за время значительно меньшее, чем О(п log п).
37. [5]

Необходимо отсортировать последовательность нулей (О) и единиц

(l)

посредством

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

а) Разработайте алгоритм для сортировки этой последовательности за п

— l

сравнений

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

Ь) Разработайте алгоритм для сортировки этой последовательности за 2п/3 сравнений
в среднем случае (допуская, что каждый из п элементов может быть О или

одинако­

вой вероятностью). Докажите, что ваш алгоритм является оптимальным.

38. [6}
q-

Пусть Р

простой, но не обязательно выпуклый, п-сторонный многоугольник, а

произвольная точка, не обязательно находящаяся в Р. Разработайте эффективный

алгоритм поиска прямой линии, начинающейся в точке

q

и пересекающей наибольшее

количество ребер многоугольника Р. Иными словами, в каком направлении нужно це­

литься из ружья, находясь в точке

q,

чтобы пуля пробила наибольшее количество стен?

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

Часть

178

/.

Практическая разработка алгоритмов

вание только одной стены. Для решения этой задачи возможно создание алгоритма
с временем исполнения О(п

log п).

Нижние предель1

39. [5] В одной из моих научных статей (см. книгу [Ski88]) я привел пример
тировки методом сравнений с временной сложностью О(п log( Га)) .

алгоритма сор­

Почему такой алгоритм оказался возможным, несмотря на то, что нижний предел сор­
тировки равен П(п

40. [5]

log п)?

Один из ваших студентов утверждает, что он разработал новую структуру данных

для очередей с приоритетами,

которая поддерживает операции insert, maximum и
extract-max с временем исполнения в наихудшем случае 0(1) для каждой. Докажите,

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

равном Щп

log п).)

Поиск

41. [3]

База данных содержит записи о

ООО клиентов в отсортированном порядке. Из них

сорок процентов считаются хорошими клиентами, т. е. на них приходится в сумме

60%

обращений к базе данных. Такую базу данных и поиск в ней можно реализовать двумя
способами:

поместить все записи в один массив и выполнять поиск требуемого клиента посред­
ством двоичного поиска;

поместить хороших клиентов в один массив, а остальных

в другой. Двоичный

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

во втором.

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

42. [5]

Число Рамануджана

Харди

это число, представимое в виде суммы двух кубов

двумя различными способами. Иными словами, существуют четыре разных числа а, Ь, с
и d, для которых а3 + Ь3 = с3 +
Например, 1729 — число Рамануджана — Харди, по­

cf.

скольку

1729 = 13 + 123 = 93 + 103.

а) Разработайте эффективный алгоритм для проверки, является ли то или иное целое
число числом Рамануджана

Харди, предоставив анализ сложности алгоритма.

Ь) Разработайте эффективный алгоритм для создания всех чисел Рамануджана- Харди
•В диапазоне от

l

до п, предоставив анализ его сложности.

Задачи по реализации
43. [5]

Возьмем двумерный массив А размером п х п, содержащий целые числа (положи­

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

в строго убывающем. (Соответственно, строка или столбец не может содержать двух

Глава

4.

Сортировка и поиск

179

нулей.) Опишите эффективный алгоритм для подсчета вхождений элемента О в мас­
сив А. Выполните анализ времени исполнения этого алгоритма.

44. [6]

Реализуйте несколько различных алгоритмов сортировки

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

методом выбора, сортировка вставками, пирамидальная сортировка, сортировка слия­

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

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

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

45. [5]

Реализуйте алгоритм внешней сортировки, использующий промежуточные файлы

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

Протестируйте свою программу как на файлах с записями малого размера, так и на
файлах с записями большого размера.

46. [8]

Разработайте и реализуйте алгоритм параллельной сортировки, распределяющий

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

алгоритма сортировки слиянием. Оцените ускорение работы алгоритма с увеличением
количества процессоров. Затем сравните время исполнения этого алгоритма с временем

исполнения реализации чисто последовательного алгоритма сортировки слиянием. Ка­
ковы ваши впечатления?

Задачи, предлагаемые на собеседовании
47. [3]

Какой алгоритм вы бы использовали для сортировки миллиона целых чисел? Сколь-

ко времени и памяти r~отребует такая сортировка?

48. [3]

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

49. [3]

Реализуйте алгоритм, который возвращает только однозначные элементы массива.

50. [5]

Как отсортировать файл размером в

тивной памятью размером всего лишь в

51. [5]

4

500

Гбайт на компьютере, оснащенном опера­

Гбайт?

Разработайте стек, поддерживающий выполнение операций занесения в стек, снятия

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

52. [5]

Дана строка из трех слов. Найдите наименьший (т. е. содержащий наименьшее ко­

личество слов) отрывок документа, в котором присутствуют все три слова. Предостав­
ляются индексы расположения этих слов в строках поиска, например

word2: (3,9, 10)

53. [6]

Есть

12

и

word3: (2,6, 15).

монет, одна из которых тяжелее или легче, чем остальные. Найдите эту мо­

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

LeetCode
1.

wordl: (1,4,5),

Все списки отсортированы.

https://leetcode.com/proЬlems/sort-Iist/

2. https ://leetcode.com/p ro Ыems/q ueue-reconstruction-by-height/

180

Часть

/.

Практическая разработка алгоритмов

3.

https://leetcode.com/proЫems/merge-k-sorted-lists/

4.

https://leetcode.com/proЫems/find-k-pairs-with-smallest-sums/

HackerRank
1. https://www.hackerrank.com/challenges/quicksort3/
2. https://www .hackerrank.com/challenges/mark-and-toys/
3. https ://www.hackerrank.com/challenges/o rgan izing-con tainers-of-balls/
Задачи по программированию
Эти задачи доступны на сайте

1. «Vito’s Family»,

глава

2. «Stacks of Flapjacks»,
3. «Bridge»,

глава

4. «ShoeMaker’s
5. «ShellSort»,

4,

4,

https://onlinejudge.org:

задача

глава

задача

4,

4,

задача

120.

1003 7.

ProЫem», глава

глава

10041.

4,

задача

задача 1О152.

10026.

ГЛАВА5

Метод «разделяй и властвуй»

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

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

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

обсуждается динамическое програм­

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

мента в найденное решение меньшей задачи. А в методе «разделяй и властвуй» задача
рекурсивно разбивается на, скажем, две половины, каждая половина решается по от­

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

и

в разд.

Слияние двух отсортированных списков, содержащих по п/2 элементов и

4.5.

властвуй»

полученных за время О(п

является

g п),

алгоритм

сортировки

слиянием,

рассмотренный

занимает только линейное время.

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

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

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

ния, определяющие сложность таких рекурсивных алгоритмов. Поэтому в этой главе

мы и рассмотрим методы для решения рекуррентностей.

5.1.

Двоичный поиск

и связанные с ним алгоритмы
Классическим примером алгоритма типа «разделяй и властвуй» является двоичный
поиск. Алгоритм двоичного поиска позволяет осуществлять быстрый поиск отсортиро­
ванных ключей в массиве

S.

Чтобы найти ключ

q,

мы сравниваем значение

q

со сред-

Часть

182
ним ключом массива

S[n/2],

S[n/2].

/.

Практическая разработка алгоритмов

Если значение ключа

q

меньше, чем значение ключа

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

S,

в противном

случае он должен находиться в его правой половине. Рекурсивно повторяя этот про­

цесс на половине, содержащей элемент

q,

pgn l

мы находим его за

сравнений, что

является большим улучшением по сравнению с ожидаемыми п/2 сравнениями при по­
следовательном поиске. Реализация алгоритма двоичного поиска на языке С приведена
в листинге

5.1.

int binary_search(item_type s[], item_type key, int low, int high) (
int middle; /* Индекс среднего элемента */
if (low > high) (
return (-1); /*Ключ

не найден*/

middle = (low + high) / 2;
if (s[middle] == key)
return (middle);

> key) (
return(binary_search(s, key, low, middle — l) ) ;
else {
return(binary_search(s, key, middle + 1, high));

i f (s[middle]

Вероятно, все это вы уже знаете. Но важно понимать, насколько быстрым является ал­

горитм двоичного поиска. Существует популярная детская игра «Двадцать вопросов».
Суть этой игры заключается в том, что один из игроков загадывает слово, а второй
пытается угадать его, задавая вопросы типа «да/нет». Если после
отгадано, то выигрывает первый игрок, в противном случае

20

вопросов слово не

второй. Но в действи­

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

рет словарь, открывает его посередине, выбирает слово (например, «ночь») и спраши­
вает первого игрока, находится ли загаданное им слово перед словом «ночь». Процесс

рекурсивно повторяется для соответствующей половины, пока слово не будет отга­
дано.

Так как стандартные словари содержат от

ренным, что

5.1.1.

20

50

ООО до

200

ООО слов, то можно быть уве­

попыток будет более чем достаточно.

Частота вхождения элемента

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

k

(например,

«Skiena»)

встреча­

ется в заданном отсортированном массиве. Так как при сортировке все копии

k

соби-

Глава

5. Метод «разделяй и властвуй»

183

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

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

в соответствующем блоке за время
блока

O(g п ).

Естественный способ определения границ

это последовательная проверка элементов слева от х до тех пор, пока не будет

найден элемент, отличающийся от ключа, и повторение процесса проверки для элемен­

тов справа от х. Разница между индексами этих границ блока, увеличенная на единицу,

и будет количеством вхождений элемента
Этот алгоритм исполняется за время

k в данный

O(g п + s),

где

набор данных.

s-

количество вхождений ключа.

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

до 0(п). Алгоритм двоичного поиска можно ускорить, модифицировав его для поиска
границ блока, содержащего элемент

k

вместо самого

k.

Допустим, мы удалим проверку

на равенство:

if (s[middle] == key) return(middle);
из реализации двоичного поиска в листинге
вместо

-1

5.1

и для каждого неуспешного поиска

будем возвращать индекс high. Теперь любой поиск станет заканчиваться

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

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

обратное и повторением поиска. Так как поиск вь~полняется за время

O(g

п), то под­

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

-1,

а (low+high)

12,

мы получим позицию между двумя элемен­

тами массива, которую должен был бы занимать ключ

k.

Этот вариант наводит на

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

k-

Е и

k + Е,

где Е —

константа

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

время

O(og п).

5.1.2.

Односторонний двоичный поиск

Теперь допустим, что у нас есть массив, заполненный последовательностью нулей, за

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

Гig п l операций сравнения. Но при отсутствии такой границы мы можем последова­
тельно выполнять сравнения по увеличивающимся интервалам (A[l], А[2], А(4], А[8],
А[lб], … ), пока не найдем ненулевой элемент. Теперь у нас имеется окно, содержащее
целевой элемент, и мы можем применить двоичный поиск. Такой односторонний

двоичный поиск возвращает границу р за самое большее 2Г lg рl операций сравнения,
независимо от размера массива. Односторонний двоичный поиск лучше всего подхо-

Часть

184

/.

Практическая разработка алгоритмов

дит для локализации элемента, расположенного недалеко от текущей позиции про­
смотра.

5.1.3.

Корни числа

Квадратным корнем числа п является такое положительное число r, для которого r 2 = п.
Хотя операция вычисления квадратного корня имеется в любом карманном калькуля­

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

> 1 должен находиться в интервале от 1 до п.
Z= 1, r = п. Теперь рассмотрим среднюю точку этого интервала т = (/ + r)/2 и
отношение т 2 к п. Если п > т 2 , то квадратный корень должен быть больше, чем т, по­
этому мы устанавливаем Z = т и повторяем процедуру. Если п < т 2 , то квадратный ко­
рень должен быть меньшим, чем т, поэтому устанавливаем r = т и повторяем проце­
Пусть

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

корень с точностью до ± 1/2 без учета знака за Г lg п l сравнений.
Этот метод деления интервала пополам можно также применять для решения более
общей задачи поиска корней уравнения. Число х называется корнем функции

f,

если

./(х) =О. Возьмем два числа Z и

r, для которых./(/)> О и./(r) (п).

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

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

обладать таким же свойством, т. е. ближайшей парой точек будут или две точки в ле­
вой или правой половине, или же пара точек слева и справа от середины. Как можно
видеть на рис.

5.4,

в последнем случае эти две точки должны быть расположены близ-


L

Рис.

5.4.

•:



R

Ближайшая пара точек в двумерном пространстве

находится или слева, или справа от центра, или же в узкой центральной полосе

194

Часть

ко к разделяющей линии (расстояние

d <

/.

Практическая разработка алгоритмов

тiп(/т;п, rтт)), а также иметь подобные

у-координаты.

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

Т(п) ~ 2-Т(п/2)

+ 0(п) =

О, условная вероятность А при В, т. е.

P(AIB), определяется

следующим образом:

P(AIB)=

Р(АпВ)_
Р(В)

В частности, если события А и В независимы, тогда

P(AIB)=

Р(АпВ)

=

Р(А)Р(В) =Р(А)

Р(В)

Р(В)

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

Вспомним события бросания игральных костей из разд.

+
+

6.1.2.

А именно:

событие А

число как минимум на одной из костей четное;

событие В

сумма чисел обеих костей или

7,

или

11.

Глава

6.

Хеширование и рандомизированные алгоритмы

Обратите внимание на то, что Р (А IB) =

1,

т. к. при любом броске с нечетной сумой чи­

сел одно из чисел должно быть четным, а другое —

Обратите внимание на то, что для Р
этому Р (BIA)

211

(BjA):

нечетным. Таким образом, А п В = В.

Р(А п В) = Р(В) =

8/36

и Р(А) =

27 /36,

по­

= 8/27.

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

Р(В I А)= Р(А 1 В)Р(В).
Р(А)
Часто, как и в случае с этой задачей, вычислить вероятности в одном направлении

более трудно, чем в другом. Применяя теорему Байеса, получаем

P(BIA) = ( 1·8/36)/(27 /36) = 8/27,
т. е. точно такой же результат, как и раньше.

6.1.4.

Распределения вероятностей

Случайные переменные

это числовые функции, в которых значения связаны с веро­

ятностями проявления. В нашем примере, где

функция выдает целое число от

2 до 12.

V(s)-

это сумма чисел на двух костях,

Вероятность определенного значения

V (s)

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

чения.

J-Ia

рис.

вероятность каждого зна­

а приводится ФПВ суммы двух правильных игральных костей.

6.5,

Обратите внимание, что пик в точке Х =

костей, вероятность которой равняется

7

соответствует наиболее вероятной сумме

116.

ФПВ

ФНВ


~
:i::
1-

~

Q.12′

сноо

1

о

:i::

t-

«‘&.

«‘g-o.on
о

а.о

С1>

CD

CD

0.4

0.0541
а.о~

r

……..

……..

—~-.-~~

1

1

• r

i

~~-,——-.-·~~….-

~

ю

Сумма обеих костей

u

6.5.

а.о

6

а

Рис.

Сумма обеих костей

Функция плотности вероятностей (ФПВ) (а) суммы двух костей содержит

точно такую же информацию, что и функция накопленных вероятностей (ФНВ)
но выглядит совершенно иначе

(6),

Часть

212

6.1.5.

Практическая разработка алгоритмов

/.

Среднее и дисперсия

Существуют два основных типа сводных статистических данных, которые совместно

предоставляют нам громадный объем информации о распределении вероятностей или
множестве данных:

+

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

произвольные выборки или точки данных;

+

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

Основной мерой центрированности является среднее значение. Среднее значение про­
извольной переменной

V,

обозначаемое как Е( V’), также называется ожидаемым значе­

нием и вычисляется по следующей формуле:

E(V) = LV(s)p(s) .
.·е.’

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

1

п

X=-L:x,.
п i=l

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

стандартное отклонение о. Стан­

V вычисляется

по формуле

cr = ~E((V -E(V))) 2 •
Для множества данных стандартное отклонение вычисляется по сумме квадратов раз­
ниц между отдельными элементами и средним значением:

cr=

L:~=’ (Х; — х)2
п-1

Связанная статистическая характеристика дисперсия вычисляется как квадрат стан­

дартного отклонения V = о 2 • Иногда в обсуждении более удобно использовать диспер­
сию, чем стандартное отклонение, ,поскольку этот термин больше чем вдвое короче. Но

обе они измеряют абсолютно одинаковую характеристику.

6.1.6.

Броски монет

Вы, скорее всего, можете довольно аккуратно определить на интуитивном уровне рас­
пределение количеств «орлов» и «решек» в

ООО бросков правильной монеты. Вы

знаете, что в п бросках, в каждом из которых вероятность выпадения «орла» составляет
р =

112,

ожидаемое количество «орлов» равно произведению рп, или

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

h

5000

в нашем

«орлов» из п бросков

213

Глава б. Хеширование и рандомизированные алгоритмы

0.008
0.007
O.OOCi

0.005
0.004
0.003
0.002
0.001
О.ООО

n
Рис.

6.6.

RМI>

Распределение вероятности выпадения

h «орлов»

1nnnn

при п бросках правильной монеты

плотно отцентрировано вокруг среднего значения и равно п/2.
В нашем примере для п

= 1О

ООО распределение

h = п/2 = 5000

А это симметричное распределение в виде колокола вокруг среднего значения. Но вы
можете не догадываться, насколько узкое это распределение (рис.

6.6).

Конечно же, теоретически, при п бросках правильной монеты количество «орлов» мо­

жет быть каким угодно в диапазоне от О доп. Но оно таким не будет. Крличество вы­
павших «орлов» будет почти всегда в пределах нескольких стандартных отклонений

от среднего, где стандартное отклонение

cr

для биноминального распределения опреде­

ляется следующей формулой

cr = Jnp(I — р)

= 0( Jn).

В самом деле, для любого распределения вероятностей как минимум 1 — ( /k2 )-я часть
массы распределения находится в пределах ± kcr от среднего значения µ. Обычно стан­
дартное отклонение cr невелико сравнительно с µ для распределений, возникающих
при анализе рандомизированных алгоритмов и процесса.

ПОДВЕДЕНИЕ итогов
Студенты часто спрашивают меня, что будет, когда рандомизированный быстрый поиск

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

вы практически наверняка просто ничего не выиграете.

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

почти всегда близко к ожидаемому.

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Случайный обход графа
ЗАДАЧА. Понимание процесса случайного обхода графа весьма важно. Ожидаемое
время обхода (количества ходов, требуемых для посещения всех вершин) будет разным
в зависимости от топологии графа (рис.

маршрута (рис.

6.7,

а)?

6. 7).

В частности, сколько займет прохождение —

Часть

214

/.

Практическая разработка алгоритмов

2

••••••

1 2 3 4 5 6

5
б

а

Рис.

6.7.

Сколько займет проход случайным образом по всем п вершинам маршруrа (а)

и графа

(6)?

Предположим, что обход начинается с левого конца маршрута, содержащего т вер­
шин. Направление перемещения определяется броском правильной монеты. Если вы­
падает «орел», делаем один ход вправо, а если «решка»

один ход влево. Если ход

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

РЕШЕНИЕ. Чтобы добраться до правого конца маршрута, после п бросков количество

выброшенных «орлов» должно быть на т

— 1 больше,

чем «решек»,

и это в предпо­

ложении, что броски не выполняются, когда мы находимся в левом конце маршрута,

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

не бросков выпадет «орел» со стандартным отклонением, равны~ cr =е( ~). Это
отклонение а описывает размах вероятной разницы между количествами «орлов» и

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

m=0(~)~n=0(m 2 ) •

6.2. Задача мячиков и контейнеров
Задача мячиков и контейнеров

классическая

задача теории вероятностей. Задача

заключается в произвольном бросании х одинаковых мячиков в у помеченных контей­
неров. Нас интересует полученное распределение мячиков в контейнерах. Сколько
контейнеров будут содержать то или иное количество мячиков?
Хеширование можно рассматривать как процесс бросания мячиков в контейнеры.

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

ние на то, что такое распределение будет выдерж1:1ваться независимо от качества функ­
ции хеширования.

Хорошая функция хеширования должна работать подобно генератору случайных чи­
сел, выбирая идентификаторы контейнеров/целых чисел с одинаковой вероятностью

от

1 до

п. Но что будет при выборе п таких целых чисел из равномерного распределе­

ния? Идеальным результатом было бы, чтобы каждый из п объектов (мячиков) попадал

Глава

6.

Хеширование и рандомизированные алгоритмы

215

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

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

6.8).
Количество корзин, содержащих

k

107
3,678,774
3,677,993
1,840,437
613,564
152,713
30,517
5,133
754
107
8

106
367,899
367,928
183,926
61,112
15,438
3130
499
56
12

n =

о

1
2
3
4
5
6
7
8
9
10
11
Рис.

6.8.

n =

k

объектов

tt= 10 8
36,789,634
36,785,705
18,392,948
6,133,955
1,531,360
306,819
51,238
7,269
972
89
10
1

Результаты для таблиц хеширования,

содержащих от одного миллиона до ста миллионов объектов

Можно видеть, что во всех трех случаях

36, 78%

всех корзин остаются пустыми. Это не

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

попадания для каждого конкретного мячика равна р

— 1 корзин. Вероятность р, не­
1)/п и стремится к 1 по мере

= (п

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

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

п-1 ) 2 ~-;;=О,367879.

P(IB1i=O)= ( -пТаким образом,

36,78%

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

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

6.8

таблице можно видеть, что с увеличением п

количество объектов в самой заполненной корзине увеличивается от

8 до 9 и до 11. В дей­
O(log пЛоg log п)

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

и медленно нарастает, т. е. не является константным (постоянным). Поэтому я несколь­
ко поторопился, когда в разд.

3. 7.1

сказал, что в наихудшем случае время доступа для

хеширования равно О( 1) 1•

1 Точнее

говоря, ожидаемое среднее время поиска дr1я всех п ключей действительно будет О( 1), но при этом
0(1og п! log log п ).

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

Часть

216

/.

Практическая разработка алгоритмов

ПОДВЕДЕНИЕ итогов
Для точного анализа случайного процесса требуются знания формальной теории вероят­
ностей, алгебры и тщательная асимптотика. В этой главе указанные предметы рассматри­
ваются лишь поверхностно, но нужно отдавать себе отчет в их необходимости.

6.2.1.

Задача о собирании купонов

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

дый контейнер не будет содержать, по крайней мере, один мячик. Сколько для этого
следует ожидать бросков? Как можно видеть на рис.

6.9,

для заполнения всех контей­

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

18

32
24

21

Рис.

6.9.

16

29

31

19

12

25

9

10

23

3

15

11

27
22

30

20

14

28

13

8

5

17

1

(33)

7

2

6

1

2

3

4

5

6

7

8

9

10

26

4

Результаты эксперимента по бросанию мячиков в контейнеры случайным образом.
Все контейнерьr заполняются только к 33-му броску

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

i

r;

прогон

корзин и до следующего попада­

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

бросании монеты с вероятностью выпадения «орла», равной р, ожидаемое количество
бросков до получения первого «орла» будет равным 1/р. Это свойство геометрического
распределения. После заполнения

в пустую корзину равна р = (п

i

корзин вероятность попадания следующего броска

Собрав все это вместе, получим следующую фор­

— i)/n.

мулу:

n-1

n-1

1=0

i=O

п-1

1=0

n-

Е(п)= Ilr,I= L ~=nI-. =nHn ::::::nlgn.
n-

l

l

Здесь главное помнить, что число гармоники

и что Нп;:::

ln

п.

ОСТАНОВКА ДЛЯ РАЗМЫШЛЕНИЙ: Время покрытия для Кп
ЗАДАЧА. Предположим, что мы начинаем случайный обход полного п-вершинного

графа (см. рис.

6.7, 6)

с вершины

1.

На каждом шаге обхода мы направляемся с теку­

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

Глава б. Хеширование и рандомизированные алгоритмы

217

РЕШЕНИЕ. Это точно такая же задача, как в предыдущем разд. «Остановка для раз­
мышлений», но с графом другого типа, и поэтому, возможно, что ее решение будет
иным.

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

1

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

log

п). Единственная

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

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

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

непосещенных вершин на следующем шаге меняется с (п — i)/n на (п — i)l(n шая общее время покрытия с пНп до (п

— 1)Нп.

ковые. Покрытие полного графа занимает 0(п
время покрытия маршрута.

6.3.

1),

— i)

умень­

Но эти значения асимптотически одина­

log

п) шагов, что намного быстрее, чем

Почему хеширование

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

— 1,

h(s) сопоставляет ключи s целым числам от О до

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

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

но анализировать, рассматривая его значения как результаты бросков игральной кости
ст гранями.

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

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

h(x)

должен всегда выдавать абсолютно одинаковый результат, иначе мы никогда не

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

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

подсунутыми

каким-то шутником данными, заставляющими алгоритм

творить несуразные вещи. Но для любой функции хеширования

h

можно, в принципе,

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

во

S,

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

s Е S.

По­

скольку множество значений этой функции содержит только т элементов, это должно

вызывать много коллизий. Так как каждая корзина в среднем содержит пт/т

=п

эле­

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

крайней мере, п элементов. Эти п элементов в этой корзине сами по себе окажутся
кошмаром для функции хеширования

h.

Часть

218

/.

Практическая разработка алгоритмов

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

Каким же образом

можно создать семейство случайных функций

Вспомним, что обычно

хеширования?

h(x) = f (х) (mod т), где функция f (х) преобразовывает ключ

в огромное значение, которое сводится к требуемому диапазону функцией

mod

т, вы­

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

f

(х)

(mod m) f- (f (x) mod р) (mod m).

Например:

21347895537127(mod 17)=8f-(21347895537127(mod2342343))(mod 17)= 12.
Таким образом, для определения функции хеширования можно выбрать случайное
целое число р:

‘/’Ц,х)

= ((f (x) mod р) mod m).

Результат будет вполне нормальным при следующих условиях:

+
+
+

большое относительно р;

fix) р

большое относительно т;

т ир

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

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

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

6.4.

6.4.

Фильтры Блума

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

Google.

Эти системы стремятся создать индекс

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

При сканировании следующей ссылки системе

Google

нужно определить, является ли

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

между а и Ь, когда

h(a) = s

и

h(b) = s,

но а f-Ь. Этот момент обсуждается в разд.

3. 7.2.

Глава б. Хеширование и рандомиэированные алгоритмы

219

Но в этой ситуации ложные коллизии не такая и большая трагедия

означают, что

Google

они всего лишь

не проиндексирует найденный новый документ. Это может быть

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

маемое таблицей пространство памяти уменьшится в

— на типичной машине зани­
64 раза. Освобожденную таким

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

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

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

Намного лучшее решение

0,05,

5%,

вероятность

что намного больше, чем приемлемо.

использовать фW1ьmр Блума, который также является

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

k

раз, используя

подаче на вход фильтра Блума документа
(присваиванием значения

1) все

s

k

разных функций хеширования. При

в таблице хеширования устанавливаются

биты, соответствующие

h1(s), h1(s), … hk(_s), означая тем

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

мый документ в таблице, необходимо проверить, равны ли все эти

k

битов единице.

Чтобы документ был ложно определен, как уже находящийся в фильтре, нужно, чтобы
все его

k

битов были установлены в хешах предыдущих документов, как показано на

рис. ЦВ-6.10.

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

km/n,

что в

k

km

битов, поэтому вероятность одной

раз больше, чем при использовании только

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

k битов

таблицы с би­

тами проверяемого документа, а вероятность этого события составляет всего лишь

(p 1)k = (km/n)k. Это своеобразное выражение, поскольку вероятность, возведенная
k, но в нашем случае с увеличением k
вероятность повышается. Чтобы определить значение k, минимизирующее р можно

Pk

=

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

На рис. ЦВ-6.11 показан график этой вероятности ошибки в зависимости от нагрузки

(т/п) с отдельной кривой для каждого значения

k от 1 до 5.

Очевидно, что использование большого количества функций хеширования (большое
значение

k)

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

таблицей хеширования (представленной на рис.

6.11

синей кривой с

k

= 1)- по

край­

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

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

Часть

220

Практическая разработка алгоритмов

/.

Для 5-процентной нагрузки частота ошибок для простой таблицы хеширования

(k·= 1)
будет в 51,2 раза больше, чем для фильтра Блума с k = 5 (9,77 х 10-4), хотя обе эти таб­

лицы занимают абсолютно одинаковый объем памяти. Фильтр Блума

замечательная

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

случающимися время от времени ошибками.

6.5.

Парадокс дня рождения

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

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

образом гарантировать постоянное время поиска в наихудшем случае?
Для статических словарей это возможно при использовании метода идеШ1ыюго хеши­
рования. В этом методе мы получаем все ключи в одной партии, после чего добавлять

или удалять элементы нельзя. Таким образом можно один раз создать структуру дан­
ных, а затем многократно использовать ее для поиска и/или проверки на вхождение во
множество. Это довольно распространенное применение, и зачем тогда тратиться на
динамические структуры данных, если они не требуются?

Один из способов реализации этого подхода

попробовать использовать заданную

функцию хеширования

h(x) на нашем множестве S из п ключей и надеяться, что она
создаст таблицу хеширования без коллизий, т. е. h(x) i- h(y), для всех пар различных
(х, у) Е S. Должно быть очевидным, что шансы везения повышаются с увеличением
размера таблицы относительно п

чем больше пустых слотов доступно для следую­

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

будем многократно вставлять в нее ключи. Для
в один из (т

— i)

(i + 1)-й

вставки вероятность попадания

все еще свободных слотов в таблице составляет (т

i)/т. Для идеаль­

ного хеширования все п вставок должны попасть в пустой слот, поэтому
n-l

Р(без коллизий)= П

(т-i)

i=o

= п т’•

1 •

т ((т — п).)

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

ния? В этом случае размер таблицы хеширования т =

коллизий уменьшается до
как показано на рис.

6.12.

1/2

для п =

23,

а для п

2: 50

365.

Вероятность отсутствия

составляет меньше

3

процентов,

Иными словами, при наличии в комнате только

23

людей

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

п = е( Гт) или, что равносильно, когда т

=

0(п 2 ).

Глава

6.

Хеширование и рандомизированные алгоритмы

1.0

••••

••••
••••
•••••••••
••••
••••
••

0.8

0.6

0.4

20

10

6.12.

……………

••

0.2

Рис.

221

30

40

50

Вероятность отсутствия коллизий быстро нарастает с увеличением количества

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

Но квадратический объем памяти

= 365

слишком высокая цена за постоянное время дос­

тупа к п элементам. Вместо этого создадим двухуровневую таблицу хеширования.
Сначала п ключей множества

S

хешируются в таблицу с п слотами. При этом коллизии

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

Пусть

/;

обозначает длину i-го списка в этой таблице. Вследствие коллизий многие спи­

ски будут содержать более чем

1 элемент.

Определим «достаточно короткий» список

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

N = ~)2 = 0(п).
/ :::(

Предположим, так случилось, что все · элементы находятся в списках длиной

/,

а это

означает, что имеется п// непустых списков. Сумма квадратов длин этих списков будет:

N

= (nll)z2 = nl- это

линейная величина, поскольку l является константой. Даже при

наличии фиксированного количества списков длиной Гп можно все равно использо­
вать линейный объем памяти.
Более того, можно доказать с высокой вероятностью, что
это свойство не выдерживается на

S

N

~ 4п. Таким образом, если

для первой примененной функции, можно попро­

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

Для таблицы второго уровня используем массив длиной N, выделив память объемом !/
для элементов i-й корзины. Обратите внимание на то, что это относительно количе­
ства элементов достаточно большой объем, позволяющий избежать парадокса дня
1

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

Полная схема этого подхода показана на рис.

6.13.

.

222

Часть

/.

Практическая разработка алгоритмов

77
1

55

53

53

2

о

Рис.

6.13.

з

4

5

6

7

8

2

о

з

В идеальном хешировании используется двухуровневая таблица хеширования,

что гарантирует постоянное время поиска. В таблице первого уровня коди~уется диапазон
индексов элементов таблицы второго уровня количеством

выделенных для хранения

/; элементов

в корзине

i

/; ,

первого уровня

Содержимое i-го элемента таблицы хеширования первого уровня содержит начальную

и конечную позиции для

!/

элементов в таблице второго уровня, соответствующих

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

Поиск элемента

s

начинается с вызова функции хеширования

h 1(s)

для вычисления

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

start + (h2(s) (mod (stop — start))),

в которой элемент

s

будет

находиться во второй таблице. Таким образом, поиск всегда можно выполнить за вре­
мя

0(1),

используя линейный объем памяти двух таблиц.

Идеальное хеширование

очень полезная практическая структура данных, подходя­

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

минимизировать требуемый объем памяти и затраты на построение и поиск,

напри­

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

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

6.6.

Метод минимальных хеш-кодов

Хеширование позволяет быстро проверить, находится ли конкретное слово w из доку­
мента

D

в документе

D 2• Для

этого просто хешируем все слова из документа

лицу Т, а затем в этой таблице ищем хеш

h(w)

D2

в таб­

требуемого слова. Для простоты и

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

ной статьи. Проверив таким образом все статьи словаря

w;

Е

D1 в

таблице Т, можно

подсчитать количество элементов пересечения и вычислить коэффициент сходства
Жаккара J

(D 1, D2) этих двух документов:

J(D D )JD1 nD2I .
1′ 2
ID1 u Dzl

Глава

6.

Хеширование и рандомизированные алгоритмы

223

Значение этого коэффициента может находиться в диапазоне от О до

1-

и представ­

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

многократном сравнении, например в масштабах Интернета, эффективность играет
важную роль. Предположим, что принять решение о степени схожести документов

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

«the»,

что даст очень мало информации

о схожести документов. Будет лучше выбрать наиболее репрезентативное слово

пример, по статистике

часто­

та слова

TF-IDF (Term Frequency —

Jnverse Document Frequency —

на­

обратная частота документа). Но и при этом подходе все равно делается

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

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

помощи искусного, но простого приема, называемого методом минuмШiьных хеш-кодов

(minwise hashing).

Суть этого приема следующая. Для каждого слова в документе

D1

вычисляется хеш-код h(w1), затем изо всех этих хеш-кодов выбирается код с наимень­

шим значением. Процесс повторяется для документа

D 2,

используя ту же функцию

хеширования.

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

s:

The

h(s):

17

Рис.

6.14.

cat
128

in

56

the
17

6.14.
The
17

hat

4

hat
4

in
56

the
17

store

96

Если два документа очень похожи, то слова, соответствующие хеш-кодам

с минимальным значениями в каждом из этих документов, будут, скорее всего, одинаковыми

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

D 1 и D2,

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

каждого документа выбрано абсолютно случайное слово. Тогда вероятность выбора
одинакового слова будет равна

l/v,

где

v обозначает размер

Теперь предположим, что словарные составы документов

словарного состава.

D 1 и D 2 не идентичны. Веро­

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

также от общего размера словарногq состава документов. По сути, эта вероятность

тот же коэффициент сходства Жаккара, описанный ранее.
Выборка большего количества слов

скажем,

k

хеш-кодов с наименьшим значением

из каждого документа, и возвращение размера пересечения по

k

дает более точную

оценку коэффициента сходства Жаккара. Но внимательный читатель может задаться

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

Часть

224
мера документов

D1

и

D 2,

/.

Практическая разработка алгоритмов

тогда как вычисление точного размера пересечения при по­

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

N

документов, каждый из которых содержит в среднем т словарных

статей. Мы хотим создать индекс, чтобы выбрать из этих документов наиболее сход­
ный с документом

Q.

Хеширование всех слов во всех документах дает таблицу размером
хранить же

k (т

Обход графа

против списков

O(d)

+ п)

против матрицы смежности 0(п 2 )
Пригодность для решения большинства проблем

Списки смежности

ПОДВЕДЕНИЕ итогов
Для большинства приложений на графах списки смежности являются более подходящей
структурой данных, чем матрицы смежности.

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

1 до

п. Ребра представляются посредством массива связ­

ных списков. Соответствующий код приведен в листинге

#define

МАХV

100

typedef struct edgenode
int у;
int weight;
struct edgenode *next;
edgenode;

7 .1.

Максимальное количество вер!Ш1н

/*

/*
/*
/*

Информация о
Вес ребра,

смежности

если ~сть

Следующее ребро в

*/

*/

*/

списке

*/

/

typedef struct
edgenode *edges[МAXV+l];
int degree[МAXV+l];
int nvertices;
int nedges;
int directed;
graph;

смежности

/*
/*
/*

Информация о

/*

Количество ребер в графе

/*

Граф ориентированный?

Степень

каждой вер!Ш1НЫ

*/
*/

Количество вер!Ш1Н в графе

*/

*/

*/

Ориентированное ребро (х, у) представляется в списке смежности структурой типа

edgenode. Поле degree содержит степень соответствующей вершины. Неориентирован-

242

Часть

/.

Практическая разработка алгоритмов

ное ребро (х, у) входит дважды в любою структуру графа на основе смежности: один
раз в виде у в списке для х и второй раз в виде х в списке для у. Булев флаг directed

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

Для демонстрации использования этой структуры данных мы покажем, как выполнить

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

которых указаны вершины очередного ребра. Начинаем с инициализации структуры
(листинг

7.2).

j Листинг 7.2.

Формат графа

void initialize_graph(graph *g, bool directed)
int i;
/ * Счетчик */
g->nvertices = О;
g->nedges = О;
g->directed
directed;
for (i = 1; i degree[i]

МАХV;

=

i++)

О;

for (i = 1; i edges[i] = NULL;

Собственно чтение файла графа заключается во вставке каждого ребра в эту структуру
(листинг

7.3).

j Листинг 7.3. Считывание

графа

void read_graph(graph *g, bool directed) {
/* Счетчик */
int i;
int m;
/* Количес тв о вершин * /
/* Вершины в ребре (х, у ) */
int х , у;
initialize_graph(g, directed);
scanf(» %d %d»,

&(~- >nvertices),

&m);

f or (i = 1; i weight
р->у

=

int у, bool directed)
/* Временный указатель */
/*Выделяем память для

edgenode */

О;

у;

=

p->next

=

g->edges[x];

g->edges[x]

/*

= р;

Вставка в начало списка

*/

g->degree[x]++;
i f ( ! directed)

{
insert_edge(g,
else {
g->nedges++;

у,

х,

true);

Для вывода графа на экран достаточно двух вложенных циклов: одного
другого

для их смежных ребер (листинг

void print_graph(graph *g)
int i;
/*
edgenode *р;
/*

Счетчик

для вершин,

7 .5).

*/

Временньм указатель

*/

for (i = l; i nvertices; i++)
printf(«%d: «, i);
р = g->edges[i];
while (р != NULL)
printf(» %d», р->у);
р = p->next;
printf («n»);

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

рабатываемого прИложения. Я рекомендую обратить внимание на библиотеки типов

244

Часть

LEDA

(см. разд.

22.1.1)

или

Boost

(см. разд.

/.

Практическая разработка алгоритмов

22.1.3),

которые я считаю лучше всего

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

7.3.

История из жизни. Жертва закона Мура

Я являюсь автором ComЬinatorica

для работы с графами (рис.

7 .5),

(www.comblnatorica.com) —

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

библиотеки алгоритмов

предназначенной для взаимодействия с компьютерной

Mathematica.

Большой проблемой в

Mathematica

из-за применяемой в ней аппликативной модели вы­

числений (в ней не поддерживаются операции записи в массивы с постоянным време­
нем исполнения) и накладных расходов на интерпретацию кода (в отличие от компи­

лирования). Код на языке программирования пакета
от

1ООО

до

5000 раз

Mathematica

обычно исполняется

медленнее, ч.ем код на языке С.

Эти особенности приложения могут значительным образом замедлять его работу. Что
еще хуже,

Mathematica сама

занимает большой объем оперативной памяти, требуя для

эффективной работы целых четыре мегабайта, что в

1990

году, когда я завершил рабо­

ту над ComЬinatorica, было потрясающе много. При этом любая попытка вычислений
на достаточно больших структурах неизбежно вызывала переполнение памяти. В такой
среде мой графический пакет имел надежду эффективно работать только на графах

очень небольшого размера .

.((/.
1

t l

ft .
• • • •
Рис.

7.5.

А


• • •
1

Репрезентативные графы в

Comblnatorica:

пути, не имеющие общих ребер (слева),

гамильтонов цикл в гиперкубе (в центре), обход в глубину дерева поиска (справа)

Одним из проектных решений, принятых мною вследствие описанных недостатков

Mathematica,

было использование матриц смежности вместо списков смежности в ка­

честве основной структуры данных графов для ComЬinatorica. Такое решение может

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

Глава

245

Обход графов

7.

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

тельно п

+ 2т

слов, где составляющая 2т отражает требования к памяти для хранения

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

мяти только в том случае, ес~и выражение п + 2т значительно меньше, чем п 2 • Размер
матрицы смежности остается управляемым для п S 100, и, конечно же, для плотных

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

7.2.

Таблица

7.2.

Результаты эталонных тестов старой ComЬinatorica

на рабочих станциях пяти поколений (время исполнения в секундах)
Машина

Команда

Sun-3

Sчn-4

Sun-5

Ultra 5

Sun Blade

PlanarQ[GridGraph[4,4]]

234,10

69,65

27,50

3,60

0,40

Length

289,85

73,20

24,40

3,44

1,58

239,67

47,76

14,70

2,00

0,91

831,68

267,5

22,05

3, 12

0,87

[Partitions[ЗO]]

VertexConnectivity

[GridGraph[З,3]]

RandomPartition[lOOO]

В

1990

году решение двух довольно сложных, но имеющих полиномиальное время вы­

полнения задач на графах из

9

и

16

вершин занимало на моем настольном компьютере

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

9

своему опыту я знал, что язык программирования пакета

х 9 равно всего лишь 81. По
Mathematica работает лучше

со структурами данных постоянного размера, наподобие матриц смежности, чем со
структурами

данных,

имеющими

непостоянный

размер,

какими

являются

списки

смежности.

Тем не менее, несмотря на все эти проблемы с производительностью, библиотека

ComЬinatorica оказалась очень хорошим приложением, и тысячи людей использовали
этот пакет для всевозможных интересных экспериментов с графами. ComЬinatorica ни­
когда не претендовала на звание высокопроизводительной библиотеки алгоритмов.
Большинство пользователей быстро осознали, что вычисления на больших графах
нереальны, но тем не менее с энтузиазмом работали с этой библиотекой как с инстру­
ментом для математических исследований и

среды моделирования.

Все были до­

вольны.

Но по прошествии нескольких лет пользователи ComЬinatorica начали спрашивать, поче­
му вычисления на графах небольшого размера занимают так много времени. Это меня не

удивляло, т. к. я знал, что моя программа всегда была медленной. Но почему людям по­
требовались годы, чтобы заметить это?

Часть

246

/.

Практическая разработка алгоритмов

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

в соответствии с этими улучшениями в аппаратном обеспечении. Частично из-за того,

что ComЬinatorica была рассчитана на работу со структурами данных графов квадра­
тичного размера, она недостаточно хорошо масштабировалась на разреженные графы.

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

ComЬinatorica нуждается в обновлении. Мой коллега, Срирам Пемараджу

Pemmaraju),

(Sriram

предложил мне свою помощь. Спустя десять лет после первоначального

выпуска библиотеки мы (преимущественно он) полностью переписали ComЬinatorica,
воспользовавшись более быстрыми структурами данных.

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

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

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

rica

может обрабатывать графы, которые в

50-100

раз больше, чем те, с которыми мог­

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

графов большего размера, чем те, для работы с которыми предназначалась старая вер­
сия ComЬinatorica. Так, на рис.

7.6,

а приводится график времени исполнения функции

MinimumspanningTree для обеих версий ComЬinatorica. Но относительная разница во
времени исполнения увеличивается с возрастанием п. На рис.

7.6,

б показано соотно­

шение времени исполнения старой и новой версии в зависимости от размера графа.

Разница между линейным и квадратичным ростом’ размера является асимптотической,
поэтому

с

возрастанием

п

последствия

перехода

на

новую

версию

становятся

еще

более заметными.
Обратите внимание на странный всплеск на графике при п::::

250.

Скорее всего, это

следствие перехода между разными уровнями иерархии памяти. Такие явления не ред­

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

производительности,

достигнутое

благодаря

использованию

списков

смежности, намного превышает любое улучшение, обусловленное применением кэша.

Из нашего опыта разработки и модифицирования библиотеки ComЬinatorica можно
извлечь такие три основных урока.

Глава

t

7.

Обход графов

247

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

Передовое аппаратное обеспечение со временем доходит до пользователей всех

уровней. В результате улучшений в аппаратном обеспечении, имевших место в те­
чение

15

лет, скорость работы первоначальной версии библиотеки ComЬinatorica

возросла более чем в

200 раз.

В этом контексте дальнейшее повышение произ­

водительности вследствие модернизации библиотеки является особенно значи­
тельным.

Время исполнения функции

MinimumSpanningTree

для обеих версий ComЬiпatorica

4S
1) {
printf ( «root articulation vertex: %d n», v) ;
11 Корневой шарнир
return;

root

=

(parent[parent[v]]

i f ( ! root)

-1);

/*Вершина

parent[v] —

корень?*/

{

== parent[v])
printf(«parent articulation vertex: %d n», parent[v] );
11 Шарнир-родитель:

if

(reachaЫe_ancestor[v]

if

(reachaЫe_ancestor[v]

== v)
printf ( «bridge articulation vertex: %d n», parent [v]);

if (tree_out_degree[v] > 0) { /* Вершина v — лист? */
printf(«bridge articulation vertex: %d n», v);
//Мостовой шарнир:

time_v = entry_time[reachaЫe_ancestor[v]];
time_parent = entry_time[reachaЫe_ancestor[parent[v]]];

Часть

268

/.

Практическая

разработка алгоритмов

if (time_v < time_parent) {
reachaЫe_ancestor[parent[v]]

reachaЬle_ancestor[v];

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

из

самого

старого

достижимого

предшественника

вершины

к

ее

родителю,

а

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

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

разъединяет граф, называется мостом

(bridge ).

Граф, не имеющий мостов, называется

реберно-двусвязным (edge-Ьiconnected).
Является ли мостом то или иное ребро (х, у), можно с легкостью определить за линей­

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

+ т)

посредством

обхода графа в глубину. Ребро (х, у) является мостом, если, во-первых, оно древесное,
а во-вторых, нет обратных ребер, которые соединяли бы вершину у или нижележащие
вершины с вершиной х или вышележащими вершинами. Соответствие ребра этим ус­

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

process vertex late

7.1 О.

(см. листинг

7.21).

Обход в глубину ориентированных графов

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

б

а

Рис.

6-

7.13.

в

Возможные типы ребер при обходе графа: а

прямое ребро; в

обратное ребро; г

7 .13 ).

г

древесные ребра;

поперечные ребра. Прямые и поперечные ребра

могут встречаться при обходе в глубину только в ориентированных графах

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

Глава

7.

269

Обход графов

ленное к вершине-потомку? Нет, ибо в таком случае мы бы уже прошли это ребро (х, у)
при исследовании вершины у, что делает его обратным ребром. Возможно ли выйти на
поперечное ребро (х, у), связывающее две вершины, не имеющие отношения друг

к другу? Опять же, нет, т. к. мы бы уже открыли это ребро при исследовании вершины у,
что делает его древесным ребром.

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

7 .13.

Поэтому такая их

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

Тип ребра можно без труда определить исходя из состояния вершины, времени ее от­

крытия и ее родителя. Соответствующая процедура приведена в листинге

int edge_classification (int
if (parent[y] == xl {
return (TREEI;

if

(discovered[y]
return (ВАСК);

&&

х,

int

у)

7.22.

{

!processed[y] 1 {

if (processed[y] && (entry_time[y]>entry_time[x]I)
return(FORWARDI;

if (processed[y] && (entry_time[y]О и
п

M[n,l] = L:s1•
i=I

Сколько времени уйдет на вычисление, если сохранять частичные результаты? Всего

в таблице имеется
для

1 ::::;

п’

::::;

п,

k·n ячеек. Сколько времени займет вычисление значений М[п’, k1
1 ::::; k’ ::::; k? Для вычисления этой величины при помощи обобщенного

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

которых является большим из двух значений: значения в таблице поиска и суммы са­
мое большее п’ элементов (занимая время О(п’)). Если заполнение каждой из

kn

ячеек

за.нимает время п2 , то всю рекуррентность можно вычислить за время О(kп3).
Меньшие значения вычисляются раньше больших с тем, чтобы на каждом шаге вычис­
лений имелись необходимые данные. Реализация алгоритма приводится в листин­
ге

10.18.

Глава

10. Динам/)Jческое

383

программирование

Листи1~ а.111орит1~·1и линеи11010
void partition(int s[], int n, int k) {
/*Массив префиксных сумм*/
int p[МAXN+l};
/* Таблица значений */
int m[МAXN+l} [МАХК+l];
/* Таблица разделителей */
int d[МAXN+l] [МАХК+l];
/* Стоимость тестового разбиения */.
int cost;
/* Счетчики */
int i, j, х;
р[О]

=

/*

О;

Создаем префиксные суммы

*/

for (i = 1; i random[O,
Понижаем температуру

!]}

then

s

si

Т

until (no change in C(s))
Return s
ПОДВЕДЕНИЕ итогов
Имитация отжига является эффективным средством , потому что она больше времени уде­
ляет «хорошим» элементам пространства решений, чем «плохим», а также потому, что
она не зацикливается на одном локальном оптимальном решении.

Так Же как и в случае с локальным поиском, представление задачи состоит из пред­
ставления пространства решений и задания несложной функции стоимости

C(s)

для

определения качества конкретного решения. Новым компонентом является график

охлажден-ия (cooliпg

schedule),

параметры которого управляют вероятностью приемле­

мости «плохого» перехода в зависимости от времени .

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

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

+
+

первоначШ1ьная температура системы

функц-ия понижен-ия температуры

обычно

обычно Т; =

1;

Tk = a·Tk-i,

где

0,8:::;

а:::;

0,99.

Это

означает экспоненциальное понижение температуры, а не линейное;

+

количество итераций перед понижением- температуры

ся провести около

1ООО

как правило, разрешает­

итераций. Кроме этого, обычно весьма выгодно удерживать

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

+

критерии приемлемости —

в типичном случае при емлем любой « хороший» пере­

ход, а также «плохой» переход, если
С(sн )-C(s,)
е

где

r-

случайное число в диапазоне О

>r,
:::; r < 1.

Константа Больцмана kн масштаби­

рует эту функцию стоимости, чтобы при начальной температуре принимались почти
все переходы;

Глава

12.

Решение сложных задач

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

461

если за последнюю итерацию (или несколько последних

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

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

http://www.algorist.com.

Сравните соотношения между временем поиска решений и их качеством для всех трех

рассмотренных эвристических алгоритмов. Наилучшую эффективность демонстрирует
алгоритм имитации отжига. На рис.

.извольных

12.1 О

показаны три прогона для трех разных про­

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

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

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

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

Имитация оnкига

т

2

s

э

Количество повторений

Рис.

12.10. Соотношения

6
1•7

между временем поиска решений и их качеством

для решения задачи коммивояжера методом имитации отжига

После десяти миллионов итераций метод имитации О1Жига выдал решение стоимостью

7212

единицы, что всего лишь на

10,4%

превышает оптимальное. Если вы готовы не­

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

и

21

секунду) понизило стоимость решения до

больше оптимальной.

6850

5

минут

единиц, что всего лишь на

4,9%

462

Часть

/.

Практическая разработка алгоритмов

При умелом обращении самые лучшие эвристические алгоритмы, настроенные под

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

Реализация
Реализация эвристического алгоритма имитации отжига приведена в листинге

итации отжига

void anneal(tsp_instance *t, tsp_solution *s) {
int х, у;
/* Пара элементов для обмена местами */
int i, j;
/* Счетчики */
bool accept_win, accept_loss; /* Условия принятия перехода */
douЫe temperature;
/* Текущая температура системы */
douЫe current value;
/* Значение текущего состояния */
douЬle start value;
/* Значение в начале цикла */
douЬle delta;
/* Значение после обмена */
douЬle exponent;
/* Показатель степени для функции
энерг.

состояния

*/

temperature = INITIAL_TEMPERATURE;
initialize_solution(t->n, s);
current value = solution_cost(s, t);
for (i = 1; i n);

х

delta = transition(s, t, х,
accept_win = (delta < 0);

у);

/*

Обмен понизил

стоимость?

*/

exponent = (-delta / current_value) / (К* temperature);
accept_loss = (exp(exponent) > random_float(O,l));
if (accept_win 1 1 accept_loss)
current value += delta;
else {
transition(s, t, х, у);

/*

Обратный переход

*/

12.8.

Глава

Решение сложных задач

12.

463

solution_count_update(s, t);

if (current_value < start_value) {

/*

Возвращаемся при этой
температуре

*/

temperature /= COOLING_FRACTION;

12.6.4.

Применение метода имитации отжига

Теперь рассмотрим несколько примеров, демонстрирующих, как можно использовать

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

Задача максимального разреза
В задаче максимального разреза требуется разделить вершины взвешенного графа
множества

V1

и

V2

G на

таким образом, чтобы максимизировать в каждом множестве вес

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

19. 6).

Как можно сформулировать задачу максимального разреза для решения методом ими­

тации отжига? Пространство решений состоит из всех

2n-l

возможных разбиений вер­

шин. Мы получаем двойную экономию по всем подмножествам вершин, зафиксировав
вершину

v1

на левой стороне разбиения. Подмножество вершин, сопровождающее эту

вершину, можно представить с помощью битового вектора. Стоимостью решения яв­

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

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

На практике именно к такому простому и естественному моделированию следует стре­
миться при поиске эвристического алгоритма.

Независимое множество
Независимым множеством графа

G

называется подмножество вершин

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

S.

S,

не содержа­

Максимальным незави­

симым множеством графа является такое множество вершин, которое порождает пус­

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

теорией шифрования (см.разд.

планированием и

19.2).

Естественное пространство состояний для решения задачи методом имитации отжига

включает в себя все 2п возможных подмножеств вершин, представленных в виде бито-

Часть

464

/.

Практическая разработка алгоритмов

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

переходов добавляет или удаляет одну вершину из множества

S.

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

ISJ —

S

S

будет функ­

подграф содержит ребро, и

в случае действительно независимого множества. Такая функция гарантирует, что

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

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

ISJ —

А. ·т 5/Т, где А. является постоянной, Т представляет температуру, а

ство ребер в подграфе, порожденном

S.

ms —

C(S)

=

количе­

Для такой цели хорошо подходят большие

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

C(S)

от Т обеспечивает

в конечном итоге вытеснение ребер по мере охлаждения системы.

Размещение компонентов на печатной плате
Задача разработки печатных плат заключается в размещении на них должным образом
компонентов

обычно это интегральные схемы. Заданными критериями компоновки

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

в

отведенное

место,

и

минимизация длины соединяющих дорожек.

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

При формальной постановке задается коллекция прямоугольных модулей

r 1, ••• , rп
(r,, r)

с соответствующими размерами h; х l;. Кроме этого, для каждой пары модулей
задается количество соединяющих их дорожек

w,1•

Требуется найти такое размещение

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

при условии, что прямоугольники не могут частично или полностью

накладываться друг на друга.

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

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

ная функция стоимости может выглядеть следующим образом:
п

C(S) = Ап.1ощадь (Sв1юппа ·Sширина) +

п

L L (Адорож.:а wij . dij + Al/(L10Жel/l/e (r; п rj )),
i=l ;=l

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

Глава

12.

Решение сложных задач

465

ПОДВЕДЕНИЕ итогов
Метод имитации отжига. является простым, но эффективным методом получения хотя и
не оптимальных, но достаточно хороших решений задач комбинаторного поиска.

12.7.

История из жизни. Только это не радио

Считайте, что это радио,

мой собеседник тихо рассмеялся.

Только это не радио.

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

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

(Eli Whitney)

считается изобретателем системы взаимозаменяемых

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

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

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

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

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

компенсирующими друг друга. Детали, изготовленные с размерами «В минус», вполне
могли быть сопряжены с деталями, изготовленными с размерами «в плюс», что давало

приемлемый результат. В этом и заключается суть селективной сборки.

Каждое устройство состоит из п деталей разных типов,

компании.

продолжал представитель

Для i-го типа детали (скажем, фланцевой прокладки) имеется

s;

ее экзем­

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

Предположим, устройство состоит из трех деталей, а сумма отклонений от нормы

всех деталей работающего устройства не должна превышать

50.

Умело подбирая для

каждого устройства детали «в плюс» с деталями «в минус», мы можем использовать

все

детали

рис.

12.11.

и

получить

три

работающих

устройства.

Эта

ситуация

показана

на

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

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

Часть

466

/.

«0
‘0
110
Рис.

12.11. Распределение деталей

Практическая разработка алгоритмов

з~

15.J
11.J

6~

з2.J

14~

между тремя устройствами с тем,

чтобы сумма отклоненнй для каждого не превышала 50
Целью бьmо совместить детали «в плюс» с деталями «в минус» друг с другом таким
образом, чтобы общее качество собранного устройства оставалось приемлемым. Опре­
деленно, задача выглядела как паросочетание в графах (см. разд.

18. 6). · Допустим,

мы

создадим граф, в котором вершины представляют экземпляры деталей, и соединим

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

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

я.

объявил

При условии, что все устройства состоят из двух деталей.

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

смех: «Все знают, что в устройстве больше двух деталей».
Так что предложенный мною алгоритмический подход был отвергнут. Расширение же

задачи до сопоставления более чем двух деталей превращало ее в задачу паросочета­

ния в гиперграфах4 , которая является NР-полной. Более того, только само время по­
строения графа может быть экспоненциально зависимым от количества типов деталей,
поскольку каждое возможное гиперребро (сборку) нужно будет создавать явным обра­
зом.

Пришлось начинать разработку алгоритма сначаnа. Итак, нужно собрать детали таким
образом, чтобы общая сумма отклонений от нормы не превышала определенного до­
пустимого значения . Это выглядело как задача упаковки контейнеров. В задаче такого
типа (см. разд.

20. 9)

требуется разложить набор элементов разного размера в наимен~.­

шее количество контейнеров с ограниченной емкостью

k.

В нашем случае контейнеры

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

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

4

Гиперrраф содержит ребра, каждое ИЗ которых может иметь более двух вершин. Их можно представить

в виде общих коллекций подмножеств вершин/элементов.

Глава

12.

467

Решение сложных задач

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

Задача разложения по контейнерам является NР-полной, однако естественно подходит

для решения методом эвристического поиска. Пространство решений состоит из вари­

антов разложения деталей по контейнерам. Каждому контейнеру мы сначала присваи­

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

Потом мы выполняем локальный поиск, перемещая детали из одного контейнера

в другой. Можно было бы перемещать по одной детали, но более эффективно обмени­
вать детали одинакового типа между двумя произвольно выбранными контейнерами.

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

операции обмена требовалось три произвольных целых числа: одно
детали (в диапазоне от

1

для выбора типа

до т) и два- для выбора контейнеров: между которыми

нужно выполнять обмен (в диапазоне от

1 до

Ь).

Ключевым решением стал выбор функции стоимости. Для каждой сборки был уста­

новлен жесткий общий предел отклонений от нормы, равный

k. Но что может быть

наилучшим способом оценки нескольких сборок? Можно было бы в качестве общей
оценки просто возвращать количество приемлемых сборок
от

1 до

целое число в диапазоне

Ь. Хотя эта величина и оставалась той, которую мы хотели оптимизировать, она

не была достаточно чувствительна к продвижению в направлении правильного реше­

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

k для сборки. Это стало бы значительно более удачной отправной точкой для

дальнейшего поиска решения, чем первоначальная ..

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

1

пункт, а каждой неработающей

значительно меньшую оценку,

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

ее общее отклонение от нормы превышало

k.

k. Оценка не­

в зависимости от того, насколько

Таким образом, оптимизатор станет пы­

таться максимизировать количество работающих устройств, после чего будет стараться

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

устройства содержат по

8 деталей

важных типов. Детали некоторых типов были более

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

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

Я наблюдал за работой программы имитации отжига. Первые четыре сборки она выда­

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

5

и

6

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

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

468

Часть

/.

Практическая разработка алгоритмов

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

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

12.8.

История из жизни. Отжиг массивов

В истории из жизни, приведенной в разд.

3.9,

рассказывалось, как мы использовали

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

Beckman Instruments)

Southem Array Maker

(производства

выкладывает дискретные последовательности олиго­

нуклеотидов в виде шестидесяти четырех параллельных рядов на полипропиленовую

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

ку вдоль определенных рядов и столбцов массива. На рис.

12.2 показан пример созда­
ния массива всех 24 = 16 пуриновых (А или G) 4-меров путем создания префиксов
вдоль рядов и суффиксов вдоль столбцов.

Суффикс

АА

Префикс

АА

АААА

AG
GA
GG

AGAA
GAAA
GGAA
Рис.

12.12.

AG
AAAG
AGAG
GAAG
GGAG

GA
AAGA
AGGA
GAGA
GGGA

GG
AAGG
AGGG
GAGG
GGGG

Массив всех пуриновых 4-меров

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

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

Array Maker.

64)

на устройстве

Southem

Нам надо было запрограммировать создание рядов и столбцов, чтобы

реализовать множество строк

S.

Мы доказали, что задача разработки плотных массивов

является NР-полной, но это не имело большого значения, поскольку решать ее в любом
случае пришлось мне и моему студенту Рики Брэдли

(Ricky Bradley).

Глава

12.

Решение сложных задач

469

Нам придется использовать эвристический метод,

сказал я ему.

Как смодели­

ровать эту задачу?

Каждую строку можно разбить на составляющие ее префиксные и суффиксные

пары. Например, строку АСС можно создать четырьмя разными способами: из префик­

са

«»

(пустая строка) и суффикса АСС, префикса А и суффикса СС, префикса АС

и суффикса С или префикса АСС и суффикса

« ».

Нам нужно найти наименьший набор

префиксов и суффиксов, которые совместно позволяют получить все такие строки,

сказал Рики.

Хорошо. Это дает нам естественное представление для метода имитации отжига.

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

Какую функцию стоимости можно использовать?

Нам требуется массив как можно меньшего размера, который охватывает все стро-

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

S,

которые еще

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

после каждого принятия перехода, и за ее работой было интересно наблюдать. Она

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

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

раций не наблюдалось никакого реального улучшения.

Кажется, что программа не может определить, когда она получает правильное

решение,

предположил я.

Функция стоимости оценивает только минимизацию

большей стороны массива. Давай добавим в нее выражения для оценки действий с дру­
гой стороной.

Рики внес соответствующие изменения в функцию стоимости, и мы снова запустили

программу. На этот раз она уверенно обрабатывала и другую сторону. Более того, на­
ши массивы стали выглядеть как тонкие прямоугольники, а не квадраты.

Ладно. Давай добавим в функцию стоимости еще одно выражение, чтобы массивы

стали квадратными.

Рики опять изменил программу. На этот раз массивы на выходе имели правильную

форму, и поиск двигался в нужном направлении. Но происходило это по-прежнему
медленно.

Многие операции вставки затрагивают недостаточное количество строк. Может

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

Часть

470

1.

Практическая разработка алгоритмов

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

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

+
+
+

+
+
+

обменять префикс/суффикс в массиве с префиксом/суффиксом вне массива;

swap add —

добавить в массив случайный префикс/суффикс;

удалить из массива случайный префикс/суффикс;

delete —

useful add —

добавить в массив префикс/суффикс с наибольшей пригодностью;

useful delete string add —

удалить из массива префикс/суффикс с наименьшей пригодностью;

выбрать произвольную строку вне массива и добавить наиболее при-

годный префикс и/или суффикс, чтобы покрыть эту строку.

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

нечная функция стоимости определялась следующим образом:

. )2
. ( тах-тт
cost=2xmax+m1n+
+4(strtotal — s «;п
… ) ‘
4
где тах str;п

максимальный размер чипа,

количество строк из множества

min — минимальный его размер, str10101 = jSj,
S, находящихся в настоящее время в чипе.

а

Насколько хороший результат мы получили? На рис.

12.13 показано четыре этапа сжа­
5716 однозначных 7-меров вируса иммунодефицита чело­
500, 1000 и, наконец, 5750 итераций.

тия массива, состоящего из
века (ВИЧ),

после О,

,,_…, .. .., .._.,;
…» …. » .. «

. …

,(«» •

……..»

:;:—.: ~.: -~ . :·::~ ::~·;. ;~:.:::. .:. :. :;

.

r~~~~~;-~~~ Miifbl~~

~~!;»; ~ ~··~-:::·: · 1)»)

всех членов,

взаимно

простых

к числу М,

малыми.

FFТ(Q).

For j

=

1 to n

si = Sample (Q)
Если

((d

=

GCD(Sj, Sk))

Возвращаем

(d)

> 1)

и

(d

делит М)),

для некоего

k < j

как множитель для М

В противном случае докладьmаем,

что множитель

не

найден

Время выполнения каждой из этих операций пропорционально п, а не М = 0(2п), т. е.
это повышение эффективности до экспоненциального времени по сравнению с разло­
жением на множители методом деления и проверки. Неизвестно о существовании ал­
горитмов с полиномиальным временем исполнения для разложения целых чисел

на

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

Глава

12.

Решение сложных задач

479

Перспективы квантовых вь1числений

12.10.5.

Каковы перспективы квантовых вычислений? Во время работы над этой книгой в

2020 году

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

+

Квантовые вычисления

это стоящая вещь, и они будут воплощены в жизнь.

Я, как человек, который в течение сорока лет наблюдает за циклами ажиотажа
в технологиях, развил достаточно надежный нюх на всякий отстой, и в настоящее

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

стиции в нее крупными компаниями и другими важными игроками. И я бы очень

удивился, если бы все это полностью сошло на нет.

+

Квантовые вычисления вряд ли окажут воздействие на рассматриваемые в этой
книге задачи.

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

специализированных применений

наподобие того, как самые быстрые суперком­

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

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

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

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

+

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

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

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

Поживем

увидим. Я с нетерпением ожидаю возможности выпустить четвертое из­

дание этой книги, возможно, в

2035

году, чтобы узнать, насколько точными были мои

предсказания.

Следует иметь в виду, что между описанными здесь «квантовыми» вычислениями и
настоящими квантовыми компьютерами существует несколько важных различий, хотя

Часть

480

/.

Практическая разработка алгоритмов

я считаю, что мое описание раскрывает суть их работы. Тем не менее в настоящих
квантовых компьютерах:

+

Роль вероятностей И2рают комплексные числа.
Вероятности

это действительные числа в диапазоне от О до

для всех элементов вероятностного пространства составляет
ности

1.

< 1,

сумма которых

А квантовые вероят­

это комплексные числа, квадраты которых находятся в диапазоне от О до

< 1,
1.

и сумма которых для всех элементов вероятностного пространства составляет

Вспомним, что рассмотренный

в разд.

5. 9

алгоритм быстрого преобразования

Фурье применим к комплексным числам, что и является основой его мощи.

+

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

формация об остальных 2п

— 1 состояниях

теряется. Таким образом, делать много­

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

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

дает эффект многократных выборок.
Основное препятствие квантовых вычислений

вопрос, как извлечь требуемый

ответ, поскольку это измерение дает только крошечную долю информации, зало­
женной в системе

Q.

Если измеряются только некоторые, а не все кубиты системы

тогда оставшиеся кубиты также «измеряются»

соответственно разрушается. Это явление называется запутанностью

ment),

Q,

в том смысле, что их состояние

(entangle-

и оно представляет собой настоящий источник волшебства квантовых вычис­

лений.

+

Настоящие квантовые системы с легкостью испытывают сбои

декогерируют

(decohere).
Манипулирование отдельными атомами для выполнения сложных задач
детская игра. Квантовые компьютеры
продолжительной работы

это не

чтобы добиться от них как можно большей

обычно работают при чрезвычайно низких температу­

рах и в экранированной среде. При современном уровне развития технологий эта
продолжительность не очень велика, что ограничивает степень сложности алгорит­

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

+

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

Я позволил себе определенные вольности в описании того, как именно можно вы­
полнять инициализацию квантовых состояний и какие операции они могут выпол­

нять. Квантовые вентили, по сути, это унитарные матрицы, умножение на которые
изменяет вероятности системы

Q.

Эти операции подробно определены свойствами

квантовой механики, и такие детали весьма важны.

Глава

Решение сложных задач

12.

481

Замечания к главе
Метод

имитации

отжига

(Кirkpatrick) и прочих

был

первоначально

рассмотрен

в

статье

Киркпатрика

где также обсуждалось его применение для решения

[KGV83],

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

основаны на материале из книги Аартса

12.6.4

[АК89]. Компания

D-Wave

(Aarts)

и Корста

(Korst)

производит класс квантовых компьютеров, в которых пред­

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

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

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

вершин. Это повышает вероятность локального улучшения. Но чтобы эффективно
поддерживать порядок полученного маршрута, требуются более сложные структуры
данных, которые рассматриваются в книге Фредмана

(Fredman)

и прочих

[FJM093].

Разные эвристические методы поиска подробно представлены в книге Аартса
Ленстра

(Lenstra) [AL97],

(Aarts)

и

которую я рекомендую всем, кто хочет углубить свои знания

в области эвристических методов поиска. В этой книге описан алгоритм поиска с за­
прета.ми

(tabu search),

который является разновидностью метода имитации отжига,

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

не­
давно рассмотренные состояния. Алгоритм муравейника рассматривается в книге До­
риго (Dorigo) и Штутцла (Stutzle) [DT04]. В книге Ливната (Livnat) и Паnадимитроу
(Papadimitriou) [LP 16] выдвигается теория, объясняющая повсеместную некачествен­

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

менее Михалевич

(Michalewicz)

и Фогель

(Fogel)

в своей книге

[MFOO]

излагают более

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

Наша работа по использованию метода имитации отжига для сжатия массивов ДНК

была представлена в журнале

[BS97]. Дополнительную информацию по селективной
(Pugh) [Pug86] и в статье Колларда (Coullard) и других

сборке вы найдете в статье Пью

[CGJ98].
Если мое введение в квантовые вычисления пробудило у вас интерес к ним, я бы на­
стоятельно рекомендовал вам обратиться к более подробным источникам информации
на эту тему. В частности, книги

[Aar3, Ber9, DPV08]

среди прочих предоставляют

интересное рассмотрение квантовых вычислений, а в книге Яновского
Манучи

(Mannucci)

Захватывающий блог Скотта Ааронсона
Ыоg/)

представляет

(Yanofsky)

и

[УМО8] этот материал излагается особенно понятно и доступно.
последние

(Scott Aaronson, https://www.scottaaronson.com/

достижения

в

алгоритмах

квантовых

а также более широкий взгляд на теорию сложности вычислений.

вычислений,

Часть

482

12.11.

/.

Практическая разработка алгоритмов

Упражнения

Частные случаи сложных задач
1. [5]

Костяшки домино представляют пары целых чисел (х» у,), где каждое из значений Х;

и у; является целым числом в диапазоне от

т пар целых чисел
цепочек [(х; 1 ,

Yil),

1 до

п. Пусть

S будет

последовательностью из

[(х 1 , у 1 ), (х 2 , у2 ), ••• , (хт, Ут)]. Суть игры состоит в создании длинных

(х; 2 , у 12 ), •• » (х 11 , у 11 )] таким образом, чтобы

YiJ

= x,(J+J)· Костяшки можно

разворачивать, т. е. (х;, у,) эквивалентно (у» х;). Дпя последовательности

(3, 5), (2, 3), (3, 8)] наиболее
(3, 8)] и [(1, 3), (3, 2), (2, 4)].

S = [(1, 3), (4, 2),
[(4, 2), (2, 3),

длинными цепочками среди прочих будут

а) Докажите, что задача вычисления самой длинной цепочки домино является NР-пол­
ной.

Ь) Предоставьте эффективный алгоритм для вычисления самой длинной цепочки доми­
но, в которой числа увеличиваются с увеличением длины цепочки. Например, для пре­

дыдущей последовательности

2. [5]

Даны граф

S

такими цепочками будут[(,

3), (3, 5)]

и

[(2, 3), (3, 5)].

G = (V, Е) и две различные вершины х и у в этом графе. За посещение
v выдается определенное количество жетонов t(v).

каждой вершины

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

Ь) Предоставьте эффективный алгоритм для вычисления такого пути, если граф

G

явля­

ется бесконтурным ориентированным графом.

3. [8}

В задаче гамильтонова дополнения требуется разработать алгоритм для добавления

к графу

G

такого наименьшего количества ребер, которое сделает его гамильтоновым

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

Аппроксимирующие алгоритмы
4. [4]

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

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

5. [5]

Имеется следующий эвристический алгоритм поиска вершинного покрытия: создает­

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

6. [5]

В задаче максимального разреза графа

вершин

V на два

G = ( V,

Е) требуется разделить множество

непересекающихся подмножества А и В таким образом, чтобы максими­

зировать количество ребер (а, Ь) Е Е, где а Е А, Ь Е В. Рассмотрим следующий эвристи­
ческий алгоритм поиска максимального разреза. Сначала помещаем вершину

множество А, а вершину

v2 —

v1

в под­

в подмножество В. Каждую оставшуюся вершину помещаем

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

Глава

12.

483

Решение сложных задач

7. [5] В задаче об упаковке в контейнеры дается п элементов, веса которых равны w 1, w2,
… , wn. Нужно найти наименьшее количество контейнеров, в которые можно упаковать
эти п элементов, при этом емкость каждого контейнера не превышает один килограмм.

В эвристическом алгоритме «первый подходящий»

(first-fit)

объекты рассматриваются

в порядке, в котором они представляются. Каждый объект укладывается в первый же
контейнер, в который он помещается. Если такого контейнера нет, объект помещается

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

8. [5]

Для только что описанного эвристического алгоритма «первый подходящий» приве­

дите пример экземпляра задачи, решение которого дает количество контейнеров, пре­

вышающее оптимальное максимум в

9. [5]

5/3

раз.

Для заданного неориентированного графа

степень ~

d,

G = (V,

Е), каждый узел которого имеет

разработайте эффективный алгоритм вычисления независимого множества,

размер которого, по крайней мере, в

l/(d + 1)

раза меньше, чем размер наибольшего не­

зависимого множества.

1О. [5]

Целью задачи раскраски вершин графа

шинам множества

G

= ( V, Е) является назначение цветов вер­

V таким образом, чтобы вершины на концах каждого ребра были

окрашены в разные цвета. Предоставьте алгоритм для раскраски графа
Л

+ 1 разными

11. [5}

G

не более чем

красками, где Л представляет максимальную степень вершин графа

G.

Продемонстрируйте, что можно решить любую головоломку су доку, вычислив ми­

нимальную вершинную раскраску конкретного, должным образом созданного графа,
содержащего (9х9)+9 вершин.

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

12. [5]

Разработайте и реализуйте эвристический алгоритм для решения задачи минимиза­

ции ширины полос, рассматриваемой в разд.

13. [5]

Разработайте и реализуйте эвристический алгоритм для решения задачи максималь­

ной приемлемости, рассматриваемой в разд.

14. [5]

17.1 О.

Разработайте и реализуйте эвристический алгоритм для решения задачи поиска мак­

симальной клики, рассматриваемой в разд.

15. [5]

16. 2.

19.1.

Разработайте и реализуйте эвристический алгоритм для решения задачи минималь­

ной вершинной раскраски, рассматриваемой в разд.

16. [5]

Разработайте и реализуйте эвристический алгоритм для решения задачи минималь­

ной реберной раскраски, рассматриваемой в разд.

17. [5]

19. 7.

19. 8.

Разработайте и реализуйте эвристический алгоритм для решения задачи поиска

минимального разрывающего циклы множества вершин, рассматриваемой в разд.

18. [5}

19.11.

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

множества, рассматриваемой в разд.

21.1.

484

Часть

/.

Практическая разработка алгоритмов

«Квантовые)) вычисления

19. [5}

Дана п-кубитовая «квантовая» система

ее N

=

Jack (Q,

Q,

у которой начальные вероятности всех

2п состояний одинаковые и составляют p(i)

=

1/2п. Предположим, операция

О») удваивает вероятность состояния, в которой значения всех кубитов равны

нулю. Сколько нужно выполнить таких операций Jack, чтобы вероятность выборки это­
го нулевого состояния составляла~

20. [5]

112?

Для задачи выполнимости создайте: (а) экземпляр задачи из п переменных, для ко­

торого есть ровно одно решение, и (Ь) экземпляр задачи из п переменных, для которого

есть ровно 2п разных решений.

21. [3}

Дана последовательность первых десяти чисел, кратных

11,

т. е.

11, 22, 33, … , 110.

Из этой последовательности произвольно выбираем два числа (х и у). Какова вероят­
ность, что нод(х, у)=

22. [8]

11?

На веб-сайте квантовых вычислений компании IВМ

computing/)

(https://www.ibm.com/quantum-

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

числений. Изучите пример программы квантовых вычислений, запустите ее на испол­
нение и наблюдайте за результатами.

LeetCode
1.

https://leetcode.com/proЫems/split-array-with-same-average/

2. https://leetcode.com/problems/smallest-sufficient-team/
3. https ://leetcode.com/pro Ыems/longest-palind rom ic-su bstring/

HackerRank
1. https://www.hackerrank.com/challenges/mancala6/
2. https://www.hackerrank.com/challenges/sams-puzzle/
3. https://www.hackerrank.com/challenges/walking-the-approximate longest-path/
Задачи

no

программированию

Эти задачи доступны на сайте

1. «Euclid

ProЬem», глава

2. «Chainsaw Massacre»,
3. «Hotter Colder»,

глава

4. «Useless Tile Packers»,

7,

задача 1О104.

глава

7,

https://onlinejudge.org:
7,

задача

задача

глава

7,

10043.

10084.
задача

10065.

ГЛАВА

13

Как разрабатывать алгоритмы?

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

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

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

в основе всех комбинаторных алгоритмов. А каталог задач во второй ее части помо­

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

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

необходимое условие.

Ключом к разработке алгоритмов (или любой другой деятельности, связанной с ре­

шением задач) является умение задавать самому себе вопросы, чтобы направлять
свой процесс мышления: «Что будет, если я попробую такой способ? А если теперь
попробовать другой?» Когда на каком-либо этапе вы столкнетесь с затруднениями,
лучше всего перейти ко второму вопросу. Самым полезным участником любой мозго­
вой атаки является тот, кто постоянно задает вопрос: «А почему нельзя применить
такой-то способ?», а не тот, кто дает ответ на этот вопрос. В конце концов, первый уча­
стник предложит подход, который второй не сможет забраковать.
С этой целью мы предоставляем последовательность вопросов, которая должна на­
правлять вашу деятельность в поиске правильного алгоритма для решения стоящей

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

в журнал. Правильным ответом на вопрос: «Можно ли сделать это таким способом?»
является не просто: «Нет», а «Нет, потому что».». Четко излагая свои мысли по поводу
отрицательного результата, вы сможете проверить, не упустили ли вариант, о котором

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

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

это поиск общей картины, основы, на которой

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

вильном ли уровне находятся ваши рассуждения. Если у вас нет глобального плана
(т. е. стратегии) для решения

поставленной перед вами задачи, то нет никакого

смысла размышлять о правильной тактике. Примером стратегического вопроса явля-

Часть

486

/.

Практическая разработка алгоритмов

ется: «Могу ли я смоделировать это приложение в виде задачи создания алгоритма

на графах?» А тактическим вопросом может быть: «Какую структуру данных следует
использовать для представления моего графа

список смежности или матрицу смеж­

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

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

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

таких как динамическое программирование, алгоритмы на графах и

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

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

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

грамме космических исследований США под названием
ный материал»)

([Wo79)).

«The Right Stuff»

(«Правиль­

В этом отрывке описывались радиопереговоры пилотов­

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

ствия из списка для нештатных ситуаций: «Проверил закрылки. Провери.ri двигатель.
Крылья в порядке. Сбросил».» Эти пилоты обладали «Правильным материалом» и бла­
годаря этому иногда им удавалось избежать гибели.

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

13.1.

Список вопросов

для разработчика алгоритмов
l.

Понимаю ли я задачу?

Из чего состоит вход?

Какой именно требуется результат?

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

было решить вручную? Что будет, если я попытаюсь решить его?

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

Каков размер типичного экземпляра решаемой задачи?
ментов?

l

ООО ООО элементов? Еще больше?

элементов?

l ООО

эле­

Глава

13.

487

Как разрабатывать алгоритмы?

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

Скольк,о времени и усилий могу я вложить в реализацию? Буду ли я ограничен
простыми алгоритмами, которые можно реализовать в коде за один день, или у

меня будет возможность поэкспериментировать с несколькими подходами и по­
смотреть, какой из них лучше?

Задачу какого типа я пытаюсь решить? Численную задачу, задачу теории графов,
геометрическую задачу, задачу со строками или задачу на множестве? Какая

формулировка выглядит самой легкой?

2.

Могу ли я найти простой детерминированный или эвристический алгоритм для
решения стоящей передо мной задачи?

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

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

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

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

например, выбирая в первую очередь наибольший элемент, наи­

меньший элемент или произвольный элемент?
Если можно, то при каком входе этот эвристический алгоритм работает хо­
рошо? Соответствует ли вход такого типа данным, актуальным для моего
приложения?
При каком входе этот эвристический алгоритм работает плохо? Если не
удастся найти примеры плохих входных экземпляров, то могу ли я пока­

зать, что алгоритм всегда работает хорошо?
0

Сколько времени требуется моему эвристическому алгоритму, чтобы вы­
дать ответ? Можно ли его реализовать простым способом?

3.

Есть ли моя задача в каталоге задач этой книги?

Что известно о моей задаче? Существует ли реализация решения, которую я могу
использовать?

Искал ли я свою задачу в правильном месте каталога? Просмотрел ли я все
рисунки? Проверил ли я все возможные ключевые слова в алфавитном указа­
теле?

Существуют ли в Интернете ресурсы, имеющие отношение к моей задаче? Ис­
кал ли я в Google
www.algorist.com?

и

Google Scholar?

Посетил ли я веб-сайт этой книги

488
4.

Часть

/.

Практическая разработка алгоритмов

Существуют ли специальные случаи стоящей передо мной задачи, которые я
знаю, как решить?

Можно ли эффективно решить задачу, игнорируя некоторые входные параметры?

Станет ли задача проще, если присвоить некоторым входным параметрам три­
виальные значения,

такие как О или

1?

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


5.

Не является ли моя задача специальным случаем одной из общих задач каталога?

Какая из стандартных парадигм разработки алгоритмов наиболее соответствует мо­
ей задаче?

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

Можно ли разбить задачу на две меньших задачи

например, посредством дво­

ичного поиска? Поможет ли метод разделения элементов на большие и малень­
кие или правые и левые? Может быть, стоит попробовать алгоритм типа «разде­
ляй и властвуй»?

Не обладают ли объекты входного множества естественным порядком слева на­
право

как,

например, символы в строке, элементы перестановки

или листья

дерева с корнем? Могу ли я применить динамическое программирование, чтобы
воспользоваться этим упорядочиванием?

Нет ли повторяющихся операций, таких как поиск наименьшего/наибольшего

элемента? Могу ли я применить специальные структуры данных для ускорения
этих операций

например, словари/таблицы хеширования или пирамиды/оче­

реди с приоритетами?

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

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

Имеет ли моя задача какое-либо сходство с задачей выполнимости, задачей ком­
мивояжера или какой-либо другой NР-полной задачей? Может ли задача быть
NР-полной и вследствие этого не иметь эффективного алгоритма решения? Есть
ли эта задача в списке из приложения к книге

6.

[GJ79]?

Я все еще не знаю, что делать?

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

22.4.

Глава

13.

Как разрабатывать алгоритмы?

489

Снова пройдитесь по этому списку вопросов с самого начала. Изменились ли ответы на некоторые вопросы при новом проходе по списку?

Решение задач не является точной наукой. Отчасти это искусство, а отчасти
ло. Навык решения задач

ремес­

это один из полезнейших навыков, и его стоит приобре­

сти. Моей любимой книгой по решению задач является книга

«How to Solve lt»

(«Как

решить задачу») [Ро57), содержащая каталог методов решения задач. Я обожаю его
просматривать

как с целью решения конкретной задачи, так и просто ради удоволь­

ствия.

13.2.

Подготовка к собеседованию

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

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

Прежде всего следует понимать, что по теме разработки алгоритмов люди знают то,

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

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

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

Задачи по разработке алгоритмов имеют склонность просачиваться в процесс собесе­

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

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

(https://www.hackerrank.com/).

По этим задачам программирования проверяются ско­

рость и правильность кодирования для отсеивания наименее обещающих кандидатов.
Но уровень успеха решения этих задач программирования повышается при постоянной
практике. Для этого студенты могут попытаться попасть в команду АСМ ICPC
(Association for Computing Machinery Intemational Collegiate Programming Contest Международная студенческая олимпиада по программированию Ассоциации по вы­
числительной технике) своего учебного заведения. Каждая такая команда состоит из
трех члейов, которые совместно должны решить от пяти до десяти задач по програм­
мированию в течение пяти часов. Эти задачи часто алгоритмического типа и обычно
интересные. Членство в такой команде принесет много пользы, даже если ваша коман­
да и не попадет на региональную олимпиаду.

490

Часть

/.

Практическая разработка алгоритмов

Для самообучения я могу порекомендовать проверить себя в решении задач кодирова­

ния на сайтах для подготовки к техническим собеседованиям с автоматической оцен­
кой ответов

LeetCode

например, того же

HackerRank (https://www.hackerrank.com)

или

(https:/Лeetcode.com). Кстати, в конце каждой главы я рекомендую соответ­

ствующие задачи по программированию на каждом из этих двух сайтов. Начинайте

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

чтобы улучшить свои навыки в этой области. Я могу скромно порекомендовать свою

книгу «Programming Challengers» 1 [SROЗ], которая предназначена для использования
в качестве учебного пособия при решении подобных задач по программированию. Если
вам нравится книга, которую вы читаете сейчас, то вполне возможно, что и другая моя

книга окажется для вас полезной.

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

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

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

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

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

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

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

исследовательских

работ

в

стартапной

компании

General Sentiment

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

1 Доступен

русский перевод этой книги: Стивен С. Скиена и Мигель А. Ревилла «Олимпиадные задачи по

программированию».

,

Глава

13.

Как разрабатывать алгоритмы?

491

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

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

чтобы получить представление об уровне сложности, на котором

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

General Sentiment, —

многие из

них затем пошли работать в еще более интересные компании.

Конечно же, я настоятельно рекомендую другим компаниям продолжать строить свои
собеседования на вопросах по алгоритмам. Чем больше компаний делают это, тем

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

@StevenSkiena.

ЧАСТЬ

11

Каталог
алгоритмиче·ских задач

ГЛАВА

14

Описание каталога

Вторая часть книги содержит каталог алгоритмических задач, которые часто возни­

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

Как использовать этот каталог? Если вы знаете на_звание своей задачи, то с помощ1ою
алфавитного указателя или оглавления найдите ее описание. Прочитайте весь матери­
ал, относящийся к задаче, поскольку он содержит ссылки на связанные с ней задачи.
Пролистайте каталог, изучая рисунки и названия задач. Возможно, что-то покажется

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

каждая

задача внесена в него несколько раз под разными ключевыми словами.

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

чем формальные описания. Так, пример, касающийся минимального остовного дерева,
демонстрирует

кластеризацию

точек

с

помощью

минимальных

остовных

деревьев.

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

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

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

несложного

решения,

которым

можно

ограничиться

в

простых

случаях,

и ссылки на более мощные алгоритмы, чтобы вы могли ими воспользоваться, если

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

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

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

22 для

многих из этих реали­

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

(www.algorist.com)

даются ссылки для их загрузки.

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

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

эмпирическому сравнению алгоритмов, если таковые имеются. Эта информация долж-

Часть

496

11.

Каталог алгоритмических задач

на представлять интерес для студентов, исследователей и практиков, не удовлетворив­
шихся рекомендуемыми решениями.

14.1.

Предостережения

Приведенный в этой части материал является каталогом алгоритмических задач, а не

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

указать вам правильное направление, чтобы вы могли

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

+

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

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

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

+

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

для создания вашего собственного кода. Другие встроенЬ1 в большие системы, и их
может быть слишком трудно извлечь и исполнять отдельно. Все эти решения со­

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

+

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

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

+

22.1.

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

рекомендациям,

как о положительных, так и об отрицательных. Особый интерес

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

ГЛАВА

15

Структуры данных

Структуры данных

это базовые конструкции для построения приложений. Чтобы

максимально полно использовать возможности стандартных структур данных, необхо­

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

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

дело далеко не тривиальное,

на которые даются ссылки, окажутся вам полезными в качестве

моделей, даже если они· и не в точности подходят для ваших задач. Некоторые важные

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

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

+

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

(SWl 1] —

удачными рисунками, показывающими алгоритмы в действии. Имеются версии
книги для языков С, С++ и

+

Java;

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

[Weil 1]-

чем на алгоритмы. Имеются версии книги для языков

+
+

С, С++ и

Java с активным использованием
структур данных JDSL (Java Data Structures Library);
версия книги для

[GTG 14] ки

Java,

[Bra08] —

Ada;

авторской библиоте­

хорошее рассмотрение более продвинутых структур данных, чем те, ко-

торые рассматриваются в других учебниках с реализацией на языке С++.
Книга

[MS 18]

содержит всесторонний обзор современных исследований в области

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

будет удивлен объемом проводимых исследований.

15.1.

Словари

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

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

q

(рис.

15.1 ).

498

Часть

11.

Каталог алгоритмических задач

Qucry’>

Quelling
Quencblng
Queried
Queries
Queгy

+—

Quest

Вход

Рис.

Выход

15.1.

Поиск в словаре

Обсуждение
Абстрактный тип данных «словарь»

(dictionary)

является одной из наиболее важных

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

Однако на практике важнее избежать применения неподходящей

с_труктуры данных, чем найти самую лучшую.

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

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

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

Выбирая структуру данных для словаря, ответьте на несколько вопросов.

+

Сколько элементов будет содержать структура данных?
Известно ли их количество заранее? Достаточно ли будет для решения вашей зада­
чи простой структуры данных, или же размер задачи настолько велик, что следует

беспокоиться о нехватке памяти или производительности виртуальной памяти?

+

Известна ли относительная частота операций вставки, удШ1ения и поиска ?
Статические структуры данных (например, упорядоченные массивы) достаточны
для приложений , в которых структура данных не подвергается изменениям после ее
первоначального создания. Полудинамические структуры данных, поддерживаю­
щие только операцию

вставки

и

не допускающие удаление данных,

значительно проще, чем полностью динамические .

реализовать

Глава

+

15.

499

Структуры данных

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

+

Критична ли скорость каждой отдельной операции WIИ требуется минимизиро­
вать только общий объем работы, выполняемый всей программой?

Когда время реакции критично

например, в программе управления аппаратом

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

например, для выяснения, кто из нарушителей закона является политиче­

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

если удается выявить их всех с минимальными общими затратами времени.

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

для большинства приложений

вполне достаточно стандартных контейнеров. Тем не менее иногда полезно знать, что
именно находится «под капотом»:

+

неупорядоченные списки WIИ массивы.
Для небольших наборов данных самой простой в обслуживании структурой будет
неупорядоченный массив. По сравнению с аккуратными и компактными массивами

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

50-100

элементов, линейное время поиска в спи­

ске или массиве сведет на нет все его преимущества.

Особенно Йнтересным и полезным вариантом является самоорганизующийся список
(self-orgaпiziпg

list),

в котором вставляемый или просматриваемый элемент переме­

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

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

+

упорядоченные списки WIИ массивы.

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

если только вы

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

500

Часть

11.

Каталог алгоритмических задач

+

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

что угодно другое) и целых чисел в диапазоне от О до т
сопровождается

массивом

из т корзин,

— 1.

реализованных

в

Создание хеш-таблицы

виде

неупорядоченного

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

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

корзины/списка. Хеширование подробно обсуждалось в разд.

3. 7.

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

приступить к реализации хеш-таблицы, ответьте на несколько вопросов.

Каким образом обрабатывать коллизии?
Использование открытой адресации вместо корзин позволит получить неболь­

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

Каким должен быть размер таблицы?
Если задействуются корзины, то значение т должно быть приблизительно рав­
ным

максимальному

количеству элементов,

которое

вы

планируете

поместить

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

шать ожидаемое максимальное количество элементов, по крайней мере, на

50%.

30-

Выбор в качестве т простого числа компенсирует возможные недостатки

неудачной хеш-функции.

Какую хеш-функцию использовать?
Для строк должна работать, например, такая формула:

1s1-1

H(S) = alsl +

I

alsl-(HI) х char(s; )(modт),

1=0

где а

размер алфавита;

возвращает его

код.

Для

char(x) —

функция, которая для каждого символа х

реализации

эффективного

вычисления

этой

хеш­

функции используйте правило Горнера (или заранее вычисляйте значения ах),
как показано в разд.

16. 9.

Версия этой хеш-функции (см. разд.

6. 7)

обладает од­

ним интересным свойством, заключающимся в том, что хеш-коды последова­

тельных окон строки размером в
время, а не за время

k

символов можно вычислять за постоянное

O(k).

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

Глава

15.

Структуры данных

501

бранная хеш-функция окажется не самой лучшей. Использование неудачной хеш­

функции может заметно снизить производительность любого приложения;

+ двоичные деревья поиска.
Двоичные деревья представляют собой элегантные структуры данных, которые

поддерживают
(см. разд.

3.4).

быстрое

исполнение

операций

вставки,

удаления

и

запросов

Главное отличие между разными типами деревьев проявляется в том,

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

ных вставок,

большинство

приложений

не обеспечивают случайность

вставок.

И действительно, несбалансированные деревья поиска, созданные вставками эле­
ментов

в

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

порядке,

никуда

не

годятся,

поскольку

их

производи­

тельность ухудшается до производительности связных списков.

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

2/3,

считаются устаревшими, и наи­

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

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

зуемого сбалансированного дерева, как профессионализм программиста, реализо­
вавшего его;

+

В-деревья.
Для наборов данных настолько больших, что они не помещаются в оперативную

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

порядков. Подобное падение производительности в меньшем масштабе может на­

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

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

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

в частности,

такие аппаратные характеристики, как размер страницы и виртуальное/реальное ад­

ресное пространство. Нечувствительные к кэшированию шtгоритмы (cache-oЫivious

algorithms)

позволяют уменьшить значение этих факторов.

Часть

502

11.

Каталог алгоритмических задач

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

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

+

списки с пропусками.

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

примерно

lg

принимается случайным образом. В

структуре задействовано

п списков, и размер каждого из них приблизительно вдвое меньше раз­

мера списка, расположенного уровнем выше. Поиск начинается в самом коротком

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

время

исполнения

запроса составляет

O(lg п).

Основные достоинства

с пропусками по сравнению со сбалансированными деревьями

списков

легкость анализа

и реализации.

Реализации

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

STL (Standard Template Library). Более подробное
STL и стандартной библиотеки С++ можно
найти в книгах [Jos12], [MeyOl] и [MDSOI]. Небольшая библиотека структур данных
Java Colections (JC) входит в стандартный пакет утилит Java java.util.
руководство по использованию библиотеки

Библиотека

LEDA

(см. раз.

22.1.1)

предоставляет полную коллекцию словарных струк­

тур данных, реализованных на языке С++, включая хеш-таблицы, совершенные хеш­

таблицы, В-деревья, красно-черные деревья, деревья случайного поиска и списки
с пропусками. В результате экспериментов, описанных в книге [МN99], было опреде­
лено, что наилучшим вариантом для словарей являются хеш-таблицы, а списки с про­

пусками и (2,4)-деревья (частный случай В-деревьев) являются наиболее эффективны­
ми древоподобными структурами.
ПРИМЕЧАНИЯ
В книге

[Knu97a]

представлено наиболее подробное описание и анализ основных словар­

ных струкrур данных. Но в ней отсутствуют некоторые современные струкrуры данных,
такие как красно-черные и косые деревья. Чтение книг Дональда Кнута послужит хоро­
шим введением в предмет для всех изучающих теорию вычислительных систем.

В книге

[MS 18]]

дан всесторонний обзор современных исследований в области струкrур

данных. Другие исследования представлены в книгах [МТ90Ь] и
учебниками по словарным струкrурам данных являются

[GBY91]. Хорошими
[Sed98], [Weil 1] и [GTGl4]. Мы

отсылаем читателя к этим работам, чтобы не приводить здесь ссылки на описания струк1УР. данных, обсуждавшихся ранее.

1996

году было посвящено реализациям элементарных струкrур

данных, включая словари (см.

Соревнование

данных и код можно загрузить с веб­

сайта

DIMACS

в

[GJM02]). Наборы
http://dimacs.rutgers.edu/Challenges.

Глава

15.

503

Структуры данных

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

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

[VitOI].

Нечувствительные к кэши­

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

буя при этом явной информации о параметре размера блока Ь. Таким образом, хорошую

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

тельных к кэшированию, приведено в

[ABFOS]

и

[Dem02].

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

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

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

Родственные задачи

Сортировка (см. разд.

15.2.

17.1),

поиск (см. разд.

17.2).

Очереди с приоритетами

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

OctoЬer

15.2).

30

DecemЬer7
Jпly

4

January 1
FeЬruary

2

DecemЬer25

January 30

Вы ход

Вход

Рис.

15.2. Очередь с

приоритетами

504

Часть

//.

Каталог алгоритмических задач

Обсуждение

Очереди с приоритетами

(priority queues)

являются полезными структурами данных

при моделировании поведения систем, особенно при сопровождении набора заплани­

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

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

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

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

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

t

Какие еще операции вам потребуются?

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

t

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

t

Можно ли повышать uли понижать приоритет элементов, уже поставленных

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

Приведем базовые реализации очереди с приоритетами:

t

упорядоченный массив uли список.

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

путем уменьшения значения

максимального ин­

декса. Но поддержание упорядоченности элементов замедляет вставку новых эле­
ментов. Упорядоченные массивы подходят для реализации очереди с приоритетами

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

t

3.5.

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

O(lg п).

Пирамиды по­

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

ние ключа корня любого поддерева было меньшим, чем любого из

ero

потомков.

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

Глава

15.

Структуры данных

505

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

упорядоченности.
в разд.

4.3.2,

Реализация двоичной пирамиды на языке С рассматривается

а операция извлечения из нее наименьшего элемента

в разд.

4.3.3.

Двоичные пирамиды являются удачным выбором в том случае, когда известна
верхняя граница количества элементов в очереди с приоритетами, поскольку размер

массива нужно задавать при его создании. Но даже это ограничение можно обойти
с помощью динамических массивов (см. разд.

+

3. 1. 1);

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

наименьшего

элемента за постоянное

время

при

ограниченном диапазоне

возможных значений ключей. Допустим, мы знаем, что все значения ключей будут
целыми числами в диапазоне от

1 до

п. Тогда можно создать массив из п связных

списков, где i-й список играет роль корзины, содержащей все элементы с ключом

На наименьший непустой список будем поддерживать указатель

top.

i.

Чтобы вста­

вить в очередь с приоритетами элемент с ключом
прllсваиваем этому указателю значение

top

ший элемент, определяем первый элемент в

на стала пустой, перемещаем указатель

top

k, добавляем его к k-й корзине и
min(top, k). Чтобы извлечь наимень­
корзине top, удаляем его и, если корзи­
=

вниз.

Очереди с приоритетами, имеющие ограничение по высоте, очень полезны для хра­

нения вершин графа, упорядоченных по их степеням, при том что такое упорядочи­

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

+

двоичные деревья поиска.

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

вый

наибольший. Чтобы найти наименьший (или наибольший) элемент, просто

выполняется проход по указателям влево (или соответственно вправо) вниз до тех
пор, пока указатель не станет нулевым. Двоичные деревья поиска оказываются наи­

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

+

Фибоначчиевы и парные (pairiпg) пирамиды.
Такие сложные очереди с приоритетами предназначены для ускорения выполнения
операции уменьшения ключа, которая понижает приоритет поставленного в очередь

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

при обнаружении более короткого, чем найденный ранее, маршрута к вер­

шине

v.

Если их реализовать и использовать должным образом, то этот тип очереди с прио­
ритетами может улучшить производительность при больших объемах вычислений.

Часть

506

11.

Каталог алгоритмических задач

Реализации

Современные языки программирования содержат библиотеки подробных и эффектив­
ных

реализаций

Collections (JC)

очередей

с

приоритетами.

Класс

входит в стандартный пакет утилит

шаблона priority_queue библиотеки

STL

PriorityQueue библиотеки

Java java.util.

Java

Методы push, top и рор

языка С++ воспроизводят операции с пира­

мидами insert, findrnax и deletemax. Более подробное руководство по использованию

библиотеки

STL

можно найти в книгах

Библиотека

LEDA

(см.разд.

22.1.1)

[MeyOI]

и

[MDSOI].

содержит полную коллекцию очередей с приорите­

тами на языке С++, включая фибоначчиевы пирамиды, парные пирамиды, деревья ван
Эмде Боаса и очереди с приоритетами, имеющие ограничение по высоте. В результате

экспериментов, описанных в книге [MN99], было установлено, что простые двоичнь1е
пирамиды являются достаточно конкурентоспособными в большинстве приложений,
при этом в прямом сравнении парные пирамиды показывают лучшую производитель­

ность, чем фибоначчиевы пирамиды. В статье

изложено описание эксперимен­

[SanOO]

тов, демонстрирующих, что производительность разработанной автором последова­

тельной пирамиды

(sequence heap),

основанной на k-м слиянии, приблизительно вдвое

лучше, чем хорошо реализованной двоичной пирамиды.

ПРИМЕЧАНИЯ
В книге

[MS 18]

приводится всесторонний обзор современного состояния дел в том, что

касается очередей с приоритетами. Результаты экспериментальных сравнений структур
данных,

реализующих

очереди

с

(CGS99], (GBY91 ], [Jon86], [LL96]

приоритетами,

и

представлены

в таких работах,

как

[SanOO].

Двусторонние очереди с приоритетами (douЫe-ended

priority queue)

позволяют расширить

набор операций с пирамидами, включив в него одновременно операции

find-min и findmax. Обзор четырех разных реализаций двусторонних очередей с приоритетами приводит­
ся в

[Sah05].

Очереди с приоритетами,

имеющие ограничение по

высоте, являются

подходящими

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

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

ко очереди с приоритетами ван Эмде Боаса (см.

[vEBKZ77])

позволяют выполнять опера­

ции вставки, удаления, поиска и определения наибольшего и наименьшего элементов за

время

O(lg lg

п), где каждый ключ имеет значение от

Фибоначчиевы

пирамиды

(см.

[FT87,

ВLТ2])

1 доп.

поддерживают выполнение

операций

вставки и уменьшения ключа за амортизированное постоянное время, а выполнение опе­

раций извлечения наименьшего элемента и удаления

за время

O(lg п).

Постоянное вре­

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

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

енты. Парные пирамиды поддерживают такие же пределы, но при меньших накладных

расходах. Эксперименты с парными и другими пирамидами описаны в [LSТ4] и

[SV87].

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

полнения для создания пирамид описаны в

,625п сравнений
O(lg п) сравнений

(см.

[GM86]),

(см. [СС92]).

[Flo64].

В наихудшем случае достаточно

а вообще для создания пирамиды необходимо от

,5п

до

Глава

15.

Структуры данных

507

Родственные задаЧи
Словари (см. разд.
разд.

15.1),

сортировка (см. разд.

17.1),

поиск кратчайшего пути (см .

18.4).

15.3.

Суффиксные деревья и массивы

Вход. Строка

S.

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

q

в строке

S (рис. 15.3).

XYZXYZ$
YZXYZ$
ZXYZ$
XYZ$
YZ$
Z$
$
Вход

Выход

Рис.

15.3. Суффиксное дерево

Обсуждение
Суффиксные деревья и массивы являются исключительно полезными структурами

данных для элегантного и эффективного решения задач по обработке строк. Правиль­

ное применение суффиксных деревьев часто позво.Ляет уменьшить время испо:Лнения

алгоритмов для обработки строк с О(п 2 ) до линейного. Суффиксные деревья упомина­
лись в истории из жизни, описанной в разд. 3.9.
Простейший экземпляр суффиксного дерева представляет собой простое нагруженное
дерево

(trie)

из п суффиксов строки

S

длиной в п символов. Нагруженным деревом на­

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

нулевую строку .

Каждый путь из корня определяет строку, оilисываемую символами, которые марки­

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

15.4

показан пример простого нагруженного дерева.

Нагруженные деревья полезны для проверки на вхождение какой-либо строки запро­
са

q

в набор строк. Обход нагруженного дерева выполняется с корня вдоль ветвей,

Часть

508

определяемых последующими символами строки
в нагруженном дереве, то тогда строка

q

q.

11.

Каталог алгоритмических задач

Если какая-либо ветвь отсутствует

не может быть элементом набора строк, опре­

деляемых этим нагруженным деревом. В противном случае строка запроса находится

за

lql

сравнений символов, неiавuсимо от количества других строк, содержащихся

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

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

1
4
2
5
3

ХУ

XYZ$
X YZ$
У ‘ X YZ$
YZ$
‘X YZ$
6 Z$
7 $

Рис.

15.4.

Нагруженное дерево для строк the, their, there, was и when (слева)

и соответствующий массив суффиксов XY ZXYZ$ (справа)

Суффиксное дерево

это просто нагруженное дерево всех суффиксов строки

фиксное дерево позволяет проверить, является ли строка запроса

S,

т. к. любая подстрока

Алгоритмы. Руководство по разработке, Стивен Скиена

Алгоритмы. Руководство по разработке, Стивен Скиена

Автор: Стивен Скиена

Доступно в форматах: EPUB | PDF | FB2

Страниц: 720

Год издания: 2011

Язык: Русский

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

Отзывы

Дамир, Томск, 05.03.2017
Тружусь в образовательной сфере, часто приходится качать методические исследования для работы. Приходилось иметь дело со многими сайтами, нередко случались сбои в получении текстов: приходили фрагменты, книги долго закачивались или совсем не загружались. Приходилось тратить много времени и оставался неприятный осадок от работы ресурсов. На данном сайте скачивание не составило труда: после смс-подтверждения «Алгоритмы. Руководство по разработке» оказалась в моем компьютере (совершенно бесплатно). Благодарю разработчиков сайта. Предполагаю продолжить сотрудничество.

Тимофей, Красноярск, 05.02.2017
Порой для того, чтобы найти нужную литературу в глобальной сети, нужно потратить от 15 минут до целого часа. Это очень не удобно, ввиду постоянного отсутствия времени на подобные занятия. Очень доволен тому, что начал функционировать сайт с настолько богатой базой необходимых знаний. Теперь проблемы поиска книг для меня не существует!

Те, кто смотрел эту страницу, также интересовались:

Часто задаваемые вопросы

1. Какой формат книги выбрать: PDF, EPUB или FB2?
Тут все зависит от ваших личных предпочтений. На сегодняшний день, каждый из этих типов книг можно открыть как
на компьютере, так и на смартфоне или планшете. Все скачанные с нашего сайта книги будут одинаково открываться
и выглядеть в любом из этих форматов. Если не знаете что выбрать, то для чтения на компьютере выбирайте PDF,
а для смартфона — EPUB.

2. Можно ли книги с вашего сайта читать на смартфоне?
Да. Как для iOS, так и для Android есть много удобных программ для чтения книг.

3. В какой программе открыть файл PDF?
Для открытия файла PDF Вы можете воспользоваться бесплатной программой Acrobat Reader. Она доступна для скачивания на сайте adobe.com

Аннотация

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

В третьем издании расширен набор рандомизированных алгоритмов, алгоритмов хеширования, аппроксимации и квантовых вычислений. Добавлено более 100 новых задач, даны ссылки к реализациям на C, C++ и Java.

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

Алгоритмы Руководство по разработке. Автор: Стивен Скиена

20 Окт

Алгоритмы Руководство по разработке. Автор: Стивен Скиена

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

Понравилась статья? Поделить с друзьями:
  • Духовая печь zigmund shtain инструкция по применению
  • Omron varispeed v7 инструкция на русском
  • Механизм синхронного открывания раздвижных дверей инструкция
  • Руководство по эксплуатации лодочного мотора меркурий 30
  • Контроллер дельта программирование руководство по программированию