Глава 1 Роль алгоритмов в вычислениях

Что такое алгоритмы

Программа вечера

Что такое алгоритмы
Антон Помазков
Алгоритмы как технология
Артём Никифоров

Кто написал эту книгу

Почему именно эта книга?

  1. Обложка первого издания, 1990
    1990
    первое издание
  2. Штайн в соавторах — книга становится CLRS. Новые главы: «Роль алгоритмов» (та самая, что разбираем), вероятностный анализ, линейное программирование.

  3. Обложка второго издания, 2001
    2001
    второе издание
  4. Деревья ван Эмде Боаса и многопоточные алгоритмы. Глава о рекуррентностях становится «Разделяй и властвуй», добавляется приложение о матрицах.

  5. Обложка третьего издания, 2009
    2009
    третье издание читаем это
  6. Паросочетания в двудольных графах, онлайн-алгоритмы, машинное обучение. Плюс суффиксные массивы, 140 новых упражнений и цвет в вёрстке.

  7. Обложка четвёртого издания, 2022
    2022
    четвёртое издание

    Текущее. Сайт с дополнительными материалами.

Доказывает, а не заявляет

Корректность выводится из инварианта цикла, время работы — из рекуррентности. Не «поверьте, тут O(n log n)».

Псевдокод вместо языка

Идея не тонет в синтаксисе и не устаревает вместе с фреймворком. Перенести на JS или Rust — работа на десять минут.

Весь путь задачи целиком

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

Широта справочника

Сортировки, структуры данных, графы, потоки, NP-полнота, криптография, БПФ, геометрия — одна книга вместо десяти.

Главы самодостаточны

Читается и подряд, и точечно: пришли за хеш-таблицами — взяли главу 11 и ушли. Поэтому книга живёт на столе, а не на полке.

Что такое алгоритм

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

input Вход

Величина или набор величин, которые мы подаём процедуре.

algorithm Алгоритм

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

output Выход

Результат — то, ради чего процедура запускалась.

Это вы пишете каждый день

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

user-list.js
const users = [
  { name: 'Аня', rating: 41 },
  { name: 'Борис', rating: 31 },
  { name: 'Вера', rating: 59 },
];

const byRating = (a, b) => a.rating - b.rating;

const sorted = [...users].sort(byRating);
наведите Три части кода

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

input Вход

Величина или набор величин, которые мы подаём процедуре.

здесь — массив users в произвольном порядке

algorithm Алгоритм

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

здесь — правило сравнения и процедура упорядочивания внутри sort

output Выход

Результат — то, ради чего процедура запускалась.

здесь — перестановка users, неубывающая по rating

Заметьте

Метод sort — это чей-то алгоритм, реализованный в движке. Кто-то доказал, что он корректен, и посчитал, сколько времени он потратит. Вопрос лишь в том, хотите ли вы быть тем, кто умеет это делать.

Алгоритм — инструмент

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

что Задача

Какие отношения должны связывать вход и выход.

Задача — это контракт Задаёт только отношение входа и выхода: «последовательность чисел → та же последовательность по неубыванию». Про способ — ни слова. Формулируется один раз, решений допускает сколько угодно. Практическая польза: пока задача не записана точно, спорить об алгоритмах бессмысленно — непонятно, что считать правильным ответом. Отсюда же берутся тесты: они проверяют контракт, а не реализацию.
как Алгоритм

Процедура, которая эти отношения обеспечивает.

Алгоритм — это реализация контракта Сортировка вставками, слиянием, быстрая — три разных алгоритма одной задачи. Ответ у них одинаковый, отличается цена: время и память. Практическая польза: если реализацию можно заменить, не меняя ожиданий, — задача и алгоритм разделены, и вы вправе выбирать по цене. Если нельзя — контракт протёк в код, и любая замена станет переписыванием.
на чём Программа

Алгоритм на конкретном языке и конкретном железе.

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

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

Пример: задача сортировки

Сортировка часто возникает на практике и служит благодатной почвой для знакомства со многими стандартными методами разработки и анализа алгоритмов — поэтому книга начинает именно с неё.

