Ontem à noite eu estava discutindo com outro programador que, embora algo possa ser O (1), uma operação que é O (n) pode superá-la se houver uma constante grande no algoritmo O (1). Ele discordou, então eu trouxe aqui.
Existem exemplos de algoritmos que superam muito os da classe abaixo? Por exemplo, O (n) sendo mais rápido que O (1) ou O (n 2 ) sendo mais rápido que O (n).
Matematicamente, isso pode ser demonstrado para uma função com limites superiores assintóticos, quando você desconsidera fatores constantes, mas esses algoritmos existem na natureza? E onde eu encontraria exemplos deles? Para que tipos de situações elas são usadas?
algorithms
big-o
KyleWpppd
fonte
fonte
n
suficiente para compensar a constante (que é o ponto da notação O-grande).Respostas:
Pesquisas em tabelas de dados fixas muito pequenas. Uma tabela de hash otimizada pode ser O (1) e ainda mais lenta que uma pesquisa binária ou mesmo uma pesquisa linear devido ao custo do cálculo de hash.
fonte
Multiplicação da matriz. O algoritmo ingênuo O (n ^ 3) é frequentemente usado na prática tão rápido quanto o O de Strassen (n ^ 2.8) para matrizes de pequenas dimensões; e Strassen é usado em vez do algoritmo O (n ^ 2.3) Coppersmith – Winograd para matrizes maiores.
fonte
Um exemplo simples é a diferença entre vários algoritmos de classificação. Mergesort, Heapsort e alguns outros são O (n log n) . Quicksort é O (n ^ 2) no pior caso. Mas muitas vezes o Quicksort é mais rápido e, na verdade, ele executa em média como O (n log n) . Mais informações .
Outro exemplo é a geração de um único número de Fibonacci. O algoritmo iterativo é O (n) , enquanto o algoritmo baseado em matriz é O (log n) . Ainda assim, para o primeiro par de milhares de números de Fibonacci, o algoritmo iterativo é provavelmente mais rápido. Isso também depende da implementação, é claro!
Algoritmos com melhor desempenho assintótico podem conter operações caras que não são necessárias com um algoritmo com desempenho pior, mas operações mais simples. No final, a anotação O diz apenas algo sobre desempenho quando o argumento em que ele opera aumenta dramaticamente (se aproxima do infinito).
fonte
Nota: Por favor, leia os comentários de @ back2dos abaixo e de outros gurus, pois eles são de fato mais úteis do que eu escrevi - Obrigado por todos os colaboradores.
Penso que no gráfico abaixo (extraído de: Big O notation , procure "The Pessimistic Nature of Algorithms:"), você pode ver que O (log n) nem sempre é melhor do que dizer, O (n). Então, acho que seu argumento é válido.
fonte
y = 1
,y = log x
etc., e a interseção dey = 1
ey = x
é realmente o ponto(1,1)
. Se isso realmente estivesse correto, do que diria, os algoritmos de maior complexidade podem ser mais rápidos para 0 a 2 entradas, algo que as pessoas dificilmente se importariam. O que o gráfico deixa completamente de levar em conta (e de que vem a diferença de desempenho perceptível em questão) são fatores constantes.Para valores práticos de
n
, sim. Isso aparece muito na teoria do CS. Freqüentemente, existe um algoritmo complicado que possui tecnicamente um desempenho grande de Oh, mas os fatores constantes são tão grandes que o tornam impraticável.Certa vez, meu professor de geometria computacional descreve um algoritmo para triangular um polígono em tempo linear, mas ele terminou com "muito complicado. Acho que ninguém realmente o implementou" (!!).
Além disso, as pilhas de fibonacci têm melhores características do que as pilhas normais, mas não são muito populares, porque na prática não se saem tão bem quanto as pilhas regulares. Isso pode cascatear com outros algoritmos que usam pilhas - por exemplo, os caminhos mais curtos de Dijkstra são matematicamente mais rápidos com uma pilha de fibonacci, mas geralmente não na prática.
fonte
Compare a inserção em uma lista vinculada e a inserção em uma matriz redimensionável.
A quantidade de dados deve ser bastante grande para que a inserção da lista vinculada O (1) valha a pena.
Uma lista vinculada possui uma sobrecarga extra para os próximos ponteiros e desreferências. Uma matriz redimensionável precisa copiar dados. Essa cópia é O (n), mas na prática é muito rápida.
fonte
A notação Big-Oh é usada para descrever a taxa de crescimento de uma função, portanto, é possível que um algoritmo O (1) seja mais rápido, mas apenas até um determinado ponto (o fator constante).
Notações comuns:
O (1) - O número de iterações (às vezes você pode se referir a isso como tempo gasto pelo usuário pela função) não depende do tamanho da entrada e é constante.
O (n) - O número de iterações cresce em uma proporção linear ao tamanho da entrada. Significado - se o algoritmo itera através de qualquer entrada N, 2 * N vezes, ainda é considerado O (n).
O (n ^ 2) (quadrático) - O número de iterações é o tamanho da entrada ao quadrado.
fonte
As bibliotecas Regex geralmente são implementadas para fazer backtracking com pior tempo exponencial, em vez de geração do DFA com complexidade de
O(nm)
.O retrocesso ingênuo pode ter um desempenho melhor quando a entrada permanece no caminho rápido ou falha sem a necessidade de retroceder excessivamente.
(Embora essa decisão não seja apenas baseada no desempenho, é também permitir referências anteriores.)
fonte
Um
O(1)
algoritmo:Um
O(n)
algoritmo:Claramente, para qualquer valor de
n
onden < one_million
, oO(n)
algoritmo dado no exemplo será mais rápido que oO(1)
algoritmo.Embora este exemplo seja um pouco complexo, é equivalente em espírito ao exemplo a seguir:
Você deve saber as constantes e coeficientes em sua
O
expressão, e você deve saber o intervalo esperado den
, a fim de determinar a priori qual algoritmo vai acabar por ser mais rápido.Caso contrário, você deve comparar os dois algoritmos com valores
n
no intervalo esperado para determinar a posteriori qual algoritmo acabou sendo mais rápido.fonte
Classificação:
A classificação por inserção é O (n ^ 2), mas supera os outros algoritmos de classificação O (n * log (n)) para um pequeno número de elementos.
Essa é a razão pela qual a maioria das implementações de classificação usa uma combinação de dois algoritmos. Por exemplo, use a classificação de mesclagem para dividir grandes matrizes até atingirem um determinado tamanho de matriz, depois use a classificação de inserção para classificar as unidades menores e mesclá-las novamente com a classificação de mesclagem.
Consulte Timsort a implementação padrão atual da classificação Python e Java 7 que usa essa técnica.
fonte
O algoritmo de unificação usado na prática é exponencial no pior dos casos, para algumas entradas patológicas.
Existe um algoritmo de unificação polinomial , mas na prática é muito lento.
fonte
O Bubblesort na memória pode superar o quicksort quando o programa está sendo trocado para o disco ou é necessário ler todos os itens do disco ao comparar.
Este deve ser um exemplo com o qual ele pode se relacionar.
fonte
Geralmente, os algoritmos mais avançados assumem uma certa quantidade de configuração (cara). Se você precisar executá-lo apenas uma vez, talvez seja melhor usar o método da força bruta.
Por exemplo: pesquisa binária e pesquisa de tabela de hash são muito mais rápidas por pesquisa do que uma pesquisa linear, mas exigem que você classifique a lista ou construa a tabela de hash, respectivamente.
A classificação custará N log (N) e a tabela de hash custará pelo menos N. Agora, se você estiver fazendo centenas ou milhares de pesquisas, isso ainda é uma economia amortizada. Mas se você precisar fazer apenas uma ou duas pesquisas, pode fazer sentido fazer apenas a pesquisa linear e economizar o custo de inicialização.
fonte
A descriptografia é geralmente 0 (1). Por exemplo, o espaço da chave para DES é 2 ^ 56, portanto, a descriptografia de qualquer mensagem é uma operação de tempo constante. É que você tem um fator de 2 ^ 56, então é uma constante muito grande.
fonte
Diferentes implementações de conjuntos vêm à minha mente. Um dos mais ingênuos é implementá-lo sobre um vetor, o que significa que,
remove
assim comocontains
e, portanto,add
todos recebem O (N).Uma alternativa é implementá-lo sobre algum hash de uso geral, que mapeia hashes de entrada para valores de entrada. Executa implementação conjunto Tais um com o (1) para
add
,contains
eremove
.Se assumirmos que N é cerca de 10 ou mais, então a primeira implementação é provavelmente mais rápida. Tudo o que precisa fazer para encontrar um elemento é comparar 10 valores a um.
A outra implementação terá que iniciar todos os tipos de transformações inteligentes, que podem ser muito mais caras do que fazer 10 comparações. Com toda a sobrecarga, você pode até ter falhas de cache e, em seguida, realmente não importa a rapidez da sua solução em teoria.
Isso não significa que a pior implementação que você pode imaginar terá um desempenho decente, se N for pequeno o suficiente. Simplesmente significa, para N suficientemente pequeno, que uma implementação ingênua, com pouco espaço e sobrecarga, pode realmente exigir menos instruções e causar menos falhas de cache do que uma implementação que coloca a escalabilidade em primeiro lugar e, portanto, será mais rápida.
Você realmente não pode saber o quão rápido algo está em um cenário do mundo real, até que você o coloque em um e simplesmente meça. Muitas vezes, os resultados são surpreendentes (pelo menos para mim).
fonte
Sim, para N. adequadamente pequeno. Sempre haverá um N, acima do qual você sempre terá a ordem O (1) <O (lg N) <O (N) <O (N log N) <O (N ^ c ) <O (c ^ N) (onde O (1) <O (lg N) significa que em um algoritmo O (1) haverá menos operações quando N for adequadamente grande ec é uma constante fixa maior que 1 )
Digamos que um algoritmo O (1) específico execute exatamente f (N) = 10 ^ 100 (um googol) e um algoritmo O (N) execute exatamente g (N) = 2 N + 5 operações. O algoritmo O (N) oferecerá maior desempenho até que você N seja aproximadamente um googol (na verdade, quando N> (10 ^ 100 - 5) / 2), portanto, se você esperasse que N estivesse na faixa de 1000 a um bilhão, sofreria uma penalidade maior usando o algoritmo O (1).
Ou, para uma comparação realista, digamos que você esteja multiplicando números de n dígitos. O algoritmo Karatsuba é no máximo 3 n ^ (lg 3) operações (que é aproximadamente O (n ^ 1.585)) enquanto o algoritmo Schönhage – Strassen é O (N log N log log N), que é uma ordem mais rápida , mas para citar wikipedia:
Portanto, se você estiver multiplicando números de 500 dígitos, não faz sentido usar o algoritmo "mais rápido" pelos grandes argumentos O.
EDIT: Você pode encontrar determinar f (N) comparado g (N), tomando o limite N-> infinito de f (N) / g (N). Se o limite for 0, então f (N) <g (N), se o limite for infinito, então f (N)> g (N), e se o limite for alguma outra constante, então f (N) ~ g (N) em termos de grande notação O.
fonte
O método simplex para programação linear pode ser exponencial no pior dos casos, enquanto algoritmos de ponto interior relativamente novos podem ser polinomiais.
No entanto, na prática, o pior cenário exponencial para o método simplex não aparece - o método simplex é rápido e confiável, enquanto os algoritmos iniciais de pontos interiores eram muito lentos para serem competitivos. (Agora existem algoritmos de pontos interiores mais modernos que são competitivos - mas o método simplex também é ...)
fonte
O algoritmo de Ukkonen para criar tentativas de sufixo é O (n log n). Tem a vantagem de estar "on-line" - ou seja, você pode acrescentar mais texto de forma incremental.
Recentemente, outros algoritmos mais complexos alegaram ser mais rápidos na prática, principalmente porque o acesso à memória tem uma localidade mais alta, melhorando assim a utilização do cache do processador e evitando paradas no pipeline da CPU. Veja, por exemplo, esta pesquisa , que afirma que 70-80% do tempo de processamento é gasto aguardando memória, e este artigo descrevendo o algoritmo "wotd".
As tentativas de sufixo são importantes em genética (para correspondência de seqüências genéticas) e, um pouco menos importante, na implementação de dicionários Scrabble.
fonte
Sempre existe o algoritmo mais rápido e mais curto para qualquer problema bem definido . Porém, é apenas puramente teoricamente o algoritmo (assintoticamente) mais rápido.
Dado qualquer descrição de um problema P e uma instância para esse problema que eu , ele enumera todos os algoritmos possíveis A e provas Pr , verificando para cada tal par se Pr é uma prova válida de que A é o algoritmo assintoticamente mais rápido para P . Se ele encontrar uma tal prova, em seguida, executa um em I .
A procura desse par à prova de problemas tem complexidade O (1) (para um problema fixo P ), portanto, você sempre usa o algoritmo assintoticamente mais rápido para o problema. No entanto, como essa constante é tão indescritivelmente enorme em quase todos os casos, esse método é completamente inútil na prática.
fonte
Muitas linguagens / estruturas usam correspondência de padrão ingênua para corresponder as strings em vez do KMP . Procuramos por cordas como Tom, Nova York, em vez de ababaabababababaababababababab.
fonte