Открыть сервис

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 →