O que significa O (log n) exatamente?


2139

Estou aprendendo sobre os tempos de execução da Big O Notation e os tempos amortizados. Eu entendo a noção de tempo linear O (n) , o que significa que o tamanho da entrada afeta o crescimento do algoritmo proporcionalmente ... e o mesmo vale para, por exemplo, tempo quadrático O (n 2 ) etc .. mesmo algoritmos , como geradores de permutação, com O (n!) vezes, que crescem por fatoriais.

Por exemplo, a seguinte função é O (n) porque o algoritmo cresce proporcionalmente à sua entrada n :

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

Da mesma forma, se houvesse um loop aninhado, o tempo seria O (n 2 ).

Mas o que exatamente é O (log n) ? Por exemplo, o que significa dizer que a altura de uma árvore binária completa é O (log n) ?

Eu sei (talvez não em detalhes) o que é Logaritmo, no sentido de que: log 10 100 = 2, mas não consigo entender como identificar uma função com um tempo logarítmico.


60
Uma árvore binária de 1 nó possui altura log2 (1) +1 = 1, uma árvore de 2 nós possui altura log2 (2) +1 = 2, uma árvore de 4 nós possui altura log2 (4) +1 = 3 e em breve. Uma árvore de nó n possui altura log2 (n) +1, portanto, adicionar nós à árvore faz com que sua altura média cresça logaritmicamente.
precisa

36
Uma coisa que vejo na maioria das respostas é que elas descrevem essencialmente "O (alguma coisa)" significa que o tempo de execução do algoritmo aumenta proporcionalmente a "alguma coisa". Dado que você solicitou "significado exato" de "O (log n)", isso não é verdade. Essa é a descrição intuitiva da notação Big-Theta, não Big-O. O (log n) intuitivamente significa que o tempo de execução cresce no máximo proporcional ao "log n": stackoverflow.com/questions/471199/…
Mehrdad Afshari

31
Lembro-me sempre dividir para conquistar como o exemplo de O (N log N)
RichardOD

14
É importante perceber que sua base de log 2 (não a base 10). Isso ocorre porque a cada etapa de um algoritmo, você remove metade das opções restantes. Na ciência da computação, quase sempre lidamos com a base de log 2 porque podemos ignorar constantes. No entanto, existem algumas excepções (ou seja Quad Árvore tempos de execução são base de log 4)
Ethan

13
@ Ethan: Não importa em que base você está, uma vez que a conversão de base é apenas uma multiplicação constante, a fórmula é log_b (x) = log_d (x) / log_d (b). Log_d (b) será apenas uma constante.
mindvirus

Respostas:


2711

Não consigo entender como identificar uma função com um tempo de log.

Os atributos mais comuns da função logarítmica em tempo de execução são os seguintes:

  • a escolha do próximo elemento no qual executar alguma ação é uma das várias possibilidades, e
  • somente um precisará ser escolhido.

ou

  • os elementos nos quais a ação é executada são dígitos de n

É por isso que, por exemplo, procurar pessoas em uma lista telefônica é O (log n). Você não precisa verificar todas as pessoas na lista telefônica para encontrar a certa; em vez disso, você pode simplesmente dividir e conquistar, baseando-se em onde o nome deles está em ordem alfabética, e em cada seção você só precisa explorar um subconjunto de cada seção antes de encontrar o número de telefone de alguém.

Obviamente, uma lista telefônica maior ainda levará mais tempo, mas não crescerá tão rapidamente quanto o aumento proporcional no tamanho adicional.


Podemos expandir o exemplo da lista telefônica para comparar outros tipos de operações e seu tempo de execução. Assumiremos que nossa agenda telefônica possui empresas (as "Páginas Amarelas") que têm nomes exclusivos e pessoas (as "Páginas Brancas") que podem não ter nomes exclusivos. Um número de telefone é atribuído a no máximo uma pessoa ou empresa. Também assumiremos que é necessário um tempo constante para acessar uma página específica.

Aqui estão os tempos de execução de algumas operações que podemos executar na lista telefônica, do mais rápido ao mais lento:

  • O (1) (na pior das hipóteses): dada a página em que o nome de uma empresa está e o nome da empresa, encontre o número de telefone.

  • O (1) (no caso médio): dada a página em que o nome de uma pessoa está e seu nome, encontre o número de telefone.

  • O (log n): Dado o nome de uma pessoa, encontre o número de telefone escolhendo um ponto aleatório na metade da parte do livro que você ainda não pesquisou e depois verifique se o nome da pessoa está nesse ponto. Em seguida, repita o processo na metade da parte do livro onde está o nome da pessoa. (Esta é uma pesquisa binária pelo nome de uma pessoa.)

  • O (n): encontre todas as pessoas cujos números de telefone contenham o dígito "5".

  • O (n): dado um número de telefone, encontre a pessoa ou empresa com esse número.

  • O (n log n): Houve uma confusão no escritório da impressora e nossa lista telefônica teve todas as suas páginas inseridas em uma ordem aleatória. Corrija a ordem para que esteja correta, observando o primeiro nome em cada página e colocando-a no local apropriado em uma lista telefônica nova e vazia.

Para os exemplos abaixo, agora estamos no escritório da impressora. As listas telefônicas estão aguardando para serem enviadas por correio para cada residente ou empresa, e há um adesivo em cada lista telefônica identificando para onde deve ser enviado. Cada pessoa ou empresa recebe uma lista telefônica.

  • O (n log n): queremos personalizar a lista telefônica, para encontrar o nome de cada pessoa ou empresa em sua cópia designada, circular seu nome no livro e escrever uma breve nota de agradecimento por seu patrocínio. .

  • O (n 2 ): ocorreu um erro no escritório, e cada entrada em cada uma das listas telefônicas tem um "0" extra no final do número de telefone. Pegue um pouco de branco e remova cada zero.

  • O (n · n!): Estamos prontos para carregar as agendas telefônicas na doca de remessa. Infelizmente, o robô que deveria carregar os livros deu errado: está colocando os livros no caminhão em uma ordem aleatória! Pior ainda, ele carrega todos os livros no caminhão, depois verifica se eles estão na ordem certa e, se não, os descarrega e começa de novo. (Essa é a temida classificação bogo .)

  • O (n n ): Você conserta o robô para que ele carregue as coisas corretamente. No dia seguinte, um de seus colegas de trabalho faz uma brincadeira com você e liga o robô da estação de carregamento aos sistemas de impressão automatizados. Sempre que o robô carrega um livro original, a impressora de fábrica executa uma duplicação de todas as agendas telefônicas! Felizmente, os sistemas de detecção de erros do robô são sofisticados o suficiente para que o robô não tente imprimir ainda mais cópias ao encontrar um livro duplicado para carregamento, mas ainda precisa carregar todos os livros originais e duplicados que foram impressos.


