O-большое¶
O-большое (также «О-большое», «O-нотация», «асимптотическая верхняя оценка») — математическое обозначение, используемое в теории алгоритмов и анализе сложности вычислений для описания асимптотического поведения функции, чаще всего — времени выполнения алгоритма или объёма используемой памяти в зависимости от размера входных данных. Формально, запись \( f(n) = O(g(n)) \) означает, что существуют такие положительные константы \( c \) и \( n_0 \), что для всех \( n \ge n_0 \) выполняется неравенство \( 0 \le f(n) \le c \cdot g(n) \). Таким образом, O-большое задаёт верхнюю границу скорости роста функции, игнорируя постоянные множители и члены низшего порядка, что позволяет сравнивать алгоритмы в отрыве от конкретной реализации и аппаратного обеспечения.
¶История
Понятие O-большого было введено немецким математиком Паулем Бахманом в 1894 году в его книге «Analytische Zahlentheorie» (рус. «Аналитическая теория чисел»). Первоначально обозначение использовалось в контексте теории чисел для оценки остаточных членов в асимптотических разложениях. В 1909 году другой немецкий математик, Эдмунд Ландау, популяризировал это обозначение в своих работах, и оно стало широко применяться в анализе. В информатику O-большое пришло в середине XX века вместе с развитием теории алгоритмов, в частности, благодаря работам Дональда Кнута, который в 1960–1970-х годах систематически использовал его для анализа сложности алгоритмов в своей серии книг «Искусство программирования». С тех пор O-нотация стала стандартным инструментом в компьютерных науках.
¶Формальное определение
Пусть \( f(n) \) и \( g(n) \) — функции, определённые на множестве натуральных чисел (или на подмножестве вещественных чисел, стремящемся к бесконечности). Говорят, что \( f(n) = O(g(n)) \) при \( n \to \infty \), если существуют положительные числа \( c \) и \( n_0 \) такие, что для всех \( n \ge n_0 \) выполняется: \[ |f(n)| \le c \cdot |g(n)|. \] В контексте анализа алгоритмов обычно рассматривают неотрицательные функции (время выполнения, количество операций), поэтому модуль опускают. Важно, что константа \( c \) может быть любой, а \( n_0 \) — пороговым значением, начиная с которого неравенство выполняется. Это означает, что O-нотация не учитывает поведение функции на малых \( n \) (например, для \( n < n_0 \)), а также нечувствительна к постоянным множителям.
¶Связанные обозначения
Помимо O-большого, в асимптотическом анализе используются и другие обозначения, образующие семейство так называемых символов Ландау:
- Ω-большое (омега-большое): \( f(n) = \Omega(g(n)) \) означает, что существуют \( c > 0 \) и \( n_0 \) такие, что \( f(n) \ge c \cdot g(n) \) для всех \( n \ge n_0 \). Это нижняя асимптотическая оценка.
- Θ-большое (тета-большое): \( f(n) = \Theta(g(n)) \) означает, что \( f(n) = O(g(n)) \) и \( f(n) = \Omega(g(n)) \) одновременно. То есть функция растёт так же, как \( g(n) \), с точностью до постоянного множителя.
- o-малое (о-малое): \( f(n) = o(g(n)) \) означает, что для любой положительной константы \( c \) существует \( n_0 \) такое, что \( f(n) < c \cdot g(n) \) для всех \( n \ge n_0 \). Это более строгая верхняя оценка, чем O-большое: \( f(n) \) растёт строго медленнее, чем \( g(n) \).
- ω-малое (омега-малое): \( f(n) = \omega(g(n)) \) означает, что для любой положительной константы \( c \) существует \( n_0 \) такое, что \( f(n) > c \cdot g(n) \) для всех \( n \ge n_0 \). Это более строгая нижняя оценка.
¶Применение в анализе алгоритмов
В информатике O-большое используется для классификации алгоритмов по их временной и пространственной сложности. Наиболее распространённые классы сложности, упорядоченные по скорости роста (от медленного к быстрому):
- O(1) — константная сложность. Время выполнения не зависит от размера входных данных. Пример: доступ к элементу массива по индексу.
- O(log n) — логарифмическая сложность. Время растёт пропорционально логарифму размера данных. Пример: бинарный поиск в отсортированном массиве.
- O(n) — линейная сложность. Время растёт прямо пропорционально размеру данных. Пример: поиск максимального элемента в неотсортированном массиве.
- O(n log n) — квазилинейная сложность. Характерна для эффективных алгоритмов сортировки, таких как быстрая сортировка (в среднем случае), сортировка слиянием, пирамидальная сортировка.
- O(n²) — квадратичная сложность. Время растёт пропорционально квадрату размера данных. Пример: сортировка пузырьком, вложенные циклы.
- O(2ⁿ) — экспоненциальная сложность. Время удваивается с каждым увеличением размера данных. Пример: рекурсивное решение задачи коммивояжёра методом полного перебора.
- O(n!) — факториальная сложность. Ещё более быстрый рост, встречается в задачах перестановок.
На практике алгоритмы с экспоненциальной и факториальной сложностью считаются неэффективными для больших объёмов данных, так как время выполнения становится неприемлемо большим.
¶Примеры
Рассмотрим простой алгоритм суммирования элементов массива:
``python def sum_array(arr): total = 0 for x in arr: total += x return total ``
Здесь выполняется один проход по массиву из \( n \) элементов, поэтому количество операций пропорционально \( n \). Сложность — \( O(n) \).
Другой пример — алгоритм проверки, есть ли в массиве повторяющиеся элементы, с помощью вложенных циклов:
``python def has_duplicates(arr): n = len(arr) for i in range(n): for j in range(i+1, n): if arr[i] == arr[j]: return True return False ``
В худшем случае (если дубликатов нет) выполняется \( n(n-1)/2 \) сравнений, что даёт сложность \( O(n^2) \).
¶Ограничения и критика
Несмотря на широкое распространение, O-нотация имеет ряд ограничений:
- Игнорирование констант. Алгоритм с \( O(n) \) может на практике работать медленнее алгоритма с \( O(n^2) \) при малых \( n \) из-за больших накладных расходов. Например, алгоритм с \( 1000n \) операций хуже алгоритма с \( n^2/2 \) при \( n < 2000 \), хотя асимптотически первый лучше.
- Усреднённая оценка. O-большое часто даёт оценку в худшем случае, но для некоторых алгоритмов (например, быстрой сортировки) среднее время выполнения значительно лучше, чем худшее.
- Не учитывает особенности памяти и кэша. На современных компьютерах время доступа к памяти может сильно варьироваться в зависимости от того, находятся ли данные в кэше процессора, что не отражается в O-нотации.
- Сложность точного определения. Для некоторых алгоритмов точное асимптотическое поведение может быть трудно вычислить или зависеть от специфических свойств входных данных.
В связи с этим при выборе алгоритма на практике часто проводят эмпирическое тестирование на реальных данных, а не полагаются исключительно на асимптотические оценки.
¶Связанные понятия
- Амортизационный анализ — метод оценки среднего времени выполнения операции в последовательности операций, часто используемый для структур данных (например, динамических массивов).
- NP-полнота — класс задач, для которых не известно эффективных (полиномиальных) алгоритмов, и для которых O-нотация используется для доказательства невозможности быстрого решения.
- Сложность по памяти — аналогично временной сложности, O-нотация применяется для оценки объёма дополнительной памяти, требуемой алгоритму.
¶Источники
- Бахман П. «Analytische Zahlentheorie» (1894).
- Кнут Д. «Искусство программирования», том 1 (1968).
- Кормен Т., Лейзерсон Ч., Ривест Р., Штайн К. «Алгоритмы: построение и анализ» (3-е издание, 2009).
- Ландау Э. «Handbuch der Lehre von der Verteilung der Primzahlen» (1909).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →


