Big-O для восьмилетних детей?

Я спрашиваю больше о том, что это значит для моего кода. Я понимаю концепции математически, мне просто трудно понять, что они означают концептуально. Например, если нужно выполнить операцию O (1) над структурой данных, я понимаю, что количество операций, которые оно должно выполнить, не будет расти, потому что есть больше элементов. А операция O (n) будет означать, что вы будете выполнять набор операций для каждого элемента. Может ли кто-нибудь заполнить пробелы здесь?

  • Например, что именно будет делать операция O (n ^ 2)?
  • И что, черт возьми, это значит, если операция O (n log (n))?
  • И нужно ли кому-нибудь курить крэк, чтобы написать O (x!)?

Ответ 1

Один из способов думать об этом:

O (N ^ 2) означает, что для каждого элемента вы делаете что-то с каждым другим элементом, например сравниваете их. Bubble sort является примером этого.

O (N log N) означает, что для каждого элемента вы делаете то, что нужно только для просмотра журнала N элементов. Обычно это происходит потому, что вы знаете что-то об элементах, которые позволяют сделать эффективный выбор. Наиболее эффективные сортировки являются примером этого, например сортировка слиянием.

O (N!) Означает что-то сделать для всех возможных перестановок N элементов. Путешествующий продавец - пример этого, где есть N! способы посетить узлы, и решение грубой силы состоит в том, чтобы посмотреть на общую стоимость каждой возможной перестановки, чтобы найти оптимальную.

Ответ 2

Большое значение, которое означает обозначение Big-O для вашего кода, - это то, как оно масштабируется, когда вы удваиваете количество "вещей", на которых он работает. Вот конкретный пример:

Big-O       |  computations for 10 things |  computations for 100 things
----------------------------------------------------------------------
O(1)        |   1                         |     1
O(log(n))   |   3                         |     7
O(n)        |  10                         |   100
O(n log(n)) |  30                         |   700
O(n^2)      | 100                         | 10000

Итак, возьмите quicksort, который является O (n log (n)) против пузырьковой сортировки, которая является O (n ^ 2). При сортировке 10 вещей, quicksort в 3 раза быстрее, чем сортировка пузырьков. Но при сортировке 100 вещей, это в 14 раз быстрее! Очевидно, что важно выбрать самый быстрый алгоритм. Когда вы попадаете в базы данных с миллионом строк, это может означать разницу между вашим запросом, выполняемым за 0.2 секунды, в зависимости от времени.

Еще одна вещь, которую следует учитывать, заключается в том, что плохой алгоритм - это одно, что закон Мура не может помочь. Например, если у вас есть научный расчет, что O (n ^ 3), и он может вычислять 100 вещей в день, удваивая скорость процессора, вы получаете только 125 вещей в день. Однако, постучите, что вычисление до O (n ^ 2), и вы делаете 1000 вещей в день.

уточнение: На самом деле Big-O ничего не говорит о сравнительной производительности разных алгоритмов в той же конкретной точке размера, а скорее о сравнительной производительности одного и того же алгоритма в разных точках размера:

                 computations     computations       computations
Big-O       |   for 10 things |  for 100 things |  for 1000 things
----------------------------------------------------------------------
O(1)        |        1        |        1        |         1
O(log(n))   |        1        |        3        |         7
O(n)        |        1        |       10        |       100
O(n log(n)) |        1        |       33        |       664
O(n^2)      |        1        |      100        |     10000

Ответ 3

Вам может показаться полезным визуализировать его:

Big O Analysis

Кроме того, в LogY/LogX масштабируйте функции n 1/2 n, n 2 все выглядите как прямые линии, а на шкале LogY/X 2 n e n 10 n - прямые и n! является линейной (выглядит как n log n).

Ответ 4

Это может быть слишком математично, но здесь моя попытка. (Я математик.)

Если что-то есть O (f (n)), то время выполнения на n элементах будет равно A f (n) + B (измеряется, например, тактовыми циклами или CPU). Это ключ к пониманию того, что у вас также есть эти константы A и B, которые возникают из конкретной реализации. B представляет собой, по существу, "постоянные накладные расходы" для вашей операции, например некоторую предварительную обработку, которую вы делаете, которая не зависит от размера коллекции. A представляет скорость вашего фактического алгоритма обработки элементов.