81
@cletus: Coincidental, eu tenho medo. Eu escolhi porque as listas telefônicas têm um N grande, as pessoas entendem o que são e o que fazem, e porque é versátil como exemplo. Além disso, eu tenho que usar robôs na minha explicação! Uma vitória geral. (Além disso, parece que a sua resposta foi feito antes de eu era ainda um membro em StackOverflow para começar!)
John Feminella

12
"Ocorreu um erro no escritório, e cada entrada em cada uma das listas telefônicas tem um" 0 "extra no final do número de telefone. Pegue um pouco de branco e remova cada zero." <- não é a ordem N ao quadrado. N é definido como o tamanho da entrada. O tamanho da entrada é o número de números de telefone, que é o número de números por livro multiplicado pelo número de livros. Ainda é uma operação de tempo linear.
Billy ONeal

21
@ Billy: Neste exemplo, Né o número de pessoas em um único livro. Como todas as pessoas na lista telefônica também obtêm sua própria cópia do livro, existem N listas telefônicas idênticas , cada uma com Npessoas, o que é O (N ^ 2).
John Feminella

48
O (1) não é o melhor caso, e não o pior caso, pois é estranhamente destacado como?
Svip

54
Levei tempo O (long⅝n! N-55/2) para encontrar uma definição O (log n) que finalmente faz sentido. +1
iAteABug_And_iLiked_it

611

O(log N)basicamente significa que o tempo aumenta linearmente enquanto naumenta exponencialmente. Portanto, se levar alguns 1segundos para calcular 10elementos, levará 2segundos para calcular 100elementos, 3segundos para calcular 1000elementos e assim por diante.

É O(log n)quando dividimos e conquistamos tipos de algoritmos, por exemplo, pesquisa binária. Outro exemplo é a classificação rápida, onde cada vez que dividimos a matriz em duas partes e cada vez que leva O(N)tempo para encontrar um elemento dinâmico. Por isso N O(log N)


108
Três linhas de sabedoria que superam todas as outras respostas de redação ... :) Caso alguém esteja faltando, no contexto de programação, a base do log é 2 (não 10), então O (log n) é escalado como 1 segundo por 10 elementos, 2 seg para 20, 3 para 40 etc
NAWFAL

3
Concordado, conciso e claro, embora a questão final do OP fosse como identificar uma função logarítmica, não exatamente "o que é"?
Adam

4
Sim, a função logarítmica é inversa à função exponencial. ((log x) base a) é inverso de (a potência x). A análise qualitativa dessas funções com gráficos daria mais intuição.
overexchange

7
Isso levou cerca de três leituras para perceber que não estava errado. O tempo aumenta linearmente, enquanto o número de elementos é exponencial. Isso significa mais elementos durante menos tempo . Isso é mentalmente desgastante para aqueles que visualizam logcomo a curva de log familiar em um gráfico.
Qix - MONICA FOI ERRADA

1
Eu acho que essa é uma resposta muito boa, exceto na parte em que afirma que a pesquisa binária é um algoritmo de divisão e conquista. Não é.
code_dredd

579

Muitas boas respostas já foram postadas nesta pergunta, mas acredito que realmente estamos perdendo uma importante - a saber, a resposta ilustrada.

O que significa dizer que a altura de uma árvore binária completa é O (log n)?

O desenho a seguir mostra uma árvore binária. Observe como cada nível contém o dobro do número de nós em comparação com o nível acima (portanto, binário ):

Árvore binária

A pesquisa binária é um exemplo com complexidade O(log n). Digamos que os nós no nível inferior da árvore na figura 1 representem itens em alguma coleção classificada. A pesquisa binária é um algoritmo de dividir e conquistar, e o desenho mostra como precisaremos (no máximo) de 4 comparações para encontrar o registro que estamos procurando neste conjunto de dados de 16 itens.

Suponhamos que tivéssemos um conjunto de dados com 32 elementos. Continue o desenho acima e descubra que agora precisaremos de 5 comparações para encontrar o que estamos procurando, pois a árvore só aumentou um nível ainda mais quando multiplicamos a quantidade de dados. Como resultado, a complexidade do algoritmo pode ser descrita como uma ordem logarítmica.

A plotagem log(n)em um pedaço de papel comum resultará em um gráfico em que a elevação da curva desacelera à medida que naumenta:

O (log n)


60
"Observe como cada nível contém o número duplo de nós em comparação com o nível acima (portanto, binário)" Isso está incorreto. O que você está descrevendo é uma árvore binária equilibrada . Uma árvore binária significa apenas que cada nó tem no máximo dois filhos.
Oenotria

8
De fato, é uma árvore binária balanceada muito especial, chamada árvore binária completa. Eu editei a resposta, mas preciso de alguém para aprová-la.
user21820

5
Uma árvore binária completa não precisa ter o último nível para ser completamente preenchido. Eu diria que uma 'árvore binária completa' é mais apropriada.
Sr. AJ

Sua resposta tenta responder mais concretamente ao problema original do OP, por isso é melhor que a resposta atual aceita (IMO), mas ainda é muito incompleta: você apenas dá um meio exemplo e duas imagens ...
nbro

2
Essa árvore possui 31 itens, não 16. Por que é chamado de conjunto de dados de 16 itens? Cada nó em que representa um número, caso contrário, seria uma árvore binária ineficiente: P
Perry Monschau

