Что O (log n) означает точно? - PullRequest
1915 голосов
/ 21 февраля 2010

В настоящее время я узнаю о времени работы Big O Notation и времени амортизации. Я понимаю понятие O (n) линейного времени, означающее, что размер входных данных пропорционально влияет на рост алгоритма ... и то же самое относится, например, к квадратичному времени O (n 2 ) и т. д. Даже алгоритмы, такие как генераторы перестановок, увеличивают в O (n!) раз на факториалы.

Например, следующая функция: O (n) , поскольку алгоритм растет пропорционально его вводу n :

f(int n) {
  int i;
  for (i = 0; i < n; ++i)
    printf("%d", i);
}

Аналогично, если бы был вложенный цикл, время было бы равно O (n 2 ).

Но что именно это O (log n) ? Например, что значит сказать, что высота полного двоичного дерева равна O (log n) ?

Я знаю (возможно, не очень подробно), что такое логарифм, в том смысле, что: log 10 100 = 2, но я не могу понять, как определить функцию с логарифмическим временем.

Ответы [ 32 ]

2453 голосов
/ 21 февраля 2010

Я не могу понять, как идентифицировать функцию по времени журнала.

Наиболее распространенными атрибутами логарифмической функции времени выполнения являются:

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

или

  • элементы, над которыми выполняется действие, это цифры n

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

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

<Ч />

Мы можем расширить пример телефонной книги, чтобы сравнить другие виды операций и их время выполнения. Предположим, что в нашей телефонной книге есть предприятий («Желтые страницы») с уникальными именами и людей («Белые страницы»), которые могут не иметь уникальных имен. Номер телефона назначается максимум одному человеку или бизнесу. Мы также предполагаем, что для перехода на определенную страницу требуется постоянное время.

Вот время выполнения некоторых операций, которые мы могли бы выполнять с телефонной книгой, от лучшего к худшему:

  • O (1) (в лучшем случае): Если на странице указано название компании и название компании, найдите номер телефона.

  • O (1) (средний регистр): С учетом страницы, на которой указано имя человека и его имя, найдите номер телефона.

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

  • O (n): Найти всех людей, номера телефонов которых содержат цифру «5».

  • O (n): По номеру телефона найдите человека или компанию с этим номером.

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

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

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

  • O (n 2 ): В офисе произошла ошибка, и каждая запись в каждой из телефонных книг имеет дополнительный "0" в конце телефонный номер. Возьмите немного белого и уберите каждый ноль.

  • O (n & middot; n!): Мы готовы загрузить телефонные книги в док-станцию. К сожалению, робот, который должен был загружать книги, стал бесполезным: он кладет книги на грузовик в случайном порядке! Хуже того, он загружает все книги в грузовик, а затем проверяет, находятся ли они в правильном порядке, а если нет, то выгружает их и начинает все сначала. (Это страшная бого рода .)

  • O (n n ): Вы исправляете робота так, чтобы он правильно загружал вещи. На следующий день один из ваших коллег подшучивает над вами и подключает робот-погрузчик к автоматизированным системам печати. Каждый раз, когда робот отправляется на загрузку оригинальной книги, заводской принтер дублирует все телефонные книги! К счастью, системы обнаружения ошибок робота настолько сложны, что робот не пытается печатать еще больше копий, когда для загрузки обнаруживает дубликат книги, но ему все равно приходится загружать все напечатанные оригиналы и дубликаты книг.

Для более математического объяснения вы можете проверить, как сложность времени достигает log n здесь. https://hackernoon.com/what-does-the-time-complexity-o-log-n-actually-mean-45f94bb5bfbf

545 голосов
/ 22 февраля 2010

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

Что значит сказать, что высота полного двоичного дерева равна O (log n)?

На следующем рисунке изображено двоичное дерево. Обратите внимание, что каждый уровень содержит удвоенное количество узлов по сравнению с уровнем выше (следовательно, binary ):

Binary tree