Ключ, однако, состоит в том, что вы используете примечание O большого размера, чтобы определить , как хорошо что-то будет масштабироваться. Таким образом, эти константы не будут иметь большого значения: если вы пытаетесь выяснить, как масштабировать от 10 до 10000 единиц, кто заботится о постоянных накладных расходах B? Аналогичным образом, другие проблемы (см. Ниже), безусловно, перевесят вес мультипликативной постоянной A.

Таким образом, реальная сделка равна f (n). Если f растет вовсе не с n, т.е. f (n) = 1, тогда вы будете фантастически масштабироваться - ваше время выполнения всегда будет только A + B. Если f растет линейно с n, т.е. f (n) = n, ваше время работы будет масштабироваться в значительной степени, как лучше всего, как можно ожидать --- если ваши пользователи ждут 10 нс для 10 элементов, они будут ждать 10000 нс для 10000 элементов (игнорируя константу аддитивности). Но если он растет быстрее, например n 2 то у вас проблемы; вещи начнут замедляться слишком сильно, когда вы получите большие коллекции. f (n) = n log (n) - хороший компромисс, обычно: ваша операция не может быть настолько простой, чтобы давать линейное масштабирование, но вам удалось сократить все, чтобы она масштабировалась намного лучше, чем f (n) = n 2.

Практически, вот несколько хороших примеров:

  • O (1): извлечение элемента из массива. Мы точно знаем, где он находится в памяти, поэтому мы просто пойдем. Не имеет значения, имеет ли коллекция 10 предметов или 10000; он по-прежнему находится в индексе (скажем) 3, поэтому мы просто переходим к местоположению 3 в памяти.
  • O (n): извлечение элемента из связанного списка. Здесь A = 0.5, потому что в среднем вам придется пройти через 1/2 связанного списка, прежде чем найти элемент, который вы ищете.
  • O (n 2): различные "немые" алгоритмы сортировки. Поскольку, как правило, их стратегия включает в себя для каждого элемента (n), вы смотрите на все остальные элементы (так что разное n, давая n 2), затем расположите себя в нужном месте.
  • O (n log (n)): различные "интеллектуальные" алгоритмы сортировки. Оказывается, вам нужно всего лишь рассмотреть 10 элементов в коллекции 10 10 чтобы разумно сортировать себя по отношению ко всем остальным в коллекции. Потому что все остальные также будут искать 10 элементов, а возникающее поведение будет организовано правильно, чтобы этого было достаточно, чтобы создать отсортированный список.
  • O (n!): алгоритм, который "пытается все", поскольку существует (пропорционально) n! возможные комбинации из n элементов, которые могли бы решить данную проблему. Таким образом, он просто перебирает все такие комбинации, пытается их, а затем останавливается всякий раз, когда это удается.

Ответ 5

Ответ don.neufeld очень хорош, но я бы, вероятно, объяснил это в двух частях: во-первых, там грубая иерархия O(), к которой относится большинство алгоритмов. Затем вы можете посмотреть на каждый из них, чтобы придумать эскизы того, что делают типичные алгоритмы сложности времени.

