Часто бывает полезно оценить, сколько времени работает алгоритм. Конечно, можно его просто реализовать и запустить, но тут возникают проблемы:
- На разных компьютерах время работы всегда будет отличаться.
- Не всегда заранее доступны именно те данные, на которых он в реальности будет запускаться.
- Иногда приходится оценивать алгоритмы, требующие часы или даже дни работы.
- Жизнь коротка, и если есть несколько вариантов алгоритмов, то хочется заранее предсказать, какой из них будет быстрее, чтобы реализовывать только его.
Для этого, в качестве первого приближения, попробуем оценить число операций в алгоритме. Возникает вопрос: какие именно операции считать. Как один из вариантов — учитывать любые элементарные операции:
- арифметические операции с числами:
+, -, *, /
- сравнение чисел:
<, >, <=, >=, ==, !=
- присваивание:
a[0] = 3
При этом важно не просто считать строчки, а ещё учитывать, как реализованы некоторые отдельные вещи в самом языке. Например, в питоне срезы массива (array[3:10]
) копируют этот массив, то есть этот срез работает за 7 элементарных действий. А swap
, например, можно реализовать за 3 присваивания.
Упражнение. Попробуйте посчитать точное число сравнений и присваиваний в сортировках пузырьком, выбором, вставками и подсчетом в худшем случае. Это должна быть какая-то формула, зависящая от — длины массива.
Чтобы учесть вообще все элементарные операции, ещё надо посчитать, например, сколько раз прибавилась единичка внутри цикла for
. А ещё, например, строчка n = len(array)
— это тоже действие. Поэтому даже посчитав их, не сразу очевидно, какой из этих алгоритмов работает быстрее — сравнивать формулы сложно. Хочется придумать способ упростить эти формулы так, чтобы
- не нужно было учитывать информацию, не очень сильно влияющую на итоговое время;
- легко было оценивать время работы разных алгоритмов для больших чисел;
- легко было сравнивать алгоритмы на предмет того, какой из них лучше подходит для тех или иных входных данных.
Для этого придумали О-нотацию — асимптотическое время работы вместо точного (часто его ещё называют просто асимптотикой).
Определение. Пусть — это какая-то функция. Говорят, что функция , если существует такие константы и , что для всех .
Например:
В контексте анализа алгоритмов, фраза «алгоритм работает за операций» означает, что начиная с какого-то он делает не более чем за операций.
В таких обозначениях можно сказать, что
- сортировка пузырьком работает за ;
- сортировка выбором работает за ;
- сортировка вставками работает за ;
- сортировка подсчетом работает за .
Это обозначение удобно тем, что оно короткое и понятное, а также не зависит от умножения на константу или прибавления константы. Например, если алгоритм работает за , то это может значить, что он работает за , за , за или даже за действие. Главное — что функция ведет себя как , то есть при увеличении она увеличивается как некоторая квадратичная функция: если увеличить в 10 раз, время работы программы увеличится приблизительно в 100 раз.
Основное преимущество О-нотации в том, что все рассуждения про то, сколько операций в swap
или считать ли отдельно присваивания, сравнения и циклы — отпадают. Каков бы ни был ответ на эти вопросы, они меняют ответ лишь на константу, а значит асимптотическое время работы алгоритма не меняется.
Первые три сортировки поэтому называют квадратичными — они работают за . Сортировка подсчетом может работать намного быстрее — она работает за , а если , то это вообще линейная функция .