Двоичный поиск - пример со сложностью O(log n). Предположим, что узлы на нижнем уровне дерева на рисунке 1 представляют элементы в некоторой отсортированной коллекции. Бинарный поиск - это алгоритм «разделяй и властвуй», и на рисунке показано, как нам потребуется (не более) 4 сравнений, чтобы найти запись, которую мы ищем в этом наборе данных из 16 элементов.

Предположим, что вместо этого у нас был набор данных с 32 элементами. Продолжите рисунок выше, чтобы найти, что теперь нам нужно 5 сравнений, чтобы найти то, что мы ищем, поскольку дерево умножилось только на один уровень глубже, когда мы умножили объем данных. В результате сложность алгоритма можно описать как логарифмический порядок.

Печать log(n) на простом листе бумаги приведет к графику, на котором рост кривой замедляется с увеличением n:

O(log n)

520 голосов
/ 21 февраля 2010

O(log N) означает, что время линейно возрастает, а n экспоненциально. Таким образом, если для вычисления 10 элементов требуется 1 секунды, для вычисления 100 элементов потребуется 2 секунд, для вычисления 1000 элементов 3 секунд и т. Д.

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

223 голосов
/ 26 октября 2012

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

Двоичное дерево - это случай, когда проблема размера n делится на подзадачу размера n / 2, пока мы не достигнем проблемы размера 1:

height of a binary tree

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

Распространенным алгоритмом с O (log n) сложностью по времени является бинарный поиск, рекурсивное отношение которого равно T (n / 2) + O (1), т. Е. На каждом последующем уровне дерева вы делите задачу на половину и делаете постоянное количество дополнительная работа.

152 голосов
/ 27 апреля 2016

Обзор

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

Во-первых, вам нужно иметь общее представление о логарифме, которое вы можете получить из https://en.wikipedia.org/wiki/Logarithm. Естествознание используют e и натуральный лог. Ученики-инженеры будут использовать log_10 (база 10 журналов), а ученые-программисты будут часто использовать log_2 (база 2 журналов), поскольку компьютеры работают на двоичном языке. Иногда вы можете увидеть сокращения натурального лога как ln(), инженеры обычно оставляют _10 выключенным и просто используют log(), а log_2 сокращается как lg(). Все типы логарифмов растут одинаковым образом, поэтому они разделяют одну и ту же категорию log(n).

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

Вы можете думать о O (1), O (n), O (logn) и т. Д. Как о классах или категориях роста. Некоторые категории займут больше времени, чем другие. Эти категории помогают нам упорядочить производительность алгоритма. Некоторые растут быстрее по мере роста n. Следующая таблица демонстрирует рост численно. В таблице ниже представьте log (n) как потолок log_2.

enter image description here

Примеры простых кодов различных больших категорий O:

O (1) - Примеры с постоянным временем:

  • Алгоритм 1:

Алгоритм 1 печатает привет один раз, и он не зависит от n, поэтому он всегда будет работать в постоянное время, поэтому он равен O(1).

print "hello";
  • Алгоритм 2:

Алгоритм 2 печатает привет 3 раза, однако он не зависит от размера ввода. Даже если n растет, этот алгоритм всегда будет печатать привет 3 раза. Это, как говорится 3, является константой, так что этот алгоритм также O(1).

print "hello";
print "hello";
print "hello";

O (log (n)) - Логарифмические примеры:

  • Алгоритм 3 - действует как "log_2"

Алгоритм 3 демонстрирует алгоритм, который работает в log_2 (n). Обратите внимание, что операция post цикла for умножает текущее значение i на 2, поэтому i изменяется от 1 до 2, от 4 до 8, от 16 до 32 ...

.
for(int i = 1; i <= n; i = i * 2)
  print "hello";
  • Алгоритм 4 - действует как "log_3"

Алгоритм 4 демонстрирует log_3. Обратите внимание: i изменяется от 1 до 3 до 9 до 27 ...

for(int i = 1; i <= n; i = i * 3)
  print "hello";
  • Алгоритм 5 - действует как "log_1.02"

Алгоритм 5 важен, поскольку он помогает показать, что, пока число больше 1 и результат многократно умножается на самого себя, вы смотрите на логарифмический алгоритм.

for(double i = 1; i < n; i = i * 1.02)
  print "hello";

O (n) - Примеры линейного времени:

  • Алгоритм 6

Этот простой алгоритм выводит привет n раз.

for(int i = 0; i < n; i++)
  print "hello";
  • Алгоритм 7

Этот алгоритм показывает вариант, в котором он напечатает привет n / 2 раза. n / 2 = 1/2 * n. Мы игнорируем константу 1/2 и видим, что этот алгоритм равен O (n).

for(int i = 0; i < n; i = i + 2)
  print "hello";

O (n * log (n)) - nlog (n) Примеры:

  • Алгоритм 8

Думайте об этом как о комбинации O(log(n)) и O(n). Вложенность циклов for помогает нам получить O(n*log(n))

for(int i = 0; i < n; i++)
  for(int j = 1; j < n; j = j * 2)
    print "hello";
  • Алгоритм 9

Алгоритм 9 аналогичен алгоритму 8, но каждый из циклов допускает изменения, которые все равно приводят к конечному результату O(n*log(n))

for(int i = 0; i < n; i = i + 2)
  for(int j = 1; j < n; j = j * 3)
    print "hello";

O (n ^ 2) - n в квадрате Примеры:

  • Алгоритм 10

O(n^2) легко получается путем вложения стандартного для циклов.

for(int i = 0; i < n; i++)
  for(int j = 0; j < n; j++)
    print "hello";
  • Алгоритм 11

Как и алгоритм 10, но с некоторыми вариациями.

for(int i = 0; i < n; i++)
  for(int j = 0; j < n; j = j + 2)
    print "hello";

O (n ^ 3) - n в кубах Примеры:

  • Алгоритм 12

Это похоже на алгоритм 10, но с 3 циклами вместо 2.

for(int i = 0; i < n; i++)
  for(int j = 0; j < n; j++)
    for(int k = 0; k < n; k++)
      print "hello";
  • Алгоритм 13

Как и алгоритм 12, но с некоторыми вариациями, которые все еще дают O(n^3).

for(int i = 0; i < n; i++)
  for(int j = 0; j < n + 5; j = j + 2)
    for(int k = 0; k < n; k = k + 3)
      print "hello";

Резюме

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

126 голосов
/ 21 февраля 2010

Если у вас была функция, которая принимает:

1 millisecond to complete if you have 2 elements.
2 milliseconds to complete if you have 4 elements.
3 milliseconds to complete if you have 8 elements.
4 milliseconds to complete if you have 16 elements.
...
n milliseconds to complete if you have 2**n elements.

Тогда это займет лог 2 (n) времени.Обозначение Big O , в широком смысле, означает, что связь должна быть истинной только для больших n, и что постоянные факторы и меньшие члены можно игнорировать.

91 голосов
/ 21 февраля 2010

Логарифмическое время работы (O(log n)) по существу означает, что время работы растет пропорционально логарифму размера ввода - например, если 10 элементов занимают самое большее некоторое время x, и 100 предметов занимают самое большее, скажем, 2x, а 10000 предметов занимают самое большее 4x, тогда это выглядит как O(log n) сложность времени.

73 голосов
/ 08 октября 2016

Логарифм

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

Представьте, что у нас есть веревка, и мы привязали ее к лошади. Если веревка напрямую привязана к лошади, то сила, которую лошадь должна будет оторвать (скажем, от человека), равна непосредственно 1.

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

Теперь, если веревка зациклена один раз, лошади нужно будет тянуть в 10 раз сильнее. Если человек решает сделать лошадь действительно трудной, он может снова обмотать веревку вокруг шеста, увеличивая ее прочность еще в 10 раз. Третий цикл снова увеличит силу еще в 10 раз.

enter image description here

Мы видим, что для каждого цикла значение увеличивается на 10. Число ходов, необходимое для получения любого числа, называется логарифмом числа, т.е. нам нужно 3 сообщения, чтобы умножить вашу силу в 1000 раз, 6 сообщений, чтобы умножить ваша сила на 1000000.

3 - логарифм 1000, а 6 - логарифм 1 000 000 (основание 10).