Вход Последовательность из n чисел ⟨a₁, a₂, …, an
Выход Перестановка ⟨a₁′, a₂′, …, an′⟩ входной последовательности,
такая, что a₁′ ≤ a₂′ ≤ … ≤ an

Что должно получиться

Если на вход подаётся ⟨31, 41, 59, 26, 41, 58⟩, вывод алгоритма сортировки должен быть ⟨26, 31, 41, 41, 58, 59⟩ — и никакой другой.

31
41
59
26
41
58
26
31
41
41
58
59
оба числа 41 остаются — на выходе перестановка входа

Экземпляр задачи

Экземпляр instance — конкретные входные данные, удовлетворяющие всем ограничениям постановки задачи, необходимые для её решения. Задача одна, экземпляров — бесконечно много.

algorithm Алгоритм сортировки
Заметьте

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

Что значит «корректный алгоритм»

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

любой вход Все экземпляры

Не «мои три теста», а каждый допустимый вход задачи.

алгоритм Завершается

Работа заканчивается за конечное число шагов — иначе ответа нет вовсе.

верный выход Решает задачу

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

Заметьте

Корректность — это свойство алгоритма, а не отчёт о прогоне тестов. Её доказывают, а не наблюдают.

Некорректный алгоритм

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

вход Входные данные
процедура Алгоритм

Не завершается

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

Отвечает неправильно

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

Ошибается, но контролируемо

Когда это норма: частоту ошибок можно посчитать и сделать сколь угодно малой. Наведите — пример из главы 31.

Глава 31 · проверка больших чисел на простоту Вероятностный тест Миллера — Рабина может назвать составное число простым. Но вероятность такой ошибки за один прогон не больше 1/4, а прогоны независимы: после k повторов она падает до 4−k. Двадцать повторов дают шанс ошибки меньше одной триллионной — меньше, чем вероятность сбоя самого железа. Зато тест отрабатывает за миллисекунды там, где честный перебор делителей не закончится никогда. Ровно так генерируются простые числа для ключей RSA.
Заметьте

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

Где может существовать алгоритм

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

Естественный язык

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

INSERTION-SORT(A)
  for j = 2 to A.length
    key = A[j]
    i = j - 1
    while i > 0 and A[i] > key
      A[i + 1] = A[i]
      i = i - 1
    A[i + 1] = key
псевдокод CLRS

Компьютерная программа

Код на JavaScript, Rust, C — привычная для нас форма. Язык не меняет сути алгоритма.

function insertionSort(a) {
  for (let j = 1; j < a.length; j++) {
    const key = a[j];
    let i = j - 1;
    while (i >= 0 && a[i] > key) {
      a[i + 1] = a[i];
      i--;
    }
    a[i + 1] = key;
  }
}
реализация

Аппаратная реализация

Схема в кремнии. В главе 30 книга приводит не только алгоритм БПФ, но и набросок проекта микросхемы для него.

микросхема: компаратор и обмен
Единственное требование

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

Где применяются алгоритмы

Вычислительные задачи, для которых разработаны алгоритмы, отнюдь не ограничиваются сортировкой. Дальше — четыре примера из книги, намеренно из очень разных областей. Карточки кликабельны: каждая ведёт на свой разбор.

Заметьте

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

Проект по расшифровке генома человека

Цель проекта — идентифицировать все гены ДНК человека, определить последовательности трёх миллиардов базовых пар, отсортировать эту информацию в базах данных и разработать инструменты для её анализа. Для всех этапов нужны сложные алгоритмы — и вот какие именно.

сборка

Граф де Брёйна

Секвенатор выдаёт миллиарды коротких прочтений. Их режут на слова длины k: слово — вершина графа, перекрытие — ребро, а сборка генома превращается в поиск пути через этот граф. Так работают Velvet и SPAdes.

родня из книги: графы, главы 22–24 Zerbino & Birney, Genome Research, 2008 ↗
сравнение

Смит — Уотерман

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

родня из книги: ДП, глава 15 Smith & Waterman, J. Mol. Biol., 1981 ↗
поиск по банку