245

A explicação abaixo está usando o caso de uma árvore binária totalmente equilibrada para ajudá-lo a entender como obtemos a complexidade do tempo logarítmico.

Árvore binária é um caso em que um problema de tamanho n é dividido em subproblema de tamanho n / 2 até chegarmos a um problema de tamanho 1:

altura de uma árvore binária

E é assim que você obtém O (log n), que é a quantidade de trabalho que precisa ser feito na árvore acima para chegar a uma solução.

Um algoritmo comum com complexidade de tempo O (log n) é a Pesquisa Binária, cuja relação recursiva é T (n / 2) + O (1), ou seja, em cada nível subseqüente da árvore, você divide o problema pela metade e realiza uma quantidade constante de trabalho adicional.


2
novato aqui. Então, você poderia dizer que a altura da árvore é a taxa de divisão por recursão para atingir o tamanho n = 1?
Cody

@ Cody, sim a maior parte da sua observação é precisa. Este exemplo ilustra / utiliza log_2. Sua observação gastaria além log_2e seria precisa para qualquer log_xlugar x > 1. Fazer divisão direta pode não levar a 1 exatamente, portanto, você pode dizer a divisão recursiva até que a Ceiling()divisão mais recente seja igual a 1 ou algo semelhante.
James Oravec 9/11

198

Visão geral

Outros deram bons exemplos de diagramas, como os diagramas de árvore. Não vi nenhum exemplo simples de código. Portanto, além da minha explicação, fornecerei alguns algoritmos com instruções de impressão simples para ilustrar a complexidade de diferentes categorias de algoritmos.

Primeiro, você deseja ter uma idéia geral do Logaritmo, que pode ser obtida em https://en.wikipedia.org/wiki/Logarithm . Uso das ciências naturais ee registro natural. Os discípulos da engenharia usarão o log_10 (base 10 do log) e os cientistas da computação usarão muito o log_2 (base 2 do log), uma vez que os computadores são binários. Às vezes, você verá as abreviações do log natural como ln(), os engenheiros normalmente deixam o _10 desativado e apenas usam log()e o log_2 é abreviado como lg(). Todos os tipos de logaritmos crescem de maneira semelhante, é por isso que compartilham a mesma categoria de log(n).

Quando você olha os exemplos de código abaixo, recomendo olhar O (1), O (n) e O (n ^ 2). Depois de ser bom com eles, olhe para os outros. Incluí exemplos limpos e variações para demonstrar como as mudanças sutis ainda podem resultar na mesma categorização.

Você pode pensar em O (1), O (n), O (logn) etc. como classes ou categorias de crescimento. Algumas categorias levarão mais tempo do que outras. Essas categorias nos ajudam a ordenar o desempenho do algoritmo. Alguns cresceram mais rápido à medida que a entrada n cresce. A tabela a seguir demonstra o referido crescimento numericamente. Na tabela abaixo, considere log (n) como o limite máximo de log_2.

insira a descrição da imagem aqui

Exemplos de código simples de várias grandes categorias O:

O (1) - Exemplos de tempo constante:

  • Algoritmo 1:

O algoritmo 1 imprime olá uma vez e não depende de n, portanto sempre será executado em tempo constante, como é O(1).

print "hello";
  • Algoritmo 2:

O algoritmo 2 imprime olá 3 vezes, no entanto, não depende do tamanho da entrada. Mesmo quando n cresce, esse algoritmo sempre imprime olá apenas 3 vezes. Dito isto 3, é uma constante, então esse algoritmo também é O(1).

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

O (log (n)) - Exemplos logarítmicos:

  • Algoritmo 3 - Funciona como "log_2"

O algoritmo 3 demonstra um algoritmo que é executado em log_2 (n). Observe que a pós-operação do loop for multiplica o valor atual de i por 2, ivaria de 1 a 2 a 4 a 8 a 16 a 32 ...

for(int i = 1; i <= n; i = i * 2)
  print "hello";
  • Algoritmo 4 - Funciona como "log_3"

O algoritmo 4 demonstra log_3. O aviso ivaria de 1 a 3 a 9 a 27 ...

for(int i = 1; i <= n; i = i * 3)
  print "hello";
  • Algoritmo 5 - Funciona como "log_1.02"

O algoritmo 5 é importante, pois ajuda a mostrar que, desde que o número seja maior que 1 e o resultado seja multiplicado repetidamente contra si mesmo, você está procurando um algoritmo logarítmico.

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

O (n) - Exemplos de tempo linear:

  • Algoritmo 6

Esse algoritmo é simples, que imprime olá n vezes.

for(int i = 0; i < n; i++)
  print "hello";
  • Algoritmo 7

Este algoritmo mostra uma variação, na qual imprimirá olá n / 2 vezes. n / 2 = 1/2 * n. Ignoramos a constante 1/2 e vemos que esse algoritmo é O (n).

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

O (n * log (n)) - nlog (n) Exemplos:

  • Algoritmo 8

Pense nisso como uma combinação de O(log(n))e O(n). O aninhamento dos loops for nos ajuda a obter oO(n*log(n))

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

O algoritmo 9 é como o algoritmo 8, mas cada um dos loops permitiu variações, o que ainda resulta no resultado final sendo 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 ao quadrado Exemplos:

  • Algoritmo 10

O(n^2) é obtido facilmente aninhando o padrão para loops.

for(int i = 0; i < n; i++)
  for(int j = 0; j < n; j++)
    print "hello";
  • Algoritmo 11

Como o algoritmo 10, mas com algumas variações.

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

O (n ^ 3) - n em cubo Exemplos:

  • Algoritmo 12

É como o algoritmo 10, mas com 3 loops em vez de 2.

for(int i = 0; i < n; i++)
  for(int j = 0; j < n; j++)
    for(int k = 0; k < n; k++)
      print "hello";
  • Algoritmo 13

Como o algoritmo 12, mas com algumas variações que ainda produzem 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";

Sumário