Для практических целей единственное, что когда-либо имеет значение O(), следующее:

  • O (1) "постоянное время" - требуемое время не зависит от размера ввода. В качестве грубой категории я бы включил алгоритмы, такие как поиск хэша и Union-Find здесь, хотя ни один из них не является фактически O (1).
  • O (log (n)) "логарифмический" - он становится медленнее, когда вы получаете большие входы, но как только ваш ввод становится довольно большим, он не изменится достаточно, чтобы беспокоиться. Если ваше время выполнения в порядке с данными разумного размера, вы можете затопить его с таким количеством дополнительных данных, сколько хотите, и все будет в порядке.
  • O (n) "linear" - чем больше входных данных, тем дольше это требуется в четном компромиссе. Три раза размер ввода будет примерно в три раза длиннее.
  • O (n log (n)) "лучше, чем квадратичный" - увеличивает размер входного размера, но он все еще управляем. Алгоритм, вероятно, порядочен, просто потому, что основная проблема сложнее (решения менее локализованы по отношению к входным данным), чем те проблемы, которые могут быть решены в линейном времени. Если ваши входные размеры встают там, не предполагайте, что вы могли бы обойтись в два раза больше размера, не изменяя архитектуру (например, перемещая вещи на ночные партийные вычисления или не делая вещи за кадр). Это нормально, если размер ввода немного увеличивается; просто следите за мультипликаторами.
  • O (n ^ 2) "квадратичный" - он действительно будет работать только до определенного размера вашего ввода, поэтому обратите внимание на то, насколько он может быть большой. Кроме того, ваш алгоритм может сосать - задумайтесь над тем, есть ли алгоритм O (n log (n)), который даст вам то, что вам нужно. Как только вы здесь, почувствуйте себя очень благодарным за удивительное оборудование, в котором мы были одарены. Не так давно то, что вы пытаетесь сделать, было бы невозможно для всех практических целей.
  • O (n ^ 3) "кубический" - качественно не все, отличное от O (n ^ 2). Те же комментарии применяются, только более того. Там есть приличный шанс, что более умный алгоритм мог бы на этот раз сбрить что-то меньшее, например O (n ^ 2 log (n)) или O (n ^ 2.8...), но опять же есть хороший шанс, что это не будет стоить проблем. (Вы уже ограничены в своем практическом размере ввода, поэтому постоянные факторы, которые могут потребоваться для более умных алгоритмов, вероятно, восполнят их преимущества для практических случаев. Кроме того, мышление медленное, позволяя компьютеру пережевывать его, может сэкономить ваше время в целом.)
  • O (2 ^ n) "экспоненциальный" - проблема либо фундаментально усложняется, либо вы идиот. Эти проблемы имеют для них узнаваемый вкус. Размеры вашего ввода ограничены довольно жестким пределом. Вы быстро узнаете, вписываетесь ли вы в этот предел.

И что это. Есть много других возможностей, которые подходят между ними (или больше, чем O (2 ^ n)), но они не часто бывают на практике, и они не качественно отличаются от одного из них. Кубические алгоритмы уже немного растянуты; Я включил их только потому, что часто сталкивался с ними, чтобы их можно было упомянуть (например, умножение матрицы).

Что на самом деле происходит для этих классов алгоритмов? Ну, я думаю, вы хорошо начали, хотя есть много примеров, которые не соответствовали бы этим характеристикам. Но для вышесказанного я бы сказал, что обычно это что-то вроде:

  • O (1) - вы больше всего смотрите на блок фиксированных размеров ваших входных данных и, возможно, ни один из них. Пример: максимум отсортированного списка.
    • Или ваш размер ввода ограничен. Пример: добавление двух чисел. (Заметим, что добавление N чисел - это линейное время.)
  • O (log n) - каждый элемент вашего ввода говорит вам достаточно, чтобы игнорировать значительную часть остальной части ввода. Пример: когда вы смотрите на элемент массива в двоичном поиске, его значение говорит вам, что вы можете игнорировать "половину" вашего массива, не глядя ни на что. Или аналогично, элемент, на который вы смотрите, дает вам достаточно резюме о доле оставшегося ввода, который вам не нужно будет смотреть на него.
    • Нет ничего особенного в половинах, хотя - если вы можете игнорировать только 10% вашего ввода на каждом шаге, он все еще логарифмичен.
  • O (n) - вы выполняете фиксированный объем работы для каждого элемента ввода. (Но см. Ниже.)
  • O (n log (n)) - существует несколько вариантов.
    • Вы можете разделить вход на две сваи (не более линейного времени), решить проблему самостоятельно на каждой куче, а затем объединить две сваи, чтобы сформировать окончательное решение. Независимость двух свай является ключевой. Пример: классическая рекурсивная слияния.
    • Каждый переход по данным по линейному времени получает вас на полпути к вашему решению. Пример: quicksort, если вы думаете с точки зрения максимального расстояния каждого элемента до его окончательной упорядоченной позиции на каждом этапе разбиения (и да, я знаю, что это фактически O (n ^ 2) из-за вырожденных вариантов поворота. попадает в мою категорию O (n log (n)).)
  • O (n ^ 2) - вам нужно посмотреть на каждую пару входных элементов.
    • Или вы этого не сделаете, но вы думаете, что делаете, и используете неправильный алгоритм.
  • O (n ^ 3) - um... У меня нет быстрой характеристики. Вероятно, это одно из:
    • Вы умножаете матрицы
    • Вы смотрите на каждую пару входов, но операция, которую вы выполняете, требует снова просмотреть все входы.
    • вся структура графика вашего ввода релевантна
  • O (2 ^ n) - вам нужно рассмотреть все возможные подмножества ваших входов.

