Практические примеры обозначения Big O на Java

Практические Java-примеры обозначения Big O

1. обзор

В этом руководстве мы поговорим о том, чтоBig O Notation means. We’ll go through a few examples to investigate its effect on the running time of your code.

2. Интуиция нотации Big O

Мы часто слышимperformance of an algorithm described using Big O Notation.

Изучение производительности алгоритмов - или алгоритмической сложности - относится к областиalgorithm analysis. Анализ алгоритма отвечает на вопрос о том, сколько ресурсов, таких как дисковое пространство или время, потребляет алгоритм.

Мы будем смотреть на время как на ресурс. Как правило, чем меньше времени занимает алгоритм, тем лучше.

3. Алгоритмы постоянного времени -O(1)с

Как этот размер входного алгоритма влияет на время его работы? Key to understanding Big O is understanding the rates at which things can grow. Рассматриваемая скорость здесьtime taken per input size.

Рассмотрим этот простой кусок кода:

int n = 1000;
System.out.println("Hey - your input is: " + n);

Ясно, что не имеет значения, что такоеn выше. Этот кусок кода занимает постоянное количество времени для запуска. Это не зависит от размера n.

Так же:

int n = 1000;
System.out.println("Hey - your input is: " + n);
System.out.println("Hmm.. I'm doing more stuff with: " + n);
System.out.println("And more: " + n);

В приведенном выше примере также постоянное время. Даже если для выполнения требуется в 3 раза больше времени, itdoesn’t depend on the size of the input, n. Мы обозначаем алгоритмы с постоянным временем следующим образом:O(1). Обратите внимание, чтоO(2),O(3) или дажеO(1000) будут означать одно и то же.

Нас не волнует, сколько именно времени потребуется на запуск, а только то, что это занимает постоянное время.

4. Алгоритмы логарифмического времени -O(log n)с

Алгоритмы с постоянным временем (асимптотически) самые быстрые. Logarithmic time is the next quickest. К сожалению, представить их немного сложнее.

Одним из распространенных примеров алгоритма логарифмического времени является алгоритмbinary search. Чтобы увидеть, как реализовать двоичный поиск в Java,click here.

Здесь важно то, чтоrunning time grows in proportion to the logarithm of the input (in this case, log to the base 2):

for (int i = 1; i < n; i = i * 2){
    System.out.println("Hey - I'm busy looking at: " + i);
}

Еслиn равно 8, вывод будет следующим:

Hey - I'm busy looking at: 1
Hey - I'm busy looking at: 2
Hey - I'm busy looking at: 4

Наш простой алгоритм запустил log (8) = 3 раза.

5. Линейные временные алгоритмы -O(n)с

После алгоритмов логарифмического времени мы получаем следующий самый быстрый класс:linear time algorithms.

Если мы говорим, что что-то растет линейно, мы имеем в виду, что оно растет прямо пропорционально размеру его входных данных.

Придумайте простой цикл for:

for (int i = 0; i < n; i++) {
    System.out.println("Hey - I'm busy looking at: " + i);
}

Сколько раз это делается для цикла? n раз, конечно! Мы не знаем точно, сколько времени это займет, и нас это не волнует.

Что мы действительно знаем, так это то, что простой алгоритм, представленный выше, будет линейно расти с размером его входных данных.

Мы предпочли бы время выполнения0.1n, чем(1000n + 1000), но оба алгоритма по-прежнему являются линейными; они оба растут прямо пропорционально размеру их ресурсов.

Опять же, если алгоритм был изменен на следующее:

for (int i = 0; i < n; i++) {
    System.out.println("Hey - I'm busy looking at: " + i);
    System.out.println("Hmm.. Let's have another look at: " + i);
    System.out.println("And another: " + i);
}

Время выполнения по-прежнему будет линейным по размеру входных данныхn. Обозначим линейные алгоритмы следующим образом:O(n).

Как и в случае с алгоритмами постоянного времени, мы не заботимся о специфике среды выполнения. O(2n+1) is the same as O(n), поскольку нотация Big O занимается ростом размеров входных данных.

6. N Log N Time Algorithms -O(n log n)с

n log n is the next class of algorithms. Время работы увеличивается пропорциональноn log n входа:

for (int i = 1; i <= n; i++){
    for(int j = 1; j < 8; j = j * 2) {
        System.out.println("Hey - I'm busy looking at: " + i + " and " + j);
    }
}