O exemplo acima fornece vários exemplos diretos e variações para ajudar a demonstrar que mudanças sutis podem ser introduzidas que realmente não alteram a análise. Espero que isso lhe dê informações suficientes.


17
Impressionante. A melhor explicação para mim que eu já vi. Seria melhor se isso O(n^2)for observado como uma combinação de O(n)e O(n), então O(n) * O(n) = O(n * n) = O(n^2). Parece um pouco de pulo sem essa equação. É uma repetição de explicações anteriores, mas acho que essa repetição pode fornecer mais confiança aos leitores para a compreensão.
Eonil

2
Esta é simplesmente a melhor explicação de sempre.
Edgar Kiljak

2
@IceTea, para fornecer informações / intuição para sua pergunta. Se você fizer um gráfico nversus versus, n/2verá que os dois fazem uma linha reta. Isso os coloca na mesma classe, pois têm taxas de crescimento semelhantes (pense nisso como a forma do gráfico). Da mesma forma, se você fizer um gráfico log_2versus, log_3verá que os dois assumem "formas semelhantes" ou "taxas de crescimento semelhantes".
James Oravec 9/11

1
@IceTea, a explicação dada por @Shai e @James é mais precisa, n/2 or 2n or n+2 or nterá linhas diferentes no gráfico, mas elas terão a mesma taxa de crescimento, o que significa que todos seguirão um crescimento linear.
Naresh Joshi

2
E o caso em que temos dois loops aninhados, mas o segundo iterador depende do primeiro, essa dependência afeta a complexidade do tempo?
Bionix1441

131

Se você tivesse uma função que aceita:

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.

Depois, leva 2 (n) tempo para o log . A notação Big O , em termos gerais, significa que o relacionamento só precisa ser verdadeiro para n grande e que fatores constantes e termos menores podem ser ignorados.


log2 (n) é o mesmo que o (log n)?
Sven van den Boogaart

Sim, veja o comentário de nawfal para obter outra resposta aqui: (copiar e colar) - no contexto de programação, a base do log é 2 (não 10), então O (log n) escala como 1 segundo para 10 elementos, 2 segundos para 20 , 3 para 40, etc #
Andrejs

@SvenvandenBoogaart, ilustra o exemplo desta solução log_2, que está na classe O(log(n)). Há muitos outros na mesma classe de O(log(n))ie log_xondex > 1
James Oravec

@Andrejs, seu comentário so O(log n) scales like 1 sec for 10 elements, 2 sec for 20, 3 for 40 etcé impreciso. Esse padrão / classe corresponderia / alinharia com O(n)não O(log(n)). Se alguém estava interessado em log_10seguida, um exemplo seria equivalente 1 s por 10 elementos, 2 segundos para 100, 3 por 1000, etc.
James Oravec

99

O tempo de execução logarítmico ( O(log n)) significa essencialmente que o tempo de execução aumenta proporcionalmente ao logaritmo do tamanho da entrada - por exemplo, se 10 itens levam no máximo algum tempo xe 100 itens levam no máximo, digamos 2x, e 10.000 itens leva no máximo 4x, então parece uma O(log n)complexidade de tempo.


1
+1, mas você realmente deve apontar que é log2, não log10.
Adriano Varoli Piazza 21/02

62
log2 ou log10 é irrelevante. Eles diferem apenas por um fator de escala, o que os torna da mesma ordem, ou seja, ainda crescem na mesma taxa.
Noldorin

17
O mais interessante dos logaritmos é que, ao comparar as alturas relativas, a base exata usada não importa. log 10,000 / log 100é 2, independentemente da base usada.
Anon.

12
Para ser nitpicky, O (lg n) significa que o tempo de execução é no máximo proporcional a lg n. O que você descreve é ​​Theta (lg n).

1
@rgrig: Isso é verdade. Eu editei alguns "at mosts" para indicar a natureza do limite superior do big-O.
Anon.

95

O logaritmo

Ok, vamos tentar entender completamente o que é realmente um logaritmo.

Imagine que temos uma corda e a amarramos a um cavalo. Se a corda estiver diretamente amarrada ao cavalo, a força que o cavalo precisaria puxar (digamos, de um homem) é diretamente 1.

Agora imagine que a corda está presa em volta de um poste. O cavalo para fugir agora terá que se esforçar muitas vezes. A quantidade de vezes dependerá da aspereza da corda e do tamanho do poste, mas vamos supor que ela multiplique a força de alguém por 10 (quando a corda fizer uma curva completa).

Agora, se a corda for presa uma vez, o cavalo precisará puxar 10 vezes mais. Se o humano decidir tornar isso realmente difícil para o cavalo, ele poderá prender a corda novamente em volta de um poste, aumentando sua força em mais 10 vezes. Um terceiro loop aumentará novamente a força em mais 10 vezes.

insira a descrição da imagem aqui

Podemos ver que, para cada loop, o valor aumenta em 10. O número de voltas necessárias para obter qualquer número é chamado logaritmo do número, ou seja, precisamos de 3 postagens para multiplicar sua força por 1000 vezes, 6 postagens para multiplicar sua força por 1.000.000.

3 é o logaritmo de 1.000 e 6 é o logaritmo de 1.000.000 (base 10).

Então, o que O (log n) realmente significa?

No exemplo acima, nossa 'taxa de crescimento' é O (log n) . Para cada loop adicional, a força que nossa corda pode suportar é 10 vezes mais:

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

Agora, o exemplo acima usou a base 10, mas, felizmente, a base do log é insignificante quando falamos de grande notação.

Agora vamos imaginar que você está tentando adivinhar um número entre 1 e 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!  

Agora você precisou de 7 palpites para acertar. Mas qual é o relacionamento aqui? Qual é a maior quantidade de itens que você pode adivinhar em cada palpite adicional?

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

Usando o gráfico, podemos ver que, se usarmos uma pesquisa binária para adivinhar um número entre 1 e 100, levaremos no máximo 7 tentativas. Se tivéssemos 128 números, também poderíamos adivinhar o número em 7 tentativas, mas 129 números nos levarão no máximo 8 tentativas (em relação aos logaritmos, aqui precisaríamos de 7 suposições para um intervalo de 128 valores, 10 suposições para um intervalo de valores de 1024 7 é o logaritmo de 128, 10 é o logaritmo de 1024 (base 2)).