Ни один из них не является строгим. Особенно нелинейные алгоритмы времени (O (n)): я мог бы привести несколько примеров, где вы должны посмотреть все входы, затем половину из них, затем половину из них и т.д. Или наоборот - - вы складываете пары входов, а затем рекурсируете на выходе. Они не соответствуют описанию выше, так как вы не смотрите на каждый вход один раз, но он все еще выходит в линейном времени. Тем не менее, 99,2% времени, линейное время означает просмотр каждого входа один раз.

Ответ 6

Многие из них легко продемонстрировать с чем-то не-программированием, например, перетасовкой карт.

Сортировка колоды карт, пройдя всю колоду, чтобы найти туз пик, а затем пройти через всю колоду, чтобы найти 2 пика, и так далее будет худшим случаем n ^ 2, если колода уже была отсортированы в обратном порядке. Вы просмотрели все 52 карты 52 раза.

В общем, действительно плохие алгоритмы не обязательно преднамеренно, они обычно являются неправильным использованием чего-то другого, например вызова метода, линейного внутри какого-либо другого метода, который линейно повторяется над одним и тем же набором.

Ответ 7

Хорошо - здесь есть очень хорошие ответы, но почти все они, похоже, совершают ту же ошибку, и это пронизывает обычное использование.

Покажем, что f (n) = O (g (n)), если до коэффициента масштабирования и для всех n, большего, чем некоторые n0, g (n) больше, чем е (п). То есть f (n) растет быстрее, чем, или ограничено сверху, g (n). Это ничего не говорит о том, как быстро растет f (n), за исключением того, что гарантируется, что он не будет хуже, чем g (n).

Конкретный пример: n = O (2 ^ n). Мы все знаем, что n растет гораздо быстрее, чем 2 ^ n, поэтому дает нам право говорить, что оно ограничено сверху показательной функцией. Существует много места между n и 2 ^ n, поэтому это не очень жесткая привязка, но это все еще законная граница.

Почему мы (ученые-компьютерщики) используем границы, а не точны? Поскольку а) границы часто легче доказывать, и б) это дает нам короткую руку для выражения свойств алгоритмов. Если я скажу, что мой новый алгоритм равен O (n.log n), это означает, что в худшем случае его время выполнения будет ограничено сверху n.log n на n входах, при достаточно большом n (хотя см. Мои комментарии ниже когда я мог бы не понимать худший случай).

Если вместо этого мы хотим сказать, что функция растет точно так же быстро, как и какая-то другая функция, мы используем theta для создания этой точки (я напишу T (f (n)), чтобы обозначить \Theta of f (n) в уценке). T (g (n)) является короткой рукой для ограничения от выше и ниже на g (n), опять же, до коэффициента масштабирования и асимптотически.

Это f (n) = T (g (n)) <= > f (n) = O (g (n)) и g (n) = O (f (n)). В нашем примере мы можем видеть, что n!= T (2 ^ n), поскольку 2 ^ n!= O (n).

Зачем беспокоиться об этом? Потому что в вашем вопросе вы пишете: "Кто-то должен был бы курить, чтобы написать O (x!)?" Ответ отрицательный, потому что в основном все, что вы пишете, будет ограничено сверху факториальной функцией. Время работы quicksort - O (n!) - это просто не плотная граница.

Здесь также есть еще одно измерение тонкости. Обычно мы говорим о вводе наихудшего случая, когда мы используем нотацию O (g (n)), поэтому мы делаем составную инструкцию: в худшем случае время работы не будет хуже алгоритм, который принимает шаги g (n), снова по модулю масштабирования и для достаточно большого числа n. Но иногда мы хотим поговорить о времени работы среднего и даже лучших случаев.

Ванильная быстрая сортировка - это, как всегда, хороший пример. Это T (n ^ 2) в худшем случае (на самом деле он займет не менее n ^ 2 шага, но не значительно больше), но T (n.log n) в среднем случае, то есть ожидаемое число шаги пропорциональны n.log n. В лучшем случае это также T (n.log n), но вы могли бы улучшить это, например, проверять, был ли массив уже отсортирован, и в этом случае лучшим временем выполнения будет T (n).