BLAST

Честный Смит — Уотерман против всей базы последовательностей не успеет никогда. BLAST строит хеш-индекс коротких слов, находит точные затравки и расширяет только их — приближённо, зато за секунды. Главный инструмент биоинформатики уже тридцать пять лет.

родня из книги: хеш-таблицы, глава 11 Altschul et al., J. Mol. Biol., 1990 ↗
картирование

Барроуз — Уилер, FM-индекс

Чтобы разложить прочтения по референсному геному, Bowtie сжимает индекс всего генома человека до ≈1,3 ГБ — он помещается в память ноутбука — и выравнивает больше 25 миллионов прочтений на процессор-час.

родня из книги: поиск подстрок, глава 32 Langmead et al., Genome Biology, 2009 ↗
Что экономит

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

Интернет: две задачи

Интернет позволяет пользователям в любой точке мира быстро получать доступ к информации и извлекать её в больших объёмах. Работать с такими объёмами сайтам помогают хитроумные алгоритмы.

Как доставить данные

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

Методы решения — глава 24 Глава 24 · «Кратчайшие пути из одной вершины» Дейкстра растит множество вершин с уже известным расстоянием, каждый раз забирая ближайшую из очереди с приоритетами, — работает, пока веса рёбер неотрицательны. Беллман — Форд медленнее, зато переживает отрицательные веса и умеет сообщить, что кратчайшего пути нет вовсе из-за отрицательного цикла. Интернет-маршрутизация построена ровно на этой паре: OSPF внутри автономной системы считает Дейкстрой, а дистанционно-векторные протоколы вроде RIP — это распределённый Беллман — Форд.

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

Как найти нужное

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

Хеш-таблицы глава 11 Глава 11 · «Хеш-таблицы» Индекс и есть большая хеш-таблица: по слову запроса список документов достаётся в среднем за O(1), независимо от того, миллион в индексе слов или миллиард. Дальше остаётся пересечь несколько таких списков — вместо того чтобы просматривать сами страницы. В книге здесь же разбирается, почему хеш-функция должна «размазывать» ключи равномерно и что делать с коллизиями. и поиск подстрок глава 32 Глава 32 · «Поиск подстрок» Индекс отвечает, в каком документе есть слово, но не где именно в тексте стоит фраза. Для этого — Кнут — Моррис — Пратт: он заранее считает по образцу таблицу префиксов и потому никогда не откатывается назад по тексту, отрабатывая за O(n + m). И Рабин — Карп: он сравнивает не символы, а скользящий хеш окна — тем же приёмом ищут плагиат и дубликаты страниц.

«алгоритм» документы 3, 10, 20
перебрать всё
заглянуть в индекс

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

Электронная коммерция

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

канал один и тот же — меняется только то, что по нему едет
в основе лежит

Криптография с открытым ключом

Ключей два: открытым зашифровать может каждый, расшифровать — только владелец закрытого. Поэтому магазину не нужно заранее договариваться с вами о секрете.

глава 31 Глава 31 · «Теоретико-числовые алгоритмы» Там разбирается RSA — та самая схема Ривеста, Шамира и Адлемана. Стойкость держится на том, что перемножить два больших простых числа легко, а разложить произведение обратно — нет. Рабочие инструменты главы: алгоритм Евклида для НОД, быстрое возведение в степень по модулю и вероятностный тест простоты Миллера — Рабина, которым генерируют сами простые числа для ключей.
в основе лежит

Цифровые подписи

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

глава 31 Глава 31 · цифровая подпись RSA Подпись — это хеш сообщения, зашифрованный закрытым ключом. Проверяющий расшифровывает её открытым ключом и сравнивает с хешем, который посчитал сам: совпало — значит, подписал владелец ключа и текст не менялся. На этом стоят сертификаты TLS, из-за которых браузер показывает замок, и подписи пакетов в npm и дистрибутивах.

Производство и коммерция

Один класс задач с одной формулой: ограниченные ресурсы → максимальная выгода. Меняются только декорации.

