mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-06 12:44:19 +00:00
build
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
---
|
||||
comments: true
|
||||
icon: material/timer-sand
|
||||
---
|
||||
|
||||
# Глава 2. Анализ сложности
|
||||
|
||||
{ class="cover-image" }
|
||||
|
||||
!!! abstract
|
||||
|
||||
Анализ сложности подобен пространственно-временному проводнику в огромной вселенной алгоритмов.
|
||||
|
||||
Он ведет нас вглубь двух измерений - времени и пространства, помогая искать более изящные решения.
|
||||
|
||||
## Содержание главы
|
||||
|
||||
- [2.1 Оценка эффективности алгоритмов](performance_evaluation.md)
|
||||
- [2.2 Итерация и рекурсия](iteration_and_recursion.md)
|
||||
- [2.3 Временная сложность](time_complexity.md)
|
||||
- [2.4 Пространственная сложность](space_complexity.md)
|
||||
- [2.5 Резюме](summary.md)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 2.1 Оценка эффективности алгоритмов
|
||||
|
||||
При проектировании алгоритмов мы последовательно стремимся к двум уровням целей.
|
||||
|
||||
1. **Найти решение задачи**: алгоритм должен надежно получать правильный ответ в заданном диапазоне входных данных.
|
||||
2. **Найти оптимальное решение**: для одной и той же задачи может существовать несколько решений, и нам хочется выбрать максимально эффективный алгоритм.
|
||||
|
||||
Иными словами, если задача в принципе решается, эффективность алгоритма становится главным критерием оценки его качества. Она включает два следующих измерения.
|
||||
|
||||
- **Временная эффективность**: сколько времени работает алгоритм.
|
||||
- **Пространственная эффективность**: сколько памяти занимает алгоритм.
|
||||
|
||||
Короче говоря, **наша цель - проектировать структуры данных и алгоритмы, которые "и быстры, и экономны по памяти"**. Эффективная оценка алгоритмов крайне важна, потому что только так можно сравнивать разные алгоритмы и направлять процесс их проектирования и оптимизации.
|
||||
|
||||
Методы оценки эффективности в основном делятся на два типа: практическое тестирование и теоретическая оценка.
|
||||
|
||||
## 2.1.1 Практическое тестирование
|
||||
|
||||
Предположим, у нас есть алгоритм `A` и алгоритм `B`, оба решают одну и ту же задачу, и нам нужно сравнить их эффективность. Самый прямой способ - взять компьютер, запустить оба алгоритма и зафиксировать время работы и объем используемой памяти. Такой способ оценки отражает реальную ситуацию, но имеет и серьезные ограничения.
|
||||
|
||||
С одной стороны, **трудно исключить влияние факторов тестовой среды**. Аппаратная конфигурация влияет на производительность алгоритма. Например, если алгоритм имеет высокий уровень параллелизма, он лучше подходит для многоядерных CPU; если алгоритм интенсивно работает с памятью, он покажет себя лучше на быстрой памяти. Иными словами, результаты тестирования одного и того же алгоритма на разных машинах могут различаться. Это означает, что пришлось бы тестировать на самых разных машинах и усреднять результаты, а на практике это нереалистично.
|
||||
|
||||
С другой стороны, **полное тестирование требует больших ресурсов**. По мере изменения объема входных данных алгоритм может вести себя по-разному. Например, при небольшом объеме входных данных время работы алгоритма `A` может быть меньше, чем у алгоритма `B`; но при большом объеме результаты могут оказаться прямо противоположными. Поэтому для убедительных выводов пришлось бы тестировать входные данные множества разных масштабов, а это требует значительных вычислительных ресурсов.
|
||||
|
||||
## 2.1.2 Теоретическая оценка
|
||||
|
||||
Поскольку практическое тестирование имеет серьезные ограничения, можно попытаться оценить эффективность алгоритма только с помощью вычислений. Такой метод называется <u>асимптотическим анализом сложности (asymptotic complexity analysis)</u>, или сокращенно <u>анализом сложности</u>.
|
||||
|
||||
Анализ сложности показывает зависимость между временем и пространственными ресурсами, требуемыми алгоритму, и масштабом входных данных. **Он описывает тенденцию роста времени и памяти, необходимых алгоритму, по мере увеличения размера входных данных**. Это определение звучит немного тяжеловесно, поэтому полезно разложить его на три ключевые идеи.
|
||||
|
||||
- "Временные и пространственные ресурсы" соответствуют <u>временной сложности (time complexity)</u> и <u>пространственной сложности (space complexity)</u> соответственно.
|
||||
- "По мере увеличения размера входных данных" означает, что сложность отражает связь между эффективностью алгоритма и масштабом входа.
|
||||
- "Тенденция роста времени и пространства" означает, что анализ сложности интересуется не конкретными значениями времени или памяти, а тем, насколько быстро они растут.
|
||||
|
||||
**Анализ сложности устраняет недостатки практического тестирования**, что проявляется в следующих аспектах.
|
||||
|
||||
- Для него не нужно реально запускать код, а значит, он экологичнее и экономит ресурсы.
|
||||
- Он не зависит от тестовой среды, поэтому результаты анализа применимы ко всем платформам выполнения.
|
||||
- Он позволяет увидеть эффективность алгоритма при разных объемах данных, особенно на больших данных.
|
||||
|
||||
!!! tip
|
||||
|
||||
Если понятие сложности пока все еще кажется тебе запутанным, не переживай: мы подробно разберем его в следующих разделах.
|
||||
|
||||
Анализ сложности дает нам "линейку" для оценки эффективности алгоритмов, позволяя измерять, сколько времени и памяти требуется для выполнения конкретного алгоритма, и сравнивать эффективность разных алгоритмов между собой.
|
||||
|
||||
Сложность - это математическое понятие, поэтому для начинающих оно может показаться довольно абстрактным и сравнительно трудным. С этой точки зрения анализ сложности, возможно, не лучший самый первый материал для знакомства. Однако, когда мы обсуждаем особенности конкретной структуры данных или алгоритма, почти невозможно не затронуть скорость его работы и использование памяти.
|
||||
|
||||
В итоге рекомендуется еще до глубокого погружения в структуры данных и алгоритмы **сформировать хотя бы первичное понимание анализа сложности, чтобы уметь выполнять анализ сложности простых алгоритмов**.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 2.5 Резюме
|
||||
|
||||
### 1. Ключевые выводы
|
||||
|
||||
**Оценка эффективности алгоритмов**
|
||||
|
||||
- Временная эффективность и пространственная эффективность - два главных показателя, по которым оценивают качество алгоритма.
|
||||
- Мы можем оценивать эффективность алгоритма с помощью практического тестирования, но при этом трудно устранить влияние тестовой среды, а само тестирование потребляет много вычислительных ресурсов.
|
||||
- Анализ сложности устраняет недостатки практического тестирования, дает результаты, применимые ко всем платформам выполнения, и позволяет увидеть эффективность алгоритма при разных масштабах данных.
|
||||
|
||||
**Временная сложность**
|
||||
|
||||
- Временная сложность используется для оценки того, как меняется время работы алгоритма с ростом объема данных. Она хорошо подходит для оценки эффективности, но в некоторых случаях может давать недостаточно точное сравнение, например когда входные данные малы или когда временные сложности совпадают.
|
||||
- Худшая временная сложность обозначается с помощью нотации Big $O$ и соответствует асимптотической верхней границе функции, отражая уровень роста числа операций $T(n)$ при стремлении $n$ к положительной бесконечности.
|
||||
- Вывод временной сложности включает два шага: сначала подсчитывается число операций, затем определяется асимптотическая верхняя граница.
|
||||
- Распространенные временные сложности в порядке роста: $O(1)$, $O(\log n)$, $O(n)$, $O(n \log n)$, $O(n^2)$, $O(2^n)$ и $O(n!)$.
|
||||
- Временная сложность некоторых алгоритмов не фиксирована, а зависит от распределения входных данных. Различают худшую, лучшую и среднюю временную сложность; лучшая временная сложность используется редко, потому что для ее достижения вход обычно должен удовлетворять строгим условиям.
|
||||
- Средняя временная сложность отражает эффективность алгоритма на случайных входных данных и ближе всего к его поведению в практических сценариях. Для ее вычисления нужно знать распределение входных данных и рассчитать соответствующее математическое ожидание.
|
||||
|
||||
**Пространственная сложность**
|
||||
|
||||
- Пространственная сложность играет роль, аналогичную временной: она показывает тенденцию роста потребления памяти по мере увеличения объема данных.
|
||||
- Память, связанная с выполнением алгоритма, можно разделить на входное пространство, временное пространство и выходное пространство. Обычно входное пространство не включается в расчет пространственной сложности. Временное пространство можно разбить на временные данные, пространство кадров стека и пространство инструкций; при этом пространство кадров стека обычно влияет на сложность только в рекурсивных функциях.
|
||||
- Обычно нас интересует только худшая пространственная сложность, то есть пространственная сложность алгоритма при худшем наборе входных данных и в худший момент времени выполнения.
|
||||
- Распространенные пространственные сложности в порядке роста: $O(1)$, $O(\log n)$, $O(n)$, $O(n^2)$ и $O(2^n)$.
|
||||
|
||||
### 2. Q & A
|
||||
|
||||
**Q**: Является ли пространственная сложность хвостовой рекурсии равной $O(1)$?
|
||||
|
||||
Теоретически пространственную сложность хвостово-рекурсивных функций можно оптимизировать до $O(1)$ . Однако большинство языков программирования (например Java, Python, C++, Go, C# и другие) не поддерживают автоматическую оптимизацию хвостовой рекурсии, поэтому на практике пространственная сложность обычно считается равной $O(n)$ .
|
||||
|
||||
**Q**: В чем разница между терминами function и method?
|
||||
|
||||
<u>Функция (function)</u> может выполняться независимо, и все ее параметры передаются явно. <u>Метод (method)</u> связан с объектом, неявно получает объект, который его вызывает, и может работать с данными, содержащимися в экземпляре класса.
|
||||
|
||||
Ниже это проиллюстрировано на примере нескольких распространенных языков программирования.
|
||||
|
||||
- C - процедурный язык программирования без объектно-ориентированной модели, поэтому в нем есть только функции. Однако мы можем имитировать объектно-ориентированное программирование через структуры (`struct`), и функции, связанные со структурами, эквивалентны методам в других языках.
|
||||
- Java и C# - объектно-ориентированные языки программирования, в которых блоки кода (методы) обычно являются частью класса. Статические методы по поведению похожи на функции, потому что они привязаны к классу и не могут обращаться к конкретным переменным экземпляра.
|
||||
- C++ и Python поддерживают как процедурное программирование (функции), так и объектно-ориентированное программирование (методы).
|
||||
|
||||
**Q**: Отражает ли диаграмма "распространенных типов пространственной сложности" абсолютный размер занятой памяти?
|
||||
|
||||
Нет, эта диаграмма показывает пространственную сложность, а значит отражает именно тенденцию роста, а не абсолютный объем занятого пространства.
|
||||
|
||||
Если взять $n = 8$ , можно заметить, что значения на кривых не совпадают напрямую с соответствующими функциями. Это связано с тем, что каждая кривая содержит константный член, который сжимает диапазон значений до визуально удобного масштаба.
|
||||
|
||||
На практике, поскольку мы обычно не знаем, какова "константная" сложность каждого метода, только по сложности мы, как правило, не можем выбрать оптимальное решение для случая $n = 8$ . Но для $n = 8^5$ выбор уже очевиден: в этой области доминирует именно тенденция роста.
|
||||
|
||||
**Q**: Бывают ли случаи, когда в реальных сценариях алгоритм специально проектируют так, чтобы жертвовать временем ради пространства или пространством ради времени?
|
||||
|
||||
На практике в большинстве случаев выбирают обмен пространства на время. Например, для индексов в базах данных обычно строят B+ деревья или хеш-индексы, расходуя значительный объем памяти ради эффективных запросов уровня $O(\log n)$ или даже $O(1)$.
|
||||
|
||||
В сценариях, где память особенно дорога, наоборот, могут жертвовать временем ради пространства. Например, в embedded-разработке память устройства очень ограничена, поэтому инженеры могут отказаться от хеш-таблиц и выбрать последовательный поиск по массиву, экономя память ценой более медленного поиска.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user