Как это относится к вашему вопросу о практических реализациях этих границ? Ну, к сожалению, нотация O() скрывает константы, с которыми приходится сталкиваться реалиям реального мира. Поэтому, хотя мы можем сказать, что, например, для операции T (n ^ 2) мы должны посетить всевозможные пары элементов, мы не знаем, сколько раз мы должны их посещать (за исключением того, что это не функция п). Поэтому мы могли бы посещать каждую пару 10 раз, или 10 ^ 10 раз, а оператор T (n ^ 2) не делает различий. Функции нижнего порядка также скрыты - нам приходилось посещать каждую пару элементов один раз и каждый отдельный элемент 100 раз, потому что n ^ 2 + 100n = T (n ^ 2). Идея нотации O() состоит в том, что при достаточно большом n это не имеет значения, потому что n ^ 2 становится намного больше, чем 100n, что мы даже не замечаем воздействия 100n на время работы. Однако мы часто имеем дело с "достаточно малым" n, так что постоянные множители и т.д. Делают реальную существенную разницу.

Например, quicksort (средняя стоимость T (n.log n)) и heapsort (средняя стоимость T (n.log n)) - оба алгоритма сортировки с одинаковой средней стоимостью, но quicksort обычно намного быстрее, чем у heapsort. Это происходит потому, что heapsort делает несколько сравнений на элемент, чем quicksort.

Это не означает, что нотация O() бесполезна, просто неточна. Это довольно тупой инструмент, который можно использовать для небольших n.

(В качестве заключительной заметки к этому трактату помните, что нотация O() просто описывает рост любой функции - она ​​не обязательно должна быть временем, это может быть память, обмен сообщениями в распределенной системе или количество ЦП, требуемые для параллельного алгоритма.)

Ответ 8

Я пытаюсь объяснить, предоставив простые примеры кода на С#.

Для List<int> numbers = new List<int> {1,2,3,4,5,6,7,12,543,7};

O (1) выглядит как

return numbers.First();

O (n) выглядит как

int result = 0;
foreach (int num in numbers)
{
    result += num;
}
return result;

O (n log (n)) выглядит как

int result = 0;
foreach (int num in numbers)
{
    int index = numbers.length - 1;
    while (index > 1)
    {
        // yeah, stupid, but couldn't come up with something more useful :-(
        result += numbers[index];
        index /= 2;
    }
}
return result;

O (n ^ 2) выглядит как

int result = 0;
foreach (int outerNum in numbers)
{
    foreach (int innerNum in numbers)
    {
        result += outerNum * innerNum;
    }
}
return result;

O (n!) выглядит, например, усталым, чтобы придумать что-нибудь простое.
Но надеюсь, вы получите общее мнение?

Ответ 9

То, как я описываю его для моих нетехнических друзей, выглядит следующим образом:

Рассмотрим многозначное дополнение. Хорошее старомодное, карандаш-бумажное дополнение. Вид, который вы узнали, когда вам было 7-8 лет. Учитывая два трехзначных числа, вы можете узнать, что они добавляют довольно легко.

Если бы я дал вам два 100-значных числа и спросил вас, что они добавляют, выяснение этого было бы довольно простым, даже если бы вам пришлось использовать карандаш и бумагу. Яркий ребенок мог сделать такое дополнение всего за несколько минут. Для этого потребуется всего около 100 операций.

Теперь рассмотрим многозначное умножение. Вероятно, вы узнали об этом около 8 или 9 лет. Вы (надеюсь) сделали много повторяющихся упражнений, чтобы изучить механики за ним.

Теперь, представьте, я дал вам те же самые два 100-значных числа и сказал вам размножать их вместе. Это было бы намного сложнейшей задачей , что потребует от вас часов - и вы вряд ли обойдетесь без ошибок. Причиной этого является то, что (эта версия) умножения является O (n ^ 2); каждая цифра в нижнем номере должна быть умножена на каждую цифру в верхнем номере, оставив в общей сложности около n ^ 2 операций. В случае 100-значных чисел это 10000 умножений.

Ответ 10

Нет, алгоритм O (n) не означает, что он будет выполнять операцию над каждым элементом. Запись Big-O дает вам возможность говорить о "скорости" вашего алгоритма, независимо от вашей реальной машины.

O (n) означает, что время, затрачиваемое вашим алгоритмом, растет линейно по мере увеличения вашего ввода. O (n ^ 2) означает, что время, в течение которого ваш алгоритм растет, становится квадратом вашего ввода. И так далее.

Ответ 11

Как я думаю об этом, у вас есть задача очистить проблему, вызванную каким-то злым злодеем V, который выбирает N, и вы должны оценить, сколько еще времени потребуется, чтобы закончить вашу проблему, когда он увеличивается Н.

O (1) → увеличение N действительно не имеет никакого значения вообще

O (log (N)) → каждый раз, когда V удваивает N, вам нужно потратить дополнительное время T для выполнения задачи. V снова удваивает N, и вы тратите ту же сумму.

O (N) → каждый раз, когда V удваивает N, вы тратите в два раза больше времени.

O (N ^ 2) → каждый раз, когда V удваивает N, вы тратите 4 раза больше времени. (это несправедливо!!!)

O (N log (N)) → каждый раз, когда V удваивает N, вы тратите вдвое больше времени и немного больше.

Это границы алгоритма; компьютерные ученые хотят описать, сколько времени потребуется для больших значений N. (что становится важным, когда вы факторизуете числа, которые используются в криптографии - если компьютеры ускоряются в 10 раз, сколько еще бит вы должны использовать, чтобы убедиться, что все равно им понадобится 100 лет, чтобы прервать шифрование, а не только 1 год?)

Некоторые из границ могут иметь странные выражения, если это имеет значение для вовлеченных людей. Я видел такие вещи, как O (N log (N) log (log (N))) где-то в Knuth Art of Computer Programming для некоторых алгоритмов. (не помню, какой из них был на моей голове)

Ответ 12

Одна вещь, которая по какой-то причине не была затронута:

Когда вы видите алгоритмы с такими вещами, как O (2 ^ n) или O (n ^ 3) или другие неприятные значения, это часто означает, что вам придется принять несовершенный ответ на вашу проблему, чтобы получить приемлемую производительность.

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

Рассмотрим шахматы: я не знаю точно, какое правильное решение считается, но это, вероятно, что-то вроде O (n ^ 50) или даже хуже. Теоретически невозможно, чтобы любой компьютер действительно вычислял правильный ответ - даже если вы используете каждую частицу во Вселенной в качестве вычислительного элемента, выполняющего операцию в минимально возможное время для жизни Вселенной, у вас все еще осталось много нулей, (Может ли квантовый компьютер решить это - другое дело.)

Ответ 13

  • И кто-то должен курить трещину, чтобы написать O (x!)?

Нет, просто используйте Prolog. Если вы пишете алгоритм сортировки в Prolog, просто описав, что каждый элемент должен быть больше предыдущего, и пусть откат делает сортировку для вас, это будет O (x!). Также известен как "сортировка перестановок".

Ответ 14

"Интуиция" позади Big-O

Представьте себе "соревнование" между двумя функциями над x, поскольку x приближается к бесконечности: f (x) и g (x).

Теперь, если из какой-либо точки (некоторые x) одна функция всегда имеет более высокое значение, то другая, затем позвольте этой функции "быстрее", чем другая.

Итак, например, если для каждого x > 100 вы увидите, что f (x) > g (x), то f (x) "быстрее", чем g (x).

В этом случае мы бы сказали g (x) = O (f (x)). f (x) представляет своего рода "ограничение скорости" для g (x), так как в конце концов он передает его и оставляет его навсегда.

Это не совсем точное определение примечание Big-O, в котором также указано, что f (x) должно быть больше, чем C * g (x) для некоторой константы C (что является еще одним способом сказать, что вы не можете помочь g (x) выиграть соревнование, умножив его на постоянный коэффициент - f (x) всегда победит в конце), Формальное определение также использует абсолютные значения. Но я надеюсь, что мне удалось сделать его интуитивным.

Ответ 15

Мне нравится don neufeld, но я думаю, что могу добавить что-то о O (n log n).

Алгоритм, который использует стратегию простого деления и покорения, вероятно, будет O (log n). Самый простой пример - найти что-то в отсортированном списке. Вы не начинаете с самого начала и не просматриваете его. Вы идете в середину, вы решаете, следует ли вам идти назад или вперед, прыгать на полпути к последнему месту, где вы смотрели, и повторять это, пока не найдете предмет, который вы ищете.