Например, еслиn равно 8, то этот алгоритм будет выполняться8 * log(8) = 8 * 3 = 24 раз. Имеем ли мы строгое неравенство или нет в цикле for, не имеет значения для обозначения Big O.

7. Полиномиальные временные алгоритмы -O(np)с

Далее у нас есть алгоритмы с полиномиальным временем. Эти алгоритмы даже медленнее, чем алгоритмыn log n.

Термин полином - это общий термин, который содержит квадратичный(n2), кубический(n3), четвертый(n4) и т. Д. функции. What’s important to know is that O(n2) is faster than O(n3) which is faster than O(n4), etc.

Давайте посмотрим на простой пример алгоритма квадратичного времени:

for (int i = 1; i <= n; i++) {
    for(int j = 1; j <= n; j++) {
        System.out.println("Hey - I'm busy looking at: " + i + " and " + j);
    }
}

Этот алгоритм будет выполняться82 = 64 раз. Обратите внимание: если бы мы вложили еще один цикл for, это стало бы алгоритмомO(n3).

8. Экспоненциальные временные алгоритмы -O(k _n) _

Теперь мы попадаем на опасную территорию; Эти алгоритмы растут пропорционально некоторому коэффициенту, который определяется степенью входного размера.

Например,O(2n) algorithms double with every additional input. Итак, еслиn = 2, эти алгоритмы будут выполняться четыре раза; еслиn = 3, они будут выполняться восемь раз (что-то вроде противоположности алгоритмов логарифмического времени).

АлгоритмыO(3n) утраиваются с каждым дополнительным вводом, алгоритмыO(kn) будут увеличиваться в k раз с каждым дополнительным вводом.

Давайте посмотрим на простой пример временного алгоритмаO(2n):

for (int i = 1; i <= Math.pow(2, n); i++){
    System.out.println("Hey - I'm busy looking at: " + i);
}

Этот алгоритм будет выполняться28 = 256 раз.

9. Факториальные временные алгоритмы -O(n!)с

В большинстве случаев это почти так же плохо, как и могло случиться. Этот класс алгоритмов имеет время выполнения, пропорциональноеfactorial размера ввода.

Классическим примером этого является решение проблемыtraveling salesman с использованием метода грубой силы для ее решения.

Объяснение решения проблемы коммивояжера выходит за рамки данной статьи.

Вместо этого давайте рассмотрим простой алгоритмO(n!), как в предыдущих разделах:

for (int i = 1; i <= factorial(n); i++){
    System.out.println("Hey - I'm busy looking at: " + i);
}

гдеfactorial(n) просто вычисляет n !. Если n равно 8, этот алгоритм будет выполняться8! = 40320 раз.

10. Асимптотические функции

Big O is what is known as an asymptotic function. Все это означает, что он заботится о производительности алгоритмаat the limit - т.е. - когда на него брошено много информации.

Big O не заботится о том, насколько хорошо ваш алгоритм работает с входными данными небольшого размера. Это связано с большими входными данными (подумайте о сортировке списка из миллиона номеров по сравнению с сортировка списка из 5 номеров).

Также следует отметить, чтоthere are other asymptotic functions. Big Θ (theta) и Big Ω (omega) также оба описывают алгоритмы на пределе (помните,the limit это означает только для огромных входных данных).

Чтобы понять разницу между этими тремя важными функциями, нам сначала нужно знать, что каждая из Big O, Big Θ и Big Ω описываетset (то есть набор элементов).

Здесь члены наших наборов сами являются алгоритмами:

  • Big O описывает набор всех алгоритмов, которые запускаютno worse выше определенной скорости (это верхняя граница)

  • И наоборот, Big Ω описывает набор всех алгоритмов, которые выполняютno better, превышающую определенную скорость (это нижняя граница).

  • Наконец, Big Θ описывает набор всех алгоритмов, которые запускаютat с определенной скоростью (это похоже на равенство)

Приведенные выше определения математически неточны, но они помогут нашему пониманию.

Usually, you’ll hear things described using Big O, но знать о Big Θ и Big Ω не помешает.

11. Заключение

В этой статье мы обсудили нотацию Big O и то, какunderstanding the complexity of an algorithm can affect the running time of your code.

Отличная визуализация разных классов сложностиcan be found here.

Как обычно, фрагменты кода для этого руководства можно найтиover on GitHub.