Перейти к основному содержимому

Зачем измерять алгоритмы

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

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

Нотация Big O описывает, как растёт число шагов, когда растут входные данные. Она не говорит «это занимает 3 секунды». Она говорит «если входные данные удваиваются, работа удваивается» — это O(n) — или «если входные данные удваиваются, работа остаётся прежней» — это O(1).

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


Подсчёт шагов

Цикл, который обходит каждый элемент списка, делает один проход на элемент. Для 5 элементов это 5 шагов, для 10 элементов — 10 шагов. Число шагов равно len(nums).

Нас не интересует точное число. Нас интересует форма роста: вдвое больше входных данных — вдвое больше шагов. Такая форма называется линейной и записывается как O(n).

Подсчёт шагов цикла

Функция увеличивает счётчик steps внутри цикла. Список из 5 элементов занимает 5 шагов, список из 10 элементов — 10 шагов: число шагов следует за размером входных данных.

Prompt
Напиши функцию на Python, которая суммирует список целых чисел и считает, сколько шагов цикла она выполняет, с помощью счётчика steps. Вызови её один раз со списком из 5 чисел и один раз со списком из 10 чисел и выведи оба количества шагов.
Вывод: полная программа на Python
Count the steps of a loop

Удвоение входных данных удваивает работу

Программа запускает один и тот же цикл подсчёта для n = 100, 200 и 400. Каждый раз, когда n удваивается, число шагов тоже удваивается. Это отличительный признак O(n).

Prompt
Напиши программу на Python, которая для n = 100, 200 и 400 выполняет цикл n раз, считая шаги, и выводит по одной строке для каждого n с n и количеством шагов, чтобы было видно, что удвоение n удваивает число шагов.
Вывод: полная программа на Python
Doubling the input doubles the work

Классы роста

Большинство алгоритмов относится к нескольким распространённым классам роста, от самого быстрого к самому медленному:

O(1) постоянное — работа никогда не растёт.
O(log n) логарифмическое — удвоение входных данных добавляет всего один шаг.
O(n) линейное — работа растёт вместе с входными данными.
O(n log n) — хороший алгоритм сортировки.
O(n²) квадратичное — цикл внутри цикла.
O(2ⁿ) экспоненциальное — работа удваивается с каждым дополнительным элементом.

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

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

Таблица показывает число шагов при растущем n. При n = 16 значение log n равно всего 4, а 2ⁿ уже 65536. Чем больше становится n, тем сильнее расходятся классы.

Prompt
Напиши программу на Python, которая выводит таблицу для сравнения классов роста. Для n = 1, 2, 4, 8, 16 выведи одну строку с: n, log n, n, n log n, n в квадрате и 2 в степени n. Используй math.log2 и выровняй столбцы с помощью f-строк.
Вывод: полная программа на Python
Compare the growth classes

Что игнорирует Big O

Big O сохраняет только форму роста и отбрасывает детали. O(2n), O(n + 5) и O(n) — это один и тот же класс: линейный.

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

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

Два цикла — всё равно O(n)

Программа делает два отдельных прохода по списку: один для суммы, другой для наибольшего значения. Это 2n шагов — 16 для 8 элементов. Big O отбрасывает константу 2, поэтому это всё равно O(n).

Prompt
Напиши программу на Python, которая делает два отдельных прохода по одному и тому же списку из 8 целых чисел — один цикл вычисляет сумму, другой находит наибольшее значение — и считает каждый шаг цикла в одном общем счётчике. Выведи сумму, наибольшее значение, количество элементов и общее число шагов.
Вывод: полная программа на Python
Two loops are still O(n)