Алгоритмы как технология
Introduction to Algorithms
Самая известная в мире «библия» по алгоритмам для программистов.
Представим бесконечно быстрый компьютер
…и бесконечную память в придачу. Нужно ли было бы тогда вообще изучать алгоритмы?
Скорость
Любой метод с конечным числом шагов отрабатывает мгновенно — неважно, сколько этих шагов.
Память
Храним что угодно и сколько угодно — экономить нечего и незачем.
Время — такой же ограниченный ресурс, как память
Компьютеры весьма производительны…
…но их быстродействие не может быть бесконечно большим.
Память дешевеет…
…но не может стать бесплатной.
Алгоритмы различаются сильнее, чем железо
Алгоритмы для одной и той же задачи часто различаются по эффективности намного значительнее, чем различия, вызванные разным аппаратным и программным обеспечением. Пример — две сортировки из главы 2.
Время ≈ c1n², пропорционально n². Типичная константа c1 меньше, чем у слияния: c1 < c2.
Время ≈ c2n lg n, где lg n — краткая запись log2 n. Константа c2 больше — но сомножитель куда меньше.
Переломный момент наступит всегда
На малых n вставки обычно быстрее: константа меньше. Но с ростом n ничтожный lg n полностью компенсирует разницу констант. Не имеет значения, во сколько раз c1 меньше c2 — перелом всё равно наступит.
Компьютер А против компьютера Б
Оба сортируют 10 миллионов чисел. Звучит внушительно, но это около 80 МБ восьмибайтовых целых — весьма немного даже для старых недорогих лэптопов.
- Алгоритм
- сортировка вставкой
- Код
- лучший в мире программист, машинный язык
- Цена
- 2n² команд
- Алгоритм
- сортировка слиянием
- Код
- программист-середнячок, язык высокого уровня, неэффективный компилятор
- Цена
- 50 n lg n команд
Медленный Б обгоняет в 17 раз
Алгоритмы — это технология
Пример с компьютерами А и Б показывает: алгоритмы, как и аппаратное обеспечение, следует рассматривать как технологию. Вложиться можно и в то, и в другое — и отдача сравнима.
Нужны ли алгоритмы, если есть всё это?
Вопрос книги: так ли важны алгоритмы на современных компьютерах, если достигнуты выдающиеся успехи в других высоких технологиях? Нажмите на карточку — что у неё внутри.
Веб-служба «как добраться»
В основе — мощное железо, графический интерфейс, глобальная сеть и, возможно, ООП. Но саму задачу решают алгоритмы — нажмите на пункт:
Задача: найти самый быстрый путь от дома до клуба. Числа на дорогах — минуты в пути, в кружках — лучшее известное время до точки. Алгоритм Дейкстры, раздел 24.3.
- Берём ближайшую ещё не обработанную точку — время до неё уже окончательное.
- Смотрим её соседей: если через неё к соседу быстрее, записываем соседу новое время.
- Повторяем, пока не дойдём до клуба. Маршрут — цепочка «откуда пришли».
Задача: дорога хранится как 300 точек. Когда карту отдаляют, мелкие изгибы становятся меньше пикселя — их не видно, но рисовать их всё равно пришлось бы. Какие точки можно выкинуть, не испортив форму?
- Соединяем концы участка прямой — синий пунктир; синяя полоса вокруг — допуск ε.
- Ищем точку, которая дальше всех от этой прямой, — оранжевая.
- Дальше допуска ε — оставляем её и повторяем для двух половинок. Ближе — выкидываем все точки между концами.
Задача: пользователь ищет «ул. Кормена, 31», но точных координат каждого дома в базе нет.
- Что известно: отрезок улицы между двумя перекрёстками и номера домов на его концах — 1 и 99 (■ на карте).
- Какая доля пути:
- Где точка: — там и ставим метку ●.
Дома стоят неравномерно, поэтому метка может промахнуться на пару домов, — зато не нужно хранить координаты каждого дома. Это линейная интерполяция.
Алгоритмы, на которых стоит наш фронтенд
Упражнение 1.2.1 просит пример приложения, которому нужны алгоритмы. Далеко ходить не надо — они внутри инструментов, с которыми мы работаем каждый день.
Сравнение деревьев
Лучшие универсальные алгоритмы превращения одного дерева в другое работают за O(n³): 1000 элементов — порядка миллиарда сравнений. React берёт эвристику O(n): элементы разных типов дают разные деревья, а key подсказывает, какие дети стабильны.
O(n³) → O(n)Наибольшая возрастающая подпоследовательность
Когда список переставили, Vue ищет LIS среди старых позиций узлов: они остаются на месте, двигаются только остальные — минимум перемещений в DOM.
LIS · O(n log n)Очередь задач — двоичная куча
Планировщик хранит задачи в min-heap, упорядоченной по сроку: самая срочная всегда наверху — O(1), добавить или снять задачу — O(log n). Кучи и очереди с приоритетами — глава 6.
min-heap · O(log n)Алгоритм Майерса
По умолчанию git diff ищет кратчайший набор правок алгоритмом Майерса за O(ND), где D — число различий. Поэтому дифф почти одинаковых файлов строится мгновенно.
O(ND)Граф модулей
Сборщик строит граф импортов и обходит его от точки входа: что недостижимо, в бандл не попадёт. ES-модули и сами исполняются в порядке обхода графа в глубину. Графы — глава 22.
DFS · O(V + E)Бинарный поиск на каждый скролл
Чтобы показать 20 строк из 100 000, виртуальный список находит первую видимую строку по накопленным высотам бинарным поиском — ~17 шагов вместо перебора всех строк.
O(log n)Чем больше задача, тем важнее алгоритм
Вычислительные возможности растут — и компьютеры берутся за всё более сложные задачи. А с ростом задачи различия в эффективности алгоритмов проявляются всё значительнее. Во сколько раз Б обгоняет А:
Полоски — в логарифмическом масштабе. Время — по формулам примера: 2n² / 10¹⁰ против 50 n lg n / 10⁷.
Разомнёмся: упражнения 1.2.1–1.2.3
Алгоритмы на уровне приложения
Приведите пример приложения, для которого необходимо алгоритмическое наполнение на уровне приложений, и обсудите функции этих алгоритмов.
8n² против 64 n lg n
На одной машине вставкам нужно 8n² шагов, слиянию — 64 n lg n. При каком n вставки станут медленнее слияния? lg — это log2, как во всей книге.
1) Замена n = 2k: 2k < 8k. Это не степенное уравнение — k и в показателе, и снаружи, формулы нет. Зато степени двойки легко перебрать:
k=5: 32 < 40 ✓ · k=6: 64 > 48 ✗ → граница между 32 и 64.
2) Сужаем, как бинарным поиском:
n=43: 43 < 43,4 ✓ · n=44: 44 > 43,7 ✗
Вставки быстрее при 2 ≤ n ≤ 43, с n = 44 — слияние. Точный корень ≈ 43,56 (W-функция Ламберта, как в 1.2.3).
100n² против 2n
При каком минимальном n алгоритм со временем 100n² работает быстрее, чем алгоритм со временем 2n, на одной и той же машине?
Полином против экспоненты
Переменная — в основании, степень постоянна: n², n³, 100n² = 100 · n · n. С каждым шагом растёт всё медленнее: у n² при n = 15 шаг даёт всего ×1,14.
Переменная — в показателе, основание постоянно: 2n, 3n. Каждый шаг удваивает значение — поэтому рано или поздно обгоняет любой полином.
- 100n² < 2nобе части положительны — можно логарифмировать
- log2 100 + 2 log2 n < nlog2 возрастает, поэтому знак неравенства сохраняется
- 6,64 + 2 log2 n < nэкспонента «развернулась» в линейную n, полином — в логарифм
- правая часть: +1 за шаг, левая: +2 log2(1 + 1/n)2 log2(1 + 1/n) < 1 ⇔ (1 + 1/n)² < 2 ⇔ n > 2,41 — начав выполняться, неравенство уже не нарушится
- n = 14: 14,26 > 14 ✗ · n = 15: 14,46 < 15 ✓значит, наименьшее n = 15
При n = 100 он и правда больше: 100100 = 10200, а 2100 ≈ 1030. Но тот же приём с логарифмом даёт n < 100 log2 n — и экспонента обгоняет его с n = 997, дальше навсегда. Не путать со 100n: это не полином, а тоже экспонента, только с основанием 100.
Точный корень уравнения 100n² = 2n элементарной формулой не выражается — только через W-функцию Ламберта: n = −(2 / ln 2) · W−1(−ln 2 / 20) ≈ 14,32. Ближайшее целое сверху — 15.
Какую задачу успеем решить за век?
Алгоритм выполняет f(n) операций. В условии задачи одна операция длится 1 мкс — компьютер делает 10⁶ операций в секунду, и все цифры верны только для этой скорости. Какое наибольшее n он успеет обработать? Нажмите на строку.
| f(n) | Секунда | Минута | Час | День | Месяц | Год | Век |
|---|
Месяц — 30 дней, год — 365 дней.
Алгоритмы как технология — в пяти строках
Что далее
Антон Помазков
Артём Никифоров