Observe que eu tenho negrito 'no máximo'. A notação Big-O sempre se refere ao pior caso. Se você tiver sorte, poderá adivinhar o número em uma tentativa e, portanto, o melhor caso é O (1), mas isso é outra história.

Podemos ver que, para todos os palpites, nosso conjunto de dados está diminuindo. Uma boa regra para identificar se um algoritmo tem um tempo logaritmático é verificar se o conjunto de dados diminui em uma determinada ordem após cada iteração

E quanto a O (n log n)?

Você encontrará um algoritmo de tempo linearitômico O (n log (n)) . A regra geral acima se aplica novamente, mas desta vez a função logarítmica deve executar n vezes, por exemplo, reduzindo o tamanho de uma lista n vezes , o que ocorre em algoritmos como um mergesort.

Você pode identificar facilmente se o tempo algorítmico é n log n. Procure um loop externo que itere através de uma lista (O (n)). Então veja se há um loop interno. Se o loop interno estiver cortando / reduzindo o conjunto de dados em cada iteração, esse loop será (O (log n)) e, portanto, o algoritmo geral será = O (n log n) .

Isenção de responsabilidade: O exemplo do logaritmo de corda foi retirado do excelente livro Mathematician's Delight, de W.Sawyer .


Não. In our example above, our 'growth rate' is O(log n). For every additional loop, the force our rope can handle is 10 times more, Suportado por um gráfico que mostra n == número de loops e our 'growth rate'=> 10 ^ n, que NÃO é log n. O exemplo pode ser corrigido fazendo n=# horses, o que exige que os log n lo sejam restringidos. Exemplos pedagógicos ruins produzem alunos que apenas acreditam entender.
psimpson

56

Você pode pensar em O (log N) intuitivamente dizendo que o tempo é proporcional ao número de dígitos em N.

Se uma operação executar um trabalho de tempo constante em cada dígito ou bit de uma entrada, toda a operação levará um tempo proporcional ao número de dígitos ou bits na entrada, não à magnitude da entrada; assim, O (log N) em vez de O (N).

Se uma operação toma uma série de decisões de tempo constante, cada uma das quais reduz pela metade (reduz em um fator de 3, 4, 5 ..) o tamanho da entrada a ser considerada, o conjunto levará um tempo proporcional à base 2 do log (base 3 , base 4, base 5 ...) do tamanho N da entrada, em vez de ser O (N).

E assim por diante.


7
Preciso o suficiente e mais facilmente compreendido do que a maioria das explicações, eu acho.
T.

é uma explicação de log<sub>10</sub> N, é?
#LiuYan #

1
@LiuYan 研 研 eles não disseram em que base estava o número de dígitos. De qualquer forma, log₂ (n) = log₁₀ (n) / log₁₀ (2) e 1 / log₁₀ (2) é, portanto, um multiplicador constante, com o mesmo princípio aplicável a todas as outras bases. Isso mostra duas coisas. Em primeiro lugar, o princípio de moonshadow se aplica a qualquer base (embora quanto menor a base, menos "ressaltos" na estimativa) e também que O (log n) seja O (log n), independentemente da base de cálculo que o levou a essa conclusão. .
9119 Jon Hanna

"proporcional" ... "cada um dos quais reduz pela metade o tamanho da entrada" ??????
csguy

52

A melhor maneira que eu sempre tive para visualizar mentalmente um algoritmo executado em O (log n) é a seguinte:

Se você aumentar o tamanho do problema em uma quantidade multiplicativa (ou seja, multiplicar seu tamanho por 10), o trabalho será aumentado apenas em uma quantidade aditiva.

Aplicando isso à sua pergunta sobre árvore binária, para que você tenha uma boa aplicação: se você dobrar o número de nós em uma árvore binária, a altura aumentará apenas 1 (uma quantidade aditiva). Se você dobrar novamente, ele ainda aumentará apenas 1. (Obviamente, estou assumindo que ele permanece equilibrado e tal). Dessa forma, em vez de dobrar o seu trabalho quando o tamanho do problema é multiplicado, você está fazendo um pouco mais de trabalho. É por isso que os algoritmos O (log n) são impressionantes.


52

Primeiro eu recomendo que você leia o livro a seguir;

Algoritmos (4ª Edição)

Aqui estão algumas funções e suas complexidades esperadas. Os números estão indicando frequências de execução de instruções .

Aqui estão algumas funções e suas complexidades esperadas

Seguinte Big-O Complexity Chart também retirado de bigocheatsheet Big-O Gráfico de complexidade

Por fim, uma mostra muito simples mostra como é calculada;

Anatomia das frequências de execução de instruções de um programa.

Analisando o tempo de execução de um programa (exemplo).

Analisando o tempo de execução de um programa


5
Eu não colocaria O (n log n) na cesta ruim . Pertence à feira .
André Werlang

Ao visualizar o gráfico de complexidade O-grande (acima), lembre-se de que O (n) é um ponto linear real, não a fronteira rosa / laranja. @ André É por isso que O (n log n) está corretamente marcado no suporte de desempenho 'ruim', é um desempenho pior que o linear.
JavaBeast

@JavaBeast correto, enquanto o desempenho de O (n log n) é tecnicamente pior que O (n), consulte a tabela acima, que apresenta uma boa comparação deles (veja o crescimento dos dois). O gráfico, de uma fonte diferente, é contraditório porque coloca O (1) e O (log n) no mesmo bom / excelente. sua ordem relativa de crescimento é comparável a O (n) e O (n log n). tl; dr; O (n log n) não é excelente, mas está longe de ser ruim.
André Werlang