Нефтяная компания

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

ресурс — места бурения

Кандидат в президенты

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

ресурс — бюджет кампании

Авиакомпания

Какую минимальную цену назначить за билеты, чтобы уменьшить количество свободных мест — и не нарушить законы о перевозках?

ресурс — места в салоне

Интернет-провайдер

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

ресурс — мощности сети
Один метод

Все эти задачи можно решить с помощью линейного программирования глава 29 Глава 29 · «Линейное программирование» Задача записывается так: максимизировать линейную функцию (прибыль) при наборе линейных неравенств (ресурсы, законы, мощности). Решает её симплекс-метод: допустимые решения образуют многогранник, оптимум всегда лежит в его вершине, и метод переходит из вершины в вершину, каждый раз улучшая результат. В главе разбирают, как свести к линейной программе кратчайшие пути и потоки в сетях, — то есть половина предыдущих задач оказывается её частным случаем. : цель и ограничения записываются линейными соотношениями, а дальше работает общий алгоритм. Разные отрасли — одна математика.

Задача о кратчайшем пути

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

Алгоритм Дейкстры: растим множество вершин с уже известным расстоянием, каждый раз забирая из очереди с приоритетами ближайшую. Ответ здесь — A → v₂ → v₃ → v₅ → Б = 12.

Перекрёсток вершина графа
Дорога между соседними перекрёстками ребро
Расстояние по этой дороге вес ребра
«Добраться отсюда туда покороче» кратчайший путь от одной вершины к другой

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

эффективное решение данной задачи представлено в главе 24

Длиннейшая общая подпоследовательность

Подпоследовательность X — это сама последовательность X с некоторыми удалёнными элементами. Задача: для X и Y найти их длиннейшую общую подпоследовательность. Её длина — мера схожести: цепочки ДНК считают сходными, если у них есть длинная общая подпоследовательность.

X A B C B D A B
Y B A D C B A
общая подпоследовательность: A C B A

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

последовательность X
2m

подпоследовательностей: каждый элемент либо остаётся, либо удаляется

последовательность Y
2n

подпоследовательностей — ровно по той же причине

перебрать все пары
2m·2n

сопоставлений. При m = n = 20 это уже больше триллиона пар — а двадцать символов это очень короткая строка.

Вывод книги

Выбирая все возможные подпоследовательности X и Y и сопоставляя их, задачу можно решить только за очень длительное время — если, конечно, m и n не оказываются очень малыми величинами.

BADCBA 0000000 A0011111 B0111122 C0111222 B0111233 D0112233 A0122234 B0122234

c[i, j] — длина ЛОП префиксов X[1..i] и Y[1..j]; зелёным — совпадения, из которых собран ответ

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

Большая задача разбивается на подзадачи поменьше: какова ЛОП префиксов X[1..i] и Y[1..j]. Каждая клетка таблицы — ответ на одну такую подзадачу, и считается он из уже заполненных соседей:

c[i, j] = c[i−1, j−1] + 1если xᵢ = yⱼ — символ идёт в ответ
c[i, j] = max(c[i−1, j], c[i, j−1])иначе — берём лучшее из двух соседей

Каждая подзадача решается ровно один раз и записывается в таблицу, дальше её ответ берётся готовым. Вместо 2m·2n сопоставлений — m·n клеток: для строк по тысяче символов это миллион шагов. В правом нижнем углу — длина ответа, 4; пройдя по таблице обратно, собираем саму подпоследовательность A C B A.

Глава 15

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

Топологическая сортировка

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

lib-core icons ui-kit forms app

это ровно то, что делает пакетный менеджер с деревом зависимостей

Если проект состоит из n частей, имеется n! возможных упорядочений. А факториал растёт быстрее даже показательной функции.

n = 5
120

перебрать можно вручную

n = 10
3.6·106

уже работа для компьютера

n = 20
2.4·1018

перебор закончится не при нас

n = 50
1064

больше, чем атомов в Солнечной системе

Вывод книги

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

список по завершению, от последнего к первому lib-core ui-kit icons forms app

