Каковы асимптотические функции? Что такое асимптота?
Учитывая функцию f (n), которая описывает количество ресурсов (процессорного времени, оперативной памяти, дискового пространства и т. Д.), Потребляемых алгоритмом при применении к входу размером n , мы определяем до трех асимптотических обозначений для описания его производительности для большая п .
Асимптота (или асимптотическая функция) - это просто некоторая другая функция (или отношение) g (n), к которой f (n) становится все ближе по мере того, как n растет все больше и больше, но никогда не достигает. Преимущество разговоров об асимптотических функциях состоит в том, что о них, как правило, гораздо проще говорить, даже если выражение для f (n) чрезвычайно сложно. Асимптотические функции используются как часть ограничительных обозначений, которые ограничивают f (n) выше или ниже.
(Примечание: в используемом здесь смысле асимптотические функции близки к исходной функции только после исправления некоторого постоянного ненулевого множителя, так как все три обозначения big-O / Θ / Ω игнорируют эти постоянные факторы из их рассмотрения.)
Каковы три асимптотические ограничивающие нотации и чем они отличаются?
Все три обозначения используются следующим образом:
f (n) = O (g (n))
где f (n) здесь - интересующая функция, а g (n) - некоторая другая асимптотическая функция, которой вы пытаетесь аппроксимировать f (n) . Это не должно восприниматься как равенство в строгом смысле, но формальное утверждение о том, как быстро f (n) растет относительно n по сравнению с g (n) , когда n становится большим. Пуристы часто используют альтернативные обозначения f (n) ∈ O (g (n)), чтобы подчеркнуть, что символ O (g (n)) действительно представляет собой целое семейство функций, которые имеют общую скорость роста.
Обозначение Big-ϴ (Theta) утверждает равенство при росте f (n) до постоянного множителя (подробнее об этом позже). Он ведет себя аналогично =
оператору по темпам роста.
Обозначение Big-O описывает верхнюю границу роста f (n) . Он ведет себя аналогично ≤
оператору по темпам роста.
Обозначение Big Ω (Omega) описывает нижнюю границу роста f (n) . Он ведет себя аналогично ≥
оператору по темпам роста.
Есть много других асимптотических обозначений , но они встречаются не так часто, как в литературе по информатике.
Обозначения Big-O и подобные им часто служат способом сравнения сложности времени .
Что такое сложность времени?
Временная сложность представляет собой причудливый термин для количества времени T (n), которое требуется алгоритму для выполнения в зависимости от его входного размера n . Это можно измерить в реальном времени (например, секундах), количестве инструкций процессора и т. Д. Обычно предполагается, что алгоритм будет работать на вашем обычном компьютере с архитектурой фон Неймана . Но, конечно, вы можете использовать сложность времени, чтобы говорить о более экзотических вычислительных системах, где все может быть иначе!
Также часто говорят о сложности пространства, используя обозначение Big-O. Сложность пространства - это объем памяти (хранилища), необходимый для завершения алгоритма, который может быть ОЗУ, диском и т. Д.
Возможно, один алгоритм работает медленнее, но использует меньше памяти, а другой - быстрее, но использует больше памяти. Каждый из них может быть более подходящим в различных обстоятельствах, если ресурсы ограничены по-разному. Например, встроенный процессор может иметь ограниченную память и предпочитать более медленный алгоритм, в то время как сервер в центре обработки данных может иметь большой объем памяти и предпочитать более быстрый алгоритм.
Расчет Big-ϴ
Вычисление Big-ϴ алгоритма - это тема, которая может заполнить небольшой учебник или примерно половину семестра курса бакалавриата: в этом разделе будут рассмотрены основы.
Дана функция f (n) в псевдокоде:
int f(n) {
int x = 0;
for (int i = 1 to n) {
for (int j = 1 to n) {
++x;
}
}
return x;
}
Какова сложность времени?
Внешний цикл выполняется n раз. Каждый раз, когда работает внешний цикл, внутренний цикл выполняется n раз. Это ставит время работы на T (n) = n 2 .
Рассмотрим вторую функцию:
int g(n) {
int x = 0;
for (int k = 1 to 2) {
for (int i = 1 to n) {
for (int j = 1 to n) {
++x;
}
}
}
return x;
}
Внешний цикл работает дважды. Средний цикл работает n раз. Каждый раз, когда выполняется средний цикл, внутренний цикл выполняется n раз. Это устанавливает время работы при T (n) = 2n 2 .
Теперь вопрос в том, каково асимптотическое время выполнения обеих функций?
Чтобы рассчитать это, мы выполним два шага:
- Удалить константы. Поскольку алгоритмы увеличиваются во времени за счет входных данных, другие термины доминируют во времени выполнения, что делает их неважными.
- Удалить все, кроме самого большого срока. По мере того как n уходит в бесконечность, n 2 быстро опережает n .
Ключевым моментом здесь является фокус на доминирующих терминах и упрощение до этих терминов .
T (n) = n 2 ∈ ϴ (n 2 )
T (n) = 2n 2 ∈ ϴ (n 2 )
Если у нас есть другой алгоритм с несколькими терминами, мы упростили бы его, используя те же правила:
T (n) = 2n 2 + 4n + 7 ∈ ϴ (n 2 )
Ключ ко всем этим алгоритмам заключается в том, что мы фокусируемся на самых больших терминах и удаляем константы . Мы не смотрим на фактическое время выполнения, а на относительную сложность .
Расчет Big-Ω и Big-O
Во-первых, имейте в виду, что в неофициальной литературе «Big-O» часто рассматривается как синоним Big-Θ, возможно, потому, что греческие буквы сложно набрать. Так что, если кто-то неожиданно спросит вас о Big-O алгоритма, он, вероятно, захочет его Big-Θ.
Теперь, если вы действительно хотите вычислить Big-Ω и Big-O в формальных значениях, определенных ранее, у вас есть главная проблема: существует бесконечно много описаний Big-Ω и Big-O для любой данной функции! Это все равно, что спросить, какие числа меньше или равны 42. Есть много возможностей.
Для алгоритма с T (n) ∈ ϴ (n 2 ) любые из следующих формально допустимых утверждений:
- T (n) ∈ O (n 2 )
- T (n) ∈ O (n 3 )
- T (n) ∈ O (n 5 )
- T (n) ∈ O (n 12345 × e n )
- T (n) ∈ Ω (n 2 )
- T (n) ∈ Ω (n)
- T (n) ∈ Ω (log (n))
- T (n) ∈ Ω (log (log (n)))
- T (n) ∈ Ω (1)
Но неверно утверждать, что T (n) ∈ O (n) или T (n) ∈ Ω (n 3 ) .
Что такое относительная сложность? Какие существуют классы алгоритмов?
Если мы сравним два разных алгоритма, их сложность при переходе на бесконечность обычно возрастает. Если мы посмотрим на разные типы алгоритмов, они могут оставаться относительно одинаковыми (скажем, отличающимися постоянным фактором) или могут сильно расходиться. Это причина для выполнения анализа Big-O: определить, будет ли алгоритм работать разумно с большими входными данными.
Классы алгоритмов разбиваются следующим образом:
Θ (1) - постоянная. Например, выбор первого номера в списке всегда будет занимать одинаковое количество времени.
Θ (n) - линейный. Например, повторение списка всегда будет занимать время, пропорциональное размеру списка, n .
Θ (log (n)) - логарифмическая (база обычно не имеет значения). Алгоритмы, которые разделяют пространство ввода на каждом шаге, такие как бинарный поиск, являются примерами.
Θ (n × log (n)) - линейное время логарифмическое («линеарифмическое»). Эти алгоритмы обычно разделяют и побеждают ( log (n) ), в то же время повторяя ( n ) все входные данные. Многие популярные алгоритмы сортировки (сортировка слиянием, Timsort) попадают в эту категорию.
Θ (n m ) - многочлен ( n возведен в любую постоянную m ). Это очень распространенный класс сложности, часто встречающийся во вложенных циклах.
Θ (m n ) - экспоненциальный (любая константа m, возведенная в n ). Многие рекурсивные и графовые алгоритмы попадают в эту категорию.
Θ (n!) - факториал. Некоторые графовые и комбинаторные алгоритмы имеют факториальную сложность.
Это как-то связано с лучшим / средним / худшим случаем?
Нет. Big-O и его семейство обозначений говорят о конкретной математической функции . Это математические инструменты, используемые для характеристики эффективности алгоритмов, но понятие наилучшего / среднего / наихудшего случая не имеет отношения к описанной здесь теории темпов роста.
Чтобы говорить о Big-O алгоритма, нужно взять на вооружение конкретную математическую модель алгоритма с ровно одним параметром n
, который должен описывать «размер» входных данных в каком бы то ни было смысле. Но в реальном мире входы имеют гораздо больше структуры, чем просто их длина. Если бы это был алгоритм сортировки, я мог бы питаться в строках "abcdef"
, "fedcba"
или "dbafce"
. Все они имеют длину 6, но один из них уже отсортирован, один перевернут, а последний - просто случайный беспорядок. Некоторые алгоритмы сортировки (например, Timsort) работают лучше, если входные данные уже отсортированы. Но как включить эту неоднородность в математическую модель?
Типичный подход состоит в том, чтобы просто предположить, что входные данные поступают от некоторого случайного, вероятностного распределения Затем вы усредняете сложность алгоритма по всем входам с длиной n
. Это дает вам модель сложности в среднем случае . Отсюда вы можете использовать обозначения Big-O / Θ / Ω как обычно, чтобы описать поведение среднего случая.
Но если вас беспокоят атаки типа «отказ в обслуживании», возможно, вам следует быть более пессимистичными. В этом случае безопаснее предположить, что единственными входными данными являются те, которые вызывают наибольшее количество горе в вашем алгоритме. Это дает вам модель сложности алгоритма в худшем случае . После этого вы можете говорить о Big-O / Θ / Ω и т. Д. Модели наихудшего случая .
Точно так же вы также можете сфокусировать свой интерес исключительно на входах, с которыми у вашего алгоритма меньше всего проблем, чтобы прийти к модели наилучшего случая , а затем взглянуть на Big-O / Θ / Ω и т. Д.