1
Esta resposta está errada! Supõe que N = N * N. De fato N = N! Seu exemplo é realmente N em cubos. Você faz o mesmo no seu gráfico. Seu O (n) deve realmente ser a divisão entre horrível e ruim. Prova matemática: você diz que o loop for é constante com O (1). Isso é o que o 1 realmente significa, não depende de N. Apenas significa não variável. Mas é variável, pois depende de N. Duas vezes N e metade do tempo. Portanto, é inválido. Se for desse livro, não compre! O gráfico do código que você mostrou não é real, é uma piada, veja "Impressionante", significa três pessoas fazendo sexo ao mesmo tempo! OMG
jgmjgm 8/01/19

1
O (n) não deveria estar na diagonal?
gyosifov 22/09/19

46

O que é log b (n)?

É o número de vezes que você pode cortar um log de comprimento n repetidamente em b partes iguais antes de atingir uma seção de tamanho 1.


Comentário excelente! É conciso e exatamente a resposta que estou procurando.
DennisL

18

Os algoritmos de divisão e conquista geralmente têm um logncomponente para o tempo de execução. Isso ocorre pela metade repetida da entrada.

No caso de pesquisa binária, toda iteração que você joga fora da metade da entrada. Note-se que na notação Big-O, o log é a base 2 do log.

Edit: Como observado, a base de log não importa, mas ao derivar o desempenho Big-O de um algoritmo, o fator de log virá da metade, portanto, por que eu penso nisso como base 2.


2
Por que é base de log 2? No quicksort aleatório, por exemplo, não acho que seja a base 2. Até onde eu sei, a base não importa, como a base de log a (n) = log2 (n) / log2 (a), portanto, todo logaritmo é diferente de outro por uma constante e as constantes são ignoradas na notação big-o. De fato, escrever a base de um log na notação big-o é um erro na minha opinião, pois você está escrevendo uma constante.
IVlad


É verdade que ele pode ser convertido em qualquer base e isso não importa, mas se você está tentando obter o desempenho do Big-O e vê a metade constante, ajuda a entender que você não verá a base de log 10 refletida no código.
David Kanarek

Um aspecto a parte: em coisas como árvores B, onde os nós têm um fan-out de mais de 2 (ou seja, "mais largo" que uma árvore binária), você ainda verá crescimento de O (logn), porque ainda é dividido - e -conquer, mas a base do log estará relacionada ao fan-out.
Roger Lipscombe

A digressão no log 2 foi bastante útil, na verdade.
Dan Rosenstark 5/02/19

15

Mas o que exatamente é O (log n)? Por exemplo, o que significa dizer que a altura de uma árvore binária completa é O (log n)?

Eu reformularia isso como 'a altura de uma árvore binária completa é log n'. Descobrir a altura de uma árvore binária completa seria O (log n), se você estivesse descendo passo a passo.

Não consigo entender como identificar uma função com um tempo logarítmico.

O logaritmo é essencialmente o inverso da exponenciação. Portanto, se cada 'etapa' da sua função estiver eliminando um fator de elementos do conjunto de itens original, esse é um algoritmo de tempo logarítmico.

Para o exemplo da árvore, você pode ver facilmente que descer um nível de nós reduz um número exponencial de elementos à medida que você continua percorrendo. O exemplo popular de procurar em uma lista telefônica classificada por nome é essencialmente equivalente a percorrer uma árvore de pesquisa binária (a página do meio é o elemento raiz e você pode deduzir a cada passo se vai para a esquerda ou para a direita).


3
+1 por mencionar "O logaritmo é essencialmente o inverso da exponenciação".
Talonx

12

Esses 2 casos levarão tempo O (log n)

case 1: f(int n) {
      int i;
      for (i = 1; i < n; i=i*2)
        printf("%d", i);
    }


 case 2  : f(int n) {
      int i;
      for (i = n; i>=1 ; i=i/2)
        printf("%d", i);
    }

Tenho certeza de que estou perdendo alguma coisa, mas eu não seria sempre zero e os loops são executados para sempre nos dois casos, já que 0 * 2 = 0 e 0/2 = 0?
dj_segfault

2
@dj_segfault, essa foi a minha mistake.I acho que agora faz sentido .. :)
Ravi Bisla

@RaviBisla Outras respostas afirmam que uma entrada de 10 levaria 1 vez mais que 10 loops, e uma entrada de 100 levaria 3 vezes o tempo de entrada de 1, o que definitivamente não é o caso com esses exemplos. stackoverflow.com/a/2307330/1667868
Sven van den Boogaart,

12

O (log n) é um pouco enganador, mais precisamente é O (log 2 n), ou seja (logaritmo com base 2).

A altura de uma árvore binária balanceada é O (log 2 n), pois cada nó possui dois (observe os "dois", como no log 2 n) nós filhos. Portanto, uma árvore com n nós tem uma altura de log 2 n.

Outro exemplo é a pesquisa binária, que possui um tempo de execução de O (log 2 n), pois a cada passo você divide o espaço de pesquisa por 2.


4
O (log n) é da mesma ordem que O (ld n) ou O (LN n). Eles são proporcionais. Entendo que, para fins de aprendizado, é mais fácil usar o ld.
helios

4
"mais precisamente é O (ld n)" - Não, não é: todos os logs são da mesma ordem (cada um diferindo dos outros apenas por algum fator de escala constante, que é ignorado / ignorável).
21710 ChrisW

1
você está certo, chris, redação muito ruim. deveria ter dito isso como helios. ajuda na aprendizagem / compreensão, mas finalmente todos os logs estão na mesma ordem.
Stmax 23/02

10

O(log n) refere-se a uma função (ou algoritmo, ou etapa de um algoritmo) trabalhando em uma quantidade de tempo proporcional ao logaritmo (geralmente base 2 na maioria dos casos, mas nem sempre, e, de qualquer forma, isso é insignificante pela notação big-O *) do tamanho da entrada.

A função logarítmica é o inverso da função exponencial. Em outras palavras, se sua entrada cresce exponencialmente (em vez de linearmente, como você normalmente consideraria), sua função cresce linearmente.

O(log n)os tempos de execução são muito comuns em qualquer tipo de aplicação de dividir e conquistar, porque você (idealmente) está cortando o trabalho pela metade toda vez. Se em cada uma das etapas de divisão ou conquista, você estiver trabalhando constantemente (ou um trabalho que não seja constante, mas com o tempo crescendo mais lentamente do que O(log n)), toda a sua função será O(log n). É bastante comum que cada etapa exija um tempo linear na entrada; isso equivalerá a uma complexidade total de tempo de O(n log n).