Обход в глубину

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

Часть завершается позже всех своих зависимых — значит, в списке окажется раньше них. Это ровно то условие, которого требовала задача. Правильных порядков обычно несколько: здесь вышел ui-kit раньше icons, и это тоже верно.

Каждая часть и каждая связь просматриваются по одному разу — время Θ(V + E) вместо n! перебора. Если же в графе нашёлся цикл, порядка не существует вовсе, и обход сообщит об этом сам.

Глава 22

Задачи «упорядочить с учётом зависимостей» решаются топологической сортировкой графа. Подробнее — в главе 22. Её же каждый день выполняют turbo Turborepo Система сборки JS/TS-монорепозиториев от Vercel. Из package.json пакетов строит граф задач, запускает их в топологическом порядке, независимые — параллельно, а результаты кеширует: повторная сборка без изменений занимает секунды. , nx Nx Система сборки монорепозиториев от Nrwl. Строит граф проектов и по нему находит, какие пакеты затронуло изменение (nx affected): тесты и сборка идут только для них и строго в порядке зависимостей. и bazel Bazel Система сборки Google — открытая версия внутреннего Blaze. Не привязана к языку: собирает Java, C++, Go и JS из одного графа целей. Сборки герметичные и воспроизводимые, пересобирается только то, что зависит от изменений. .

Выпуклая оболочка

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

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

В качестве набора вершин оболочки может выступать любое из 2n подмножеств множества точек. И этого мало: недостаточно знать, какие точки являются вершинами, — нужен ещё порядок их обхода.

сколько подмножеств
2n

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

и это ещё не всё

Нужен порядок обхода

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

метод 1

Обход Грэхема

Точки сортируются по углу вокруг самой нижней, а дальше стек: каждая новая точка либо продолжает оболочку, либо выталкивает предыдущую, если та оказалась внутри. Время — O(n log n), и почти всё оно уходит на сортировку.

метод 2

Метод Джарвиса — «заворачивание подарка»

Начинаем с заведомо крайней точки и каждый раз ищем следующую, вокруг которой «загибается бумага». Время — O(n·h), где h — число вершин оболочки: когда вершин мало, это выгоднее сортировки.

Решение · глава 33

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

Что общего у этих задач?

Приведённые примеры не исчерпывают применение алгоритмов, но на них видны две общие характеристики, присущие многим интересным алгоритмам.

характеристика 01

Множество вариантов-кандидатов

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

характеристика 02

Практическое применение

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

Исключение: не у всех задач есть «варианты»

Не все задачи сводятся к поиску среди кандидатов: у некоторых перебирать просто нечего, и ответ получается прямым вычислением. Самый распространённый пример — дискретное преобразование Фурье Глава 30 · БПФ Слева — как сигнал меняется во времени, справа — из каких частот он состоит. Это одни и те же данные в двух представлениях, и переход между ними делает быстрое преобразование Фурье за O(n log n) вместо O(n²) прямого счёта. Перебирать здесь нечего: множества кандидатов нет вовсе. . На нём же держатся умножение больших чисел Через частотное представление Число — это полином, а умножение полиномов через БПФ считается быстрее, чем столбиком. Столбик — O(n²) операций с цифрами, через преобразование — O(n log n). Ровно на этом стоит арифметика больших чисел в криптографии, где числа длиной в сотни цифр перемножают постоянно. , сжатие данных Выбросить то, чего не заметят Сигнал раскладывают на частоты, отбрасывают те, что вносят мало вклада, и собирают обратно. Файл становится меньше, а на слух и на глаз разница почти не различима. Так устроены JPEG и MP3 — родственным преобразованием, из той же семьи, что и БПФ. и обработка сигналов Основа целой инженерной области Фильтры, эквалайзеры, шумоподавление, распознавание речи — всё это работа с частотами, а значит, преобразование Фурье внутри. В браузере это Web Audio API: визуализация звука и эквалайзер считаются через FFT прямо на странице. .

Структуры данных

Структура данных data structure — способ хранения и организации данных, облегчающий доступ к этим данным и их модификацию.