Так что же на самом деле означает O (log n)?

В нашем примере выше, наш «темп роста» равен O (log n) . На каждую дополнительную петлю сила, которую может выдержать наша веревка, в 10 раз больше:

Turns | Max Force
  0   |   1
  1   |   10
  2   |   100
  3   |   1000
  4   |   10000
  n   |   10^n

Теперь в приведенном выше примере использовалась база 10, но, к счастью, база журнала незначительна, когда мы говорим о больших обозначениях.

Теперь давайте представим, что вы пытаетесь угадать число от 1 до 100.

Your Friend: Guess my number between 1-100! 
Your Guess: 50
Your Friend: Lower!
Your Guess: 25
Your Friend: Lower!
Your Guess: 13
Your Friend: Higher!
Your Guess: 19
Your Friend: Higher!
Your Friend: 22
Your Guess: Lower!
Your Guess: 20
Your Friend: Higher!
Your Guess: 21
Your Friend: YOU GOT IT!  

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

Guesses | Items
  1     |   2
  2     |   4
  3     |   8
  4     |   16
  5     |   32
  6     |   64
  7     |   128
  10    |   1024

Используя график, мы можем видеть, что если мы используем бинарный поиск, чтобы угадать число от 1 до 100, нам потребуется максимум 7 попыток. Если бы у нас было 128 чисел, мы могли бы также угадать число в 7 попытках, но 129 чисел потребует у нас максимум 8 попыток (в отношении логарифмов здесь нам понадобится 7 догадок для диапазона значений 128, 10 угадывает для диапазона значений 1024. 7 - логарифм 128, 10 - логарифм 1024 (основание 2)).

Обратите внимание, что я набросился "максимум". Большие обозначения всегда относятся к худшему случаю. Если вам повезет, вы можете угадать число за одну попытку, поэтому лучший вариант - O (1), но это уже другая история.

Мы видим, что при каждом предположении наш набор данных сокращается. Хорошее эмпирическое правило, чтобы определить, имеет ли алгоритм логарифмическое время, чтобы увидеть, уменьшается ли набор данных на определенный порядок после каждой итерации

А как насчет O (n log n)?

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

Вы можете легко определить, является ли алгоритмическое время n log n. Ищите внешний цикл, который перебирает список (O (n)). Затем посмотрите, есть ли внутренний цикл. Если внутренний цикл равен вырезать / уменьшить набор данных на каждой итерации, то этот цикл равен (O (log n), и поэтому общий алгоритм равен = O (n log n) .

Отказ от ответственности: Пример веревочного логарифма был взят из превосходной книги Математика «Восхищение» У. Сойера .

56 голосов
/ 21 февраля 2010

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

Если операция выполняет работу с постоянным временем для каждой цифры или бита на входе, вся операция займет время, пропорциональное количеству цифр или бит на входе, а не величине входа; таким образом, O (log N), а не O (N).

Если операция принимает серию решений с постоянным временем, каждое из которых вдвое (уменьшает в 3, 4, 5 раз) размер входного сигнала, который будет рассматриваться, для всего процесса потребуется время, пропорциональное логарифмической базе 2. (основание 3, основание 4, основание 5 ...) размера N ввода, а не O (N).

и т. Д.

51 голосов
/ 22 февраля 2010

Лучший способ, которым мне всегда приходилось мысленно визуализировать алгоритм, работающий в O (log n), заключается в следующем:

Если вы увеличите размер задачи на мультипликативную величину (то есть умножите ее размер на 10), работа будет увеличена только на аддитивную величину.

Применяя это к вашему бинарному дереву, вы получите хорошее приложение: если вы удвоите количество узлов в бинарном дереве, высота увеличится только на 1 (аддитивная сумма). Если вы удвоите его снова, оно все равно увеличится только на 1. (Очевидно, я предполагаю, что оно остается сбалансированным и тому подобное). Таким образом, вместо того, чтобы удвоить свою работу при увеличении размера задачи, вы выполняете лишь немного больше работы. Вот почему алгоритмы O (log n) великолепны.

...