A complexidade do tempo de execução da pesquisa binária é um exemplo de O(log n) . Isso ocorre porque, na pesquisa binária, você sempre ignora metade de sua entrada em cada etapa posterior, dividindo a matriz pela metade e concentrando-se apenas na metade de cada etapa. Cada etapa é de tempo constante, porque na pesquisa binária você só precisa comparar um elemento com sua chave para descobrir o que fazer a seguir, independentemente do tamanho da matriz que você está considerando em qualquer momento. Então você executa aproximadamente as etapas log (n) / log (2).

A complexidade do tempo de execução da classificação por mesclagem é um exemplo de O(n log n). Isso ocorre porque você está dividindo a matriz ao meio com cada etapa, resultando em um total de aproximadamente etapas de log (n) / log (2). No entanto, em cada etapa, é necessário executar operações de mesclagem em todos os elementos (seja uma operação de mesclagem em duas sublistas de n / 2 elementos ou duas operações de mesclagem em quatro sublistas de n / 4 elementos, é irrelevante, pois aumenta a necessidade de faça isso para n elementos em cada etapa). Assim, a complexidade total é O(n log n).

* Lembre-se de que a notação big-O, por definição , constantes não importa. Também pela mudança da regra de base para logaritmos, a única diferença entre logaritmos de bases diferentes é um fator constante.


A nota final * resolveu minha confusão sobre os logaritmos serem baseados em 2 ou 10 :) Muito obrigado.
Yahia


9

Simplificando: em cada etapa do seu algoritmo, você pode cortar o trabalho pela metade. (Assintoticamente equivalente a terceiro, quarto, ...)


2
Esta resposta é muito imprecisa. Primeiro de tudo, você pode pensar em cortar o trabalho pela metade apenas no caso do logaritmo na base 2. É realmente incrível como essa resposta (e a maioria das respostas à pergunta original) recebeu tantos votos positivos. "(Assintoticamente equivalente a terceiro, quarto, ...)"? Por que responder a uma pergunta se você não tem tempo?
Nvr 20/02

8

Se você plotar uma função logarítmica em uma calculadora gráfica ou algo semelhante, verá que ela aumenta muito lentamente - ainda mais lentamente que uma função linear.

É por isso que algoritmos com uma complexidade de tempo logarítmica são muito procurados: mesmo para n realmente grande (digamos n = 10 ^ 8, por exemplo), eles executam mais do que aceitável.


7

Mas o que exatamente é O (log n)

O que isso significa precisamente é "como ntende para infinity, a timetendência para a*log(n)onde aé um fator de escala constante".

Ou, na verdade, não significa exatamente isso; mais provavelmente significa algo como " timedividido por a*log(n)tendências em direção a 1".

"Tende para" tem o significado matemático usual de 'análise': por exemplo, que "se você escolher qualquer constante diferente de zero arbitrariamente pequena k, posso encontrar um valor correspondente Xtal que ((time/(a*log(n))) - 1)seja menor do que kpara todos os valores nmaiores que X".


Em termos leigos, significa que a equação para o tempo pode ter outros componentes: por exemplo, pode ter um tempo de inicialização constante; mas esses outros componentes empalidecem na insignificância para grandes valores de n, e a * log (n) é o termo dominante para n grande.

Observe que se a equação fosse, por exemplo ...

tempo (n) = a + b log (n) + c n + d n n

... então seria O (n ao quadrado) porque, independentemente dos valores das constantes a, b, c e diferente de zero d, o d*n*ntermo sempre dominaria sobre as outras por qualquer valor suficientemente grande de n.

É isso que a notação O de bit significa: significa "qual é a ordem do termo dominante para qualquer n suficientemente grande".



7

Posso acrescentar algo interessante, que li no livro de Kormen e etc., há muito tempo. Agora, imagine um problema, onde temos que encontrar uma solução em um espaço problemático. Esse espaço problemático deve ser finito.

Agora, se você puder provar, a cada iteração do seu algoritmo você corta uma fração desse espaço, que não é menor que algum limite, isso significa que seu algoritmo está sendo executado no tempo O (logN).

Devo salientar que estamos falando aqui de um limite relativo de fração, não do absoluto. A pesquisa binária é um exemplo clássico. A cada passo, jogamos fora 1/2 do espaço do problema. Mas a pesquisa binária não é o único exemplo. Suponha, você provou de alguma forma, que a cada passo você joga fora pelo menos 1/128 do espaço do problema. Isso significa que seu programa ainda está sendo executado no horário O (logN), embora significativamente mais lento que a pesquisa binária. Essa é uma dica muito boa na análise de algoritmos recursivos. Frequentemente, pode-se provar que em cada etapa a recursão não utilizará várias variantes, e isso leva ao corte de alguma fração no espaço do problema.


6

Posso dar um exemplo para um loop for e, uma vez que compreendi o conceito, talvez seja mais simples de entender em diferentes contextos.

Isso significa que, no loop, o passo cresce exponencialmente. Por exemplo

for (i=1; i<=n; i=i*2) {;}