Массив

Доступ по индексу мгновенный. Вставка в середину — дорогая.

Связный список

Вставка и удаление дёшевы. Доступ по номеру — только перебором.

Дерево

Хранит порядок и быстро ищет. Требует поддержания балансировки.

Хеш-таблица

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

Граф

Выражает связи между объектами. Обход дороже, чем у списка.

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

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

Книга как «сборник рецептов»

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

главы о задачах

Конкретные задачи

Готовые разборы — те самые «рецепты»:

  • Поиск медиан и порядковых статистик — глава 9
  • Вычисление минимальных остовных деревьев — глава 23
  • Определение максимального потока в сети — глава 26
знаешь задачу — берёшь готовое решение
+
главы о методах

Общие методы

То, что работает на задачах, которых в книге нет:

  • «Разделяй и властвуй» — глава 4
  • Динамическое программирование — глава 15
  • Амортизационный анализ — глава 17
не знаешь задачу — придумываешь решение

Главная мысль главы

Найти ответ умеет и перебор.
Алгоритм находит его верно на любом входе
и не перебирая всё.

01

Сначала — постановка

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

02

Корректность — на всех экземплярах

Тесты показывают, что ошибки нет на трёх входах. Доказательство — что её нет ни на одном. Алгоритм, который «иногда ошибается», — это осознанный выбор, а не находка в проде.

03

Перебор — не решение

Когда кандидатов 2n или n!, нужен метод: динамическое программирование, обход графа, геометрия. А если оптимум недостижим — приближение с доказанной границей.

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

Как разрабатывать свои алгоритмы

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

01 Поставить задачу

Точно описать вход и выход. Пока это не сделано, решать нечего.

02 Подобрать метод

«Разделяй и властвуй», динамическое программирование, жадный выбор — на что похожа задача?

03 Доказать корректность

Показать, что ответ верен на любом допустимом входе, а не на трёх примерах.

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

Посчитать, как растёт время работы с ростом входа — до того, как это увидит прод.

Заметьте

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

Сложные задачи

Обычная мера эффективности алгоритма — скорость: время, в течение которого алгоритм выдаёт результат. Но существуют задачи, для которых неизвестны эффективные методы решения.

большая часть книги

Эффективные алгоритмы

Сортировка, кратчайшие пути, потоки, оболочки — всё это решается быстро, и книга показывает как.

мера

Чем меряем

Скоростью: как растёт время работы при росте объёма входных данных. Это главная характеристика.

глава 34

А есть другие

Задачи, для которых никто не знает эффективного алгоритма. Они называются NP-полными.

Важно

«Неизвестен эффективный алгоритм» — это не то же самое, что «задача нерешаема». Решить её можно, просто время решения растёт так быстро, что при больших входах это теряет практический смысл.

NP-полные задачи

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

свойство 01

Проверить — легко

Дали готовый маршрут — посчитать его длину и сравнить с лимитом можно мгновенно.

свойство 02

Найти — тяжело

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

свойство 03

Они связаны друг с другом

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

Осторожно

Не доказано, что эффективного алгоритма не существует. Доказано лишь, что его пока никто не нашёл. Это разные утверждения — и именно поэтому вопрос до сих пор открыт.

Почему NP-полные задачи интересны

Книга называет три причины — и все три стоит понять.

причина 01

Вопрос открыт

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

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

главная открытая проблема информатики
причина 02

Замечательное свойство набора

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

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

одна дверь открывает все
причина 03

Граница проходит рядом

Некоторые NP-полные задачи похожи, но не идентичны задачам, для которых эффективные алгоритмы известны.

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

шаг в сторону — и задача другая

Зачем это знать разработчику

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

не распознал

Ищу идеальный алгоритм

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

  • Недели на оптимизацию того, что не оптимизируется
  • Ощущение, что вы просто недостаточно умны
  • Результата всё равно нет
распознал

Ищу достаточно хорошее

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

  • Понятная цель и понятный срок
  • Решение, которое работает уже завтра
  • Честный разговор с бизнесом об ожиданиях
