Есть ли способ автоматически вывести сложность алгоритма (Python)?
Нет нельзя. Потому что нельзя даже определить, завершается ли программа, или виснет. Это называется проблема остановки (https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D. Логически доказано, что невозможно автоматически решить ее.
Подобным образом можно доказать, что не существует программы, определяющей сложность любой программы.
Допустим, что такая программа есть. Напишем другую программу, которая анализирует входной исходный код этой первой программой (пусть она получила оценку f(N)), а потом делает что-то неважное (например прибавляет 1 к переменной) f(N)*N раз. Теперь запустим эту программу на собственном исходном коде. Она получит оценку своей сложности и потом сделает что-то в N раз больше. Т.е. фактически сделано f(N)*N операций, но программа же оценила этот код как O(f(N)) на этих входных данных, что неверно.
Можно написать что-то тупое и наивное, вроде подсчета количества вложенных циклов, но работать будет только в очень частных случаях и часто ошибаться.
6.1. Теория¶
Во время своей работы программы используют различные структуры данных и алгоритмы, в связи с чем обладают разной эффективностью и скоростью решения задачи. Дать оценку оптимальности решения, реализованного в программе, поможет понятие вычислительной сложности алгоритмов.
6.1.1. Основные понятия¶
Вычислительная сложность (алгоритмическая сложность) — понятие, обозначающее функцию зависимости объема работы алгоритма от размера обрабатываемых данных.
Вычислительная сложность пытается ответить на центральный вопрос разработки алгоритмов: как изменится время исполнения и объем занятой памяти в зависимости от размера входных данных?. С помощью вычислительной сложности также появляется возможность классификации алгоритмов согласно их производительности.
В качестве показателей вычислительной сложности алгоритма выступают:
Временная сложность (время выполнения).
Временная сложность алгоритма — это функция от размера входных данных, равная количеству элементарных операций, проделываемых алгоритмом для решения экземпляра задачи указанного размера.
Временная сложность алгоритма зачастую может быть определена точно, однако в большинстве случаев искать точное ее значение бессмысленно, т.к. работа алгоритма зависит от ряда факторов, например, скорости процессора, набора его инструкций и т.д.
Асимптотическая сложность оценивает сложность работы алгоритма с использованием асимптотического анализа.
Алгоритм с меньшей асимптотической сложностью является более эффективным для всех входных данных.
6.1.2. Асимптотические нотации¶
Асимптотическая сложность алгоритма описывается соответствующей нотацией:
О-нотация, \(O\) («О»-большое): описывает верхнюю границу времени (время выполнения «не более, чем…»);
Омега-нотация, \(\Omega\) («Омега»-большое): описывает нижнюю границу времени (время выполнения «не менее, чем…»).
говорит о том, что алгоритм имеет квадратичное время выполнения относительно размера входных данных в качестве верхней оценки («О большое от эн квадрат»).
Каждая оценка при этом может быть:
наилучшая: минимальная временная оценка;
наихудшая: максимальная временная оценка;
средняя: средняя временная оценка.
При оценке, как правило, указывается наихудшая оценка.
Допустим, имеется задача поиска элемента в массиве. При полном переборе слева направо:
наилучшая оценка: \(O(1)\) , если искомый элемент окажется в начале списка;
наихудшая оценка: \(O(N)\) , если искомый элемент окажется в конце списка;
средняя оценка: \(O \left ( \cfrac
6.1.2.1. Верхняя оценка и \(O\) -нотация¶
Наиболее часто используемой оценкой сложности алгоритма является верхняя (наихудшая) оценка, которая обычно выражается с использованием нотации O-большое.
Выделяют следующие основные категории алгоритмической сложности в \(O\) -нотации:
Постоянное время: \(O(1)\) .
-
Время выполнения не зависит от количества элементов во входном наборе данных.
-
Пример: операции присваивания, сложения, взятия элемента списка по индексу и др.
Линейное время: \(O(N)\) .
-
Время выполнения пропорционально количеству элементов в коллекции.
-
Пример: найти имя в телефонной книге простым перелистыванием, почистить ковер пылесосом и т.д.
Логарифмическое время: \(O(\log
-
Время выполнения пропорционально логарифму от количества элементов в коллекции.
-
Пример: найти имя в телефонной книге (используя двоичный поиск).
Линейно-логарифмическое время: \(O(N \log
-
Время выполнения больше чем, линейное, но меньше квадратичного.
-
Пример: обработка \(N\) телефонных справочников двоичным поиском.
Квадратичное время: \(O(N^<2>)\) .
-
Время выполнения пропорционально квадрату количества элементов в коллекции.
-
Пример: вложенные циклы (сортировка, перебор делителей и т.д.).
На Рисунке 6.1.1 приведен график роста \(O\) -большое.

Рисунок 6.1.1 — График роста \(O\) -большое 6 ¶
6.1.3. Оценка сложности алгоритмов¶
Для оценки вычислительной сложности алгоритмов необходимо знать и учитывать сложности:
используемых структур данных;
совокупности различных операций.
6.1.3.1. Операции над структурами данных¶
В Python имеются коллекции (структуры данных), операции над которыми имеют определенную сложность.
6.1.3.1.1. Список и кортеж¶
Большинство операций со списком/кортежем имеют сложность \(O(N)\) (Таблица 6.1.1).