A complexidade na notação O deste programa é O (log (n)). Vamos tentar percorrê-lo manualmente (n estando em algum lugar entre 512 e 1023 (excluindo 1024):

step: 1   2   3   4   5    6    7    8     9     10
   i: 1   2   4   8   16   32   64   128   256   512

Embora n esteja entre 512 e 1023, apenas 10 iterações ocorrem. Isso ocorre porque a etapa no loop cresce exponencialmente e, portanto, leva apenas 10 iterações para atingir a terminação.

O logaritmo de x (na base de a) é a função inversa de a ^ x.

É como dizer que o logaritmo é o inverso do exponencial.

Agora tente ver dessa maneira, se o exponencial cresce muito rápido, o logaritmo cresce (inversamente) muito lentamente.

A diferença entre O (n) e O (log (n)) é enorme, semelhante à diferença entre O (n) e O (a ^ n) (sendo uma constante).


6

Na verdade, se você tiver uma lista de n elementos e criar uma árvore binária a partir dessa lista (como no algoritmo de dividir e conquistar), você continuará dividindo por 2 até chegar a listas de tamanho 1 (as folhas).

Na primeira etapa, você divide por 2. Você tem 2 listas (2 ^ 1), divide cada por 2, então você tem 4 listas (2 ^ 2), você divide novamente, você tem 8 listas (2 ^ 3 ) e assim sucessivamente até o tamanho da sua lista ser 1

Isso fornece a equação:

n/(2^steps)=1 <=> n=2^steps <=> lg(n)=steps

(você pega o lg de cada lado, sendo lg a base de log 2)


2
Até que alguns malwares comecem a inserir uma nova lista com comprimento x em dois níveis antes dos nós de saída. Em seguida, ele parece que vai ser um loop infinito ...
Francis Cugler

1
Não recebi seu comentário. Minha explicação está errada?
Dinaiz 28/02

1
Eu estava apenas fazendo uma piada hipotética. Eu realmente não estava significando nada com isso.
22817 Francis Cugler

6

Toda vez que escrevemos um algoritmo ou código, tentamos analisar sua complexidade assintótica. É diferente da complexidade do tempo .

Complexidade assintótica é o comportamento do tempo de execução de um algoritmo enquanto a complexidade do tempo é o tempo de execução real. Mas algumas pessoas usam esses termos de forma intercambiável.

Porque a complexidade do tempo depende de vários parâmetros viz.
1. Sistema físico
2. Linguagem de programação
3. Estilo de codificação
4. E muito mais ......

O tempo real de execução não é uma boa medida para análise.


Em vez disso, consideramos o tamanho da entrada como parâmetro, pois, independentemente do código, a entrada é a mesma. Portanto, o tempo de execução é uma função do tamanho da entrada.

A seguir, é apresentado um exemplo de algoritmo de tempo linear


Pesquisa linear
Dados os n elementos de entrada, para pesquisar um elemento na matriz, você precisa no máximo de comparações 'n' . Em outras palavras, não importa qual linguagem de programação você usa, que estilo de codificação você prefere, em que sistema você a executa. No pior cenário, requer apenas n comparações. O tempo de execução é linearmente proporcional ao tamanho da entrada.

E não é apenas a pesquisa, qualquer que seja o trabalho (incremento, comparação ou qualquer operação), é uma função do tamanho da entrada.

Portanto, quando você diz que qualquer algoritmo é O (log n), significa que o tempo de execução é o log vezes o tamanho de entrada n.

À medida que o tamanho da entrada aumenta, o trabalho realizado (aqui o tempo de execução) aumenta (daí a proporcionalidade)

      n      Work
      2     1 units of work
      4     2 units of work
      8     3 units of work

Veja como o tamanho da entrada aumentou, o trabalho realizado aumentou e é independente de qualquer máquina. E se você tentar descobrir o valor das unidades de trabalho, na verdade, depende dos parâmetros acima especificados. Isso mudará de acordo com os sistemas e tudo.


5

Árvore

log x to base b = y é o inverso de b^y = x

Se você tem uma árvore M-ária de profundidade d e tamanho n, então:

  • atravessando a árvore inteira ~ O (M ^ d) = O (n)

  • Percorrendo um único caminho na árvore ~ ​​O (d) = O (log na base M)


5

Em tecnologia da informação, isso significa que:

  f(n)=O(g(n)) If there is suitable constant C and N0 independent on N, 
  such that
  for all N>N0  "C*g(n) > f(n) > 0" is true.

Parece que essa notação foi tirada principalmente da matemática.

Neste artigo, há uma citação: DE Knuth, "BIG OMICRON E BIG OMEGA E BIG THETA", 1976 :

Com base nas questões discutidas aqui, proponho que os membros do SIGACT e os editores de periódicos de ciência da computação e matemática adotem notações conforme definido acima, a menos que uma alternativa melhor possa ser encontrada razoavelmente em breve .

Hoje é 2016, mas ainda o usamos hoje.


Na análise matemática, isso significa que:

  lim (f(n)/g(n))=Constant; where n goes to +infinity

Mas, mesmo na análise matemática, algumas vezes esse símbolo era usado para significar "C * g (n)> f (n)> 0".

Como eu sei da universidade, o símbolo foi introduzido pelo matemático alemão Landau (1877-1938)


4

O exemplo binário completo é O (ln n) porque a pesquisa se parece com isso:

1 2 3 4 5 6 7 8 9 10 11 12

A pesquisa de 4 gera 3 ocorrências: 6, 3 e 4. E log2 12 = 3, que é uma boa proporção de quantas ocorrências necessárias.


obrigado pelo exemplo. Diz claramente como nosso algoritmo pode usar o tempo logarítmico no método de dividir e conquistar.
Abc

Então, se é um loop de n / 2, é sempre log (n)?
Gil Beyruth

3

Se você procura uma resposta baseada na intuição, gostaria de apresentar duas interpretações para você.

  1. Imagine uma colina muito alta com uma base muito ampla também. Para chegar ao topo da colina, existem duas maneiras: uma é um caminho dedicado que gira em espiral ao redor da colina, chegando ao topo; a outra: pequeno terraço como esculturas cortadas para fornecer uma escada. Agora, se a primeira maneira está atingindo no tempo linear O (n), a segunda é O (log n).

  2. Imagine um algoritmo que aceita um número inteiro ncomo entrada e é concluído no tempo proporcional a nO (n) ou teta (n), mas se for executado na proporção de tempo ao number of digits or the number of bits in the binary representation on numberentão o algoritmo será executado em O (log n) ou teta (log n) hora.


por favor edite. tem "O (n) ou teta (n)" nos dois cenários ...? Além disso, eu já ouvi muito isso, o tamanho versus os dígitos #. Estamos dizendo tamanho === 128 para n = 10000000 e dígitos === 8 para n = 10000000? Elucide por favor.
Cody

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.