Заметьте

Знание о NP-полноте — это не про теорию. Это смена стратегии: от «найти идеальное любой ценой» к «найти достаточно хорошее и поехать дальше».

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

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

склад → A → C → B → D → E → склад = 154 км склад → B → A → E → D → C → склад = 200 км склад → A → B → C → D → E → склад = 135 км
пять точек — 120 порядков обхода; двадцать точек — больше, чем мы успеем перебрать

Эта задача хорошо известна как «задача о коммивояжёре», и она является NP-полной. Эффективный алгоритм решения для неё неизвестен.

Приближённые алгоритмы

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

идеальное решение

Точный оптимум

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

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

Приближённый алгоритм

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

  • Отклонение ограничено и доказано, а не «на глаз»
  • Работает на реальных размерах данных
  • Подобные алгоритмы рассматриваются в главе 35 Глава 35 · «Приближённые алгоритмы» Ключевое понятие — коэффициент аппроксимации: доказанная граница, во сколько раз ответ может отличаться от оптимального. Для задачи о коммивояжёре с неравенством треугольника есть алгоритм с коэффициентом 2 — маршрут гарантированно не длиннее удвоенного оптимального, — и алгоритм Кристофидеса с коэффициентом 1.5. Там же разбирают вершинное покрытие, покрытие множества и рюкзак. Главное отличие от эвристики: здесь граница доказана, а не измерена на тестах.
Заметьте

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

Почему процессоры перестали ускоряться

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

раньше Выше тактовая частота

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

проблема Тепло

С ростом частоты выделение тепловой мощности растёт более чем линейно — микросхемы могут просто начать плавиться.

теперь Несколько ядер

Проектировщики всё активнее используют другой путь — процессоры, содержащие несколько «ядер».

Заметьте

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

Многоядерный процессор

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

было

Один процессор

Вся надежда — на тактовую частоту одного вычислительного блока.

стало

Несколько ядер

Несколько вычислительных единиц внутри одного процессора — каждая со своей работой.

Но само не заработает

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

Многопоточные алгоритмы

В главе 27 представлена модель для «многопоточных» алгоритмов, которая использует преимущества многоядерности.

Ядро 1
A
B
C
D
A
Ядро 2
B
Ядро 3
C
Ядро 4
D
0T/4T/23T/4T
одна и та же работа A, B, C, D — разница только в алгоритме
Заметьте

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

Упражнения

Пять упражнений из раздела 1.1. Сначала думаем сами — решение открывается кнопкой. Карточки кликабельны: каждая ведёт на своё задание.

Заметьте

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

Упражнение 1.1.1 Задание 1 из 5

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

Сортировка

  • Товары по цене в каталоге. Пользователь хочет увидеть сначала дешёвое — это ровно задача сортировки, и от неё зависит, останется ли он на странице.
  • Пользователи в админке. По дате регистрации, по активности, по фамилии — упорядочивание делает список пригодным для работы.
  • Результаты поиска по релевантности. Найти мало, нужно ещё расположить в правильном порядке: смотрят в основном первые позиции.
  • Файлы в проводнике. По имени, размеру, дате изменения — сортировка здесь промежуточный шаг к тому, чтобы найти нужный файл глазами.

Выпуклая оболочка

  • Географические точки. Очертить минимальную область, в которой работает служба доставки или в которой зафиксированы события.
  • Обработка изображений. Найти границу объекта, выделенного на снимке, — оболочка даёт его внешний контур.
  • Робототехника. Робот обходит препятствие, зная его выпуклую оболочку: это дешёвое приближение формы, достаточное для планирования пути.
  • Геометрические задачи и игры. Проверка столкновений: пересечение выпуклых оболочек считается гораздо быстрее, чем пересечение сложных форм.

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

Упражнение 1.1.2 Задание 2 из 5

Какими ещё параметрами, кроме скорости, можно характеризовать алгоритм на практике?

