1. Введение: Python для алгоритмов и оценка сложности
Введение: Python для алгоритмов и оценка сложности
Зачем Python в алгоритмах
Python часто выбирают для изучения алгоритмов по трём причинам:
При этом важно помнить: скорость выполнения кода в Python обычно ниже, чем в компилируемых языках. Поэтому в алгоритмах особенно важны правильный выбор алгоритма и оценка сложности.
Что такое алгоритм в контексте курса
В рамках курса алгоритм — это:
Мы будем стремиться к решениям, которые:
Минимальный набор Python-инструментов для алгоритмов
Ввод и вывод
В учебных и соревновательных задачах часто важна скорость ввода.
input().sys.stdin.buffer.Пример быстрого чтения целых чисел:
Вывод обычно делают через print(), но при большом количестве строк может быть выгодно собирать строки в список и печатать один раз:
Стандартные структуры данных
Python даёт готовые структуры, которые мы будем активно использовать:
list — динамический массив.tuple — неизеняемая последовательность.dict — хеш-таблица (ассоциативный массив).set — множество на основе хеш-таблицы.collections.deque — двусторонняя очередь.heapq — очередь с приоритетом (минимальная куча).Документация:
Почему нужно уметь оценивать сложность
Оценка сложности отвечает на вопрос: как изменится время работы и расход памяти, если размер входных данных вырастет?
Например, если решение работает за 1 секунду при , то при другом алгоритме (с худшей сложностью) оно может занять минуты или часы.
Главная идея:
Нотация (Big O)
Интуитивное определение
Запись означает: время (или память) растёт не быстрее, чем некоторая функция , если достаточно велико.
Здесь:
Важно: Big O — это про порядок роста, а не про точное время в секундах.
Типичные классы сложности
| Обозначение | Название | Пример ситуации |
|---|---|---|
| | константная | доступ к a[i] у списка |
| | логарифмическая | бинарный поиск |
| | линейная | один проход по массиву |
| | линейно-логарифмическая | сортировка сравнениями |
| | квадратичная | двойной цикл по всем парам |
| | экспоненциальная | полный перебор подмножеств |
!Сравнение темпов роста разных классов сложности
Как оценивать сложность на практике
Правило: считаем доминирующие операции
Мы обычно оцениваем количество повторений “основной” операции:
Если основная операция повторяется примерно раз, это ; если раз — .
Циклы
1) Один цикл по массиву длины :
Сложность по времени: , потому что цикл выполняется раз.
2) Вложенные циклы (все пары):
Сложность по времени: , потому что внутренний цикл выполняется раз для каждого из значений i.
Логарифм и бинарный поиск
Бинарный поиск работает за , потому что на каждом шаге уменьшает область поиска примерно в 2 раза.
Интуиция:
Мы останавливаемся, когда остаётся 1 вариант. Это означает , отсюда , поэтому .
Здесь:
Рекурсия: что важно отслеживать
У рекурсивных алгоритмов обычно оценивают:
Простой пример:
Здесь рекурсия углубляется на уровней, значит по времени это .
Практическая заметка: в Python глубина рекурсии ограничена, поэтому многие алгоритмы (например, обход графа) часто делают итеративно.
Время и память: две разные сложности
Временная сложность
Это количество вычислений. Именно оно чаще всего определяет, пройдёт ли решение ограничение по времени.
Пространственная сложность
Это объём дополнительной памяти, не считая входных данных.
Примеры:
Баланс часто выглядит так:
Средний случай, худший случай и амортизированная сложность
Средний и худший случаи
Некоторые операции быстры обычно, но могут быть медленнее на специально подобранных данных.
Например, dict и set в Python имеют:
Для алгоритмического мышления важно различать:
Амортизированная сложность
Амортизированная сложность — это когда редкие дорогие операции “размазываются” по множеству дешёвых.
Классический пример — list.append(x):
Как измерять производительность в Python
Оценка Big O говорит о росте, но не даёт точного времени. Для практики полезно уметь измерять.
timeit
Для микробенчмарков используйте модуль timeit:
Пример идеи (не “истина в последней инстанции”, но хороший инструмент):
Правила измерений:
Частые ошибки новичков в оценке сложности
1) Путать “быстро на моём тесте” и “быстро по асимптотике”.
2) Не замечать вложенность:
3) Считать, что сортировка — это “почти линейно”. На практике сортировка сравнением обычно .
4) Игнорировать память: решение может быть быстрым, но не проходить по лимиту памяти.
Как эта статья связана с остальным курсом
Дальше мы будем строить курс так:
Почти в каждой теме мы будем делать одно и то же:
Краткое резюме
timeit.