Если вы посмотрите на алгоритмы quicksort или mergesort, вы увидите, что они оба используют подход для разделения списка, который нужно сортировать пополам, сортируя каждую половину (используя тот же алгоритм, рекурсивно), а затем рекомбинируя две половины, Такой тип рекурсивной стратегии разделения и завоевания будет O (n log n).

Если вы подумаете об этом внимательно, вы увидите, что quicksort выполняет алгоритм разбиения O (n) на все n элементов, затем O (n) разбивает два раза на n/2 элемента, затем 4 раза на n/4 элемента и т.д., Пока вы не перейдете к n разделам на 1 элемент (который является вырожденным). Количество раз, когда вы делите n пополам, чтобы добраться до 1, составляет приблизительно log n, и каждый шаг - O (n), поэтому рекурсивное разделение и поколение - это O (n log n). Mergesort строит другой путь, начиная с n рекомбинаций 1 предмета и заканчивая 1 рекомбинацией n элементов, где рекомбинация двух отсортированных списков - O (n).

Что касается курящей трещины, чтобы написать алгоритм O (n!), то вы, если у вас нет выбора. Считается, что проблема коммивояжера, приведенная выше, является одной из таких проблем.

Ответ 16

Большинство книг Джона Бентли (например, "Программирование жемчуга" ) покрывают такие вещи по-настоящему прагматично. Этот разговор, данный им, включает в себя один такой анализ быстрой сортировки.

Несмотря на то, что Кнут не совсем увлекался вопросом, Кнут придумал интересную идею : преподавание нотации Big-O в классах исчисления в средней школе, хотя я считаю эту идею довольно эксцентричной.

Ответ 17

Подумайте об этом как об укладке блоков lego (n) по вертикали и перепрыгивании через них.

O (1) означает, что на каждом шаге вы ничего не делаете. Высота остается прежней.

O (n) означает на каждом шаге вы складываете c-блоки, где c1 является константой.

O (n ^ 2) означает, что на каждом шаге вы складываете c2 x n блоков, где c2 - константа, а n - количество блоков сложения.

O (nlogn) означает, что на каждом шаге вы складываете блоки c3 x n x log n, где c3 является константой, а n - количеством блоков сложения.

Ответ 18

Чтобы понять O (n log n), помните, что log n означает log-base-2 из n. Затем посмотрите на каждую часть:

O (n) больше или меньше, когда вы работаете с каждым элементом в наборе.

O (log n) - это когда количество операций совпадает с показателем, на который вы поднимаете 2, чтобы получить количество элементов. Например, двоичный поиск должен сокращать набор в полтора раза n раз.

O (n log n) - это комбинация - вы делаете что-то в строках двоичного поиска для каждого элемента в наборе. Эффективные сортировки часто работают, делая один цикл для каждого элемента, и в каждом цикле выполняется хороший поиск, чтобы найти нужное место для размещения рассматриваемого предмета или группы. Следовательно, n * log n.

Ответ 19

Просто, чтобы ответить на пару комментариев по моему выше сообщению:

Доменик. Я нахожусь на этом сайте, и мне все равно. Не ради педантизма, а потому, что мы - как программисты - обычно заботимся о точности. Использование неправильной нотации O() в стиле, который некоторые из них сделали здесь, делает его бессмысленным; мы можем также сказать, что что-то принимает n ^ 2 единицы времени как O (n ^ 2) в соответствии с используемыми здесь соглашениями. Использование O() ничего не добавляет. Это не просто небольшое расхождение между общим использованием и математической точностью, о котором я говорю, это разница между тем, что она имеет смысл, а это не так.

Я знаю много, много отличных программистов, которые точно используют эти термины. Говоря "о, мы программисты, поэтому нам все равно" дешёвит все предприятие.

onebyone - Ну, на самом деле, хотя я не понимаю. Это не O (1) для сколь угодно большого n, что является определением O(). Это просто показывает, что O() имеет ограниченную применимость для ограниченного n, где мы скорее будем говорить о количестве принятых шагов, а не о границе этого числа.

Ответ 20

Скажите, что ваш восьмилетний журнал (n) означает количество раз, когда вам нужно нарезать длину n log в два, чтобы получить размер n = 1: p

O (n log n) обычно сортируется O (n ^ 2) обычно сравнивает все пары элементов

Ответ 21

Предположим, у вас был компьютер, который мог бы решить проблему определенного размера. Теперь представьте, что мы можем удвоить производительность несколько раз. Насколько мы можем решить проблему с каждым удвоением?

Если мы сможем решить проблему двойного размера, то O (n).

Если у нас есть некоторый множитель, который не один, это какая-то полиномиальная сложность. Например, если каждое удвоение позволяет увеличить размер проблемы примерно на 40%, то O (n ^ 2) и около 30% будет O (n ^ 3).

Если мы просто добавим размер проблемы, это будет экспоненциальным или худшим. Например, если каждое удвоение означает, что мы можем решить задачу 1 больше, это O (2 ^ n). (Вот почему грубый ввод ключа шифрования становится практически невозможным с помощью клавиш с разумным размером: для 128-битного ключа требуется примерно в 16 раз больше, чем 64-разрядная.)

Ответ 22

Помните басню черепахи и зайца (черепаха и кролика)?

В конечном итоге побеждает черепаха, но в краткосрочной перспективе побеждает зайц.

Это похоже на O (logN) (черепаха) против O (N) (заяц).

Если два метода отличаются своим большим-O, тогда существует уровень N, в котором один из них победит, но big-O ничего не говорит о том, насколько велика N.

Ответ 23

Чтобы оставаться искренним к заданному вопросу, я бы ответил на вопрос так, как я ответил бы на 8-летнего ребенка.

Предположим, что продавец мороженого готовит несколько мороженого (например, N) разных форм, упорядоченных по порядку. Вы хотите съесть мороженое, лежащее посередине.

Случай 1: - Вы можете есть мороженое, только если вы съели все мороженое меньше, чем это Вам придется съесть половину всех приготовленных мороженого (вход). Ответ напрямую зависит от размера ввода Решение будет иметь порядок o (N)

Случай 2: - Вы можете прямо съесть мороженое посередине

Решение будет O (1)

Случай 3: вы можете есть мороженое, только если вы съели все мороженое меньше, чем оно, и каждый раз, когда вы едите мороженое, вы позволяете другому ребенку (новому ребенку каждый раз) есть все его мороженое Общее время будет N + N + N....... (N/2) раз Решением будет O (N2)

Ответ 24

log (n) означает логарифмический рост. Примером могут быть алгоритмы разделения и покорения. Если у вас 1000 отсортированных чисел в массиве (например, 3, 10, 34, 244, 1203...) и вы хотите найти номер в списке (найдите его позицию), вы можете начать с проверки значения номер в индексе 500. Если он ниже, чем вы ищете, перейдите к 750. Если он выше, чем вы ищете, перейдите к 250. Затем вы повторяете процесс, пока не найдете свое значение (и ключ). Каждый раз, когда мы прыгаем на половину пространства поиска, мы можем отбросить тестирование многих других значений, так как мы знаем, что число 3004 не может быть выше 5000 (помните, что это отсортированный список).

n log (n), то означает n * log (n).

Ответ 25

Я попытаюсь на самом деле написать объяснение для настоящего восьмилетнего мальчика, помимо технических терминов и математических понятий.

Как то, что делает операция O(n^2)?

Если вы находитесь на вечеринке, и есть n люди в партии, включая вас. Сколько рукопожатий он принимает, чтобы каждый схватил всех остальных, учитывая, что люди, вероятно, забудут, кого они схватили в какой-то момент.

Примечание: это приближается к симплексу, дающему n(n-1), который достаточно близко к n^2.

И что это значит, если операция O(n log(n))?

Ваша любимая команда выиграла, они стоят в очереди, и в команде есть n игроков. Сколько hanshakes вам понадобится для рукопожатия каждого игрока, учитывая, что вы будете каждый раз несколько раз, сколько раз, сколько цифр в количестве игроков n.

Примечание: это даст n * log n to the base 10.

И кто-то должен курить трещины, чтобы написать O(x!)?

Вы богатый ребенок, и в вашем гардеробе много салфеток, есть ящики x для каждого типа одежды, ящики рядом друг с другом, у первого ящика есть 1 предмет, у каждого ящика столько же как в выдвижном ящике слева и еще один, так что у вас есть что-то вроде 1 hat, 2 парики, брюки (x-1), а затем x рубашки. Теперь, сколько способов вы можете одеться, используя один элемент из каждого ящика.

Примечание. В этом примере показано, сколько листов в дереве решений, где number of children = depth, которое выполняется через 1 * 2 * 3 * .. * x