Прямо следует из главы

  • Потребление памяти. Алгоритм может быть быстрым, но требовать копии всех данных — на большом входе это решающий фактор.
  • Корректность. Глава вводит её как отдельное свойство: даёт ли алгоритм верный результат на любом допустимом входе.
  • Требования к хранилищу. Данные в основной памяти, на диске или на ленте — от этого зависит, какой алгоритм вообще применим.
  • Возможность параллельного выполнения. Раздел о многоядерности прямо говорит: алгоритм нужно разрабатывать с учётом параллельности.
  • Чувствительность к входным данным. Количество элементов, их исходный порядок, ограничения на значения — всё это меняет поведение алгоритма.

Практические характеристики сверх текста

  • Масштабируемость. Хорошо ли алгоритм ведёт себя, когда данных становится в сто раз больше.
  • Сложность реализации и сопровождения. Алгоритм, который в команде понимает один человек, — риск, а не преимущество.
  • Устойчивость к худшему случаю. Средняя скорость может быть отличной, а худший случай — обваливать сервис.
  • Стабильность и предсказуемость. Сохраняет ли сортировка порядок равных элементов; одинаково ли ведёт себя от запуска к запуску.
  • Стоимость. Процессорное время в облаке — это счёт в конце месяца.

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

Упражнение 1.1.3 Задание 3 из 5

Выберите одну из встречавшихся вам ранее структур данных и опишите её преимущества и ограничения.

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

  • Быстрый поиск по ключу в среднем. Время доступа почти не зависит от количества элементов — это её главное свойство.
  • Удобное отображение ключ → значение. Естественная модель для кешей, индексов, словарей, подсчёта количеств.
  • Вставка и удаление тоже дёшевы — в среднем такие же, как и поиск.

Ограничения

  • Нет естественного порядка элементов. «Дай все ключи по возрастанию» или «дай диапазон» — не её задача, нужна другая структура.
  • Возможны коллизии. Разные ключи попадают в одну ячейку, и их приходится разрешать — это цена скорости.
  • Производительность зависит от реализации и распределения ключей. Плохая хеш-функция или недружелюбные данные — и «в среднем быстро» превращается в «медленно».
  • Платит памятью. Таблице нужен запас свободных ячеек, иначе коллизий становится слишком много.

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

Упражнение 1.1.4 Задание 4 из 5

Что общего между задачей об определении кратчайшего пути и задачей о коммивояжёре? Чем они различаются?

Общее

  • Обе формулируются на графе: вершины, рёбра, веса рёбер
  • Обе про маршрут и про минимизацию суммарного расстояния
  • У обеих огромное множество вариантов-кандидатов
  • Обе имеют прямое практическое применение в перевозках и сетях

Кратчайший путь

  • Найти лучший путь между двумя вершинами
  • Промежуточные вершины проходить необязательно
  • Возвращаться в начало не нужно
  • Есть эффективный алгоритм — глава 24

Коммивояжёр

  • Найти оптимальный маршрут через все заданные точки
  • Каждую точку нужно посетить
  • Обязательно вернуться в исходную точку
  • NP-полная: эффективный алгоритм неизвестен — глава 34

Главное различие — вычислительная сложность. Формулировки соседние: «доехать из А в Б» и «объехать всё и вернуться». Но первая решается эффективно, а для второй эффективного алгоритма никто не знает — и приходится довольствоваться приближёнными решениями (глава 35). Это ровно та третья причина интереса к NP-полным задачам: небольшое изменение формулировки резко меняет сложность.

Упражнение 1.1.5 Задание 5 из 5

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

Нужно только оптимальное решение

  • Минимальная стоимость операции, когда даже небольшое отклонение недопустимо: расчёт сдачи, начисление процентов, сверка баланса. «Почти правильная» сумма — это просто неправильная сумма.
  • Проверка подписи или пароля. Ответ бинарный: совпало или нет. Приближённого варианта здесь не существует в принципе.
  • Сортировка. Частично отсортированный список — не решение задачи сортировки, а другой результат.

Достаточно близкого к наилучшему

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

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

Что далее

Что такое алгоритмы Пройдено
Антон Помазков
Алгоритмы как технология
Артём Никифоров