Existem algoritmos O (1 / n)?
Ou qualquer outra coisa que seja menor que O (1)?
Existem algoritmos O (1 / n)?
Ou qualquer outra coisa que seja menor que O (1)?
Respostas:
Esta questão não é tão estúpida quanto possa parecer. Pelo menos teoricamente, algo como O (1 / n ) é completamente sensível quando adotamos a definição matemática da notação O grande :
Agora você pode facilmente substituir g ( x ) por 1 / x … é óbvio que a definição acima ainda vale para alguns f .
Com o objetivo de estimar o crescimento assintótico do tempo de execução, isso é menos viável ... um algoritmo significativo não pode ficar mais rápido à medida que a entrada cresce. Claro, você pode construir um algoritmo arbitrário para cumprir isso, por exemplo, o seguinte:
def get_faster(list):
how_long = (1 / len(list)) * 100000
sleep(how_long)
Claramente, essa função gasta menos tempo à medida que o tamanho da entrada aumenta ... pelo menos até algum limite, imposto pelo hardware (precisão dos números, tempo mínimo que sleep
pode esperar, tempo para processar argumentos etc.): esse limite seria então um limite inferior constante; de fato, a função acima ainda possui o tempo de execução O (1).
Mas não são de fato algoritmos do mundo real, onde o tempo de execução pode diminuir (pelo menos parcialmente) quando o tamanho de entrada aumenta. Observe que esses algoritmos não exibem comportamento em tempo de execução abaixo de O (1). Ainda assim, eles são interessantes. Por exemplo, considere o algoritmo de pesquisa de texto muito simples da Horspool . Aqui, o tempo de execução esperado diminuirá à medida que a duração do padrão de pesquisa aumentar (mas o aumento da duração do palheiro aumentará novamente o tempo de execução).
Sim.
Existe precisamente um algoritmo com tempo de execução O (1 / n), o algoritmo "vazio".
Para um algoritmo ser O (1 / n), significa que ele é executado assintoticamente em menos etapas que o algoritmo que consiste em uma única instrução. Se for executado em menos de um passo para todos os n> n0, deverá consistir precisamente em nenhuma instrução para os n. Como a verificação de 'se n> n0' custa pelo menos 1 instrução, ela não deve consistir em nenhuma instrução para todos os n.
Resumindo: O único algoritmo que é O (1 / n) é o algoritmo vazio, que não consiste em nenhuma instrução.
sharptooth está correto, O (1) é o melhor desempenho possível. No entanto, isso não implica uma solução rápida, apenas uma solução em tempo fixo.
Uma variante interessante, e talvez o que realmente esteja sendo sugerido, é quais problemas ficam mais fáceis à medida que a população cresce. Eu posso pensar em uma resposta, embora artificial e explícita:
Duas pessoas em um set têm o mesmo aniversário? Quando n exceder 365, retorne true. Embora para menos de 365, este seja O (n ln n). Talvez não seja uma ótima resposta, pois o problema não fica lentamente mais fácil, mas se torna O (1) para n> 365.
Isso não é possível. A definição de Big-O não é maior que desigualdade:
A(n) = O(B(n))
<=>
exists constants C and n0, C > 0, n0 > 0 such that
for all n > n0, A(n) <= C * B(n)
Portanto, B (n) é de fato o valor máximo; portanto, se diminuir à medida que n aumenta, a estimativa não será alterada.
Do meu aprendizado anterior sobre a grande notação O, mesmo que você precise de 1 etapa (como verificar uma variável, realizar uma atribuição), é O (1).
Observe que O (1) é o mesmo que O (6), porque a "constante" não importa. É por isso que dizemos que O (n) é o mesmo que O (3n).
Portanto, se você precisar de apenas 1 passo, é O (1) ... e como seu programa precisa de pelo menos 1 passo, o mínimo que um algoritmo pode seguir é O (1). A menos que não façamos, então é O (0), eu acho? Se fizermos alguma coisa, então é O (1), e esse é o mínimo que pode ser.
(Se escolhermos não fazê-lo, isso poderá se tornar uma pergunta Zen ou Tao ... no domínio da programação, O (1) ainda é o mínimo).
Ou que tal:
programador : chefe, encontrei uma maneira de fazer isso em O (1) tempo!
chefe : não há necessidade de fazê-lo, estamos falidos esta manhã.
programador : oh, então, torna-se O (0).
Não, isso não é possível:
Como n tende ao infinito em 1 / n, finalmente alcançamos 1 / (inf), que é efetivamente 0.
Assim, a classe grande-oh do problema seria O (0) com um n massivo, mas mais próximo do tempo constante com um n baixo. Isso não é sensato, pois a única coisa que pode ser feita mais rapidamente que o tempo constante é:
void nothing() {};
E mesmo isso é discutível!
Assim que você executa um comando, você está em pelo menos O (1), então não, não podemos ter uma grande classe de O (1 / n)!
Que tal não executar a função (NOOP)? ou usando um valor fixo. Isso conta?
Costumo usar O (1 / n) para descrever probabilidades que diminuem à medida que as entradas aumentam - por exemplo, a probabilidade de uma moeda justa aparecer depois do log2 (n) é O (1 / n).
O (1) significa simplesmente "tempo constante".
Quando você adiciona uma saída antecipada a um loop [1], está (em notação big-O) transformando um algoritmo O (1) em O (n), mas tornando-o mais rápido.
O truque é, em geral, o algoritmo de tempo constante é o melhor e o linear é melhor que o exponencial, mas para pequenas quantidades de n, o algoritmo exponencial pode ser realmente mais rápido.
1: Assumindo um comprimento de lista estático para este exemplo
Para quem ler esta pergunta e quiser entender do que se trata a conversa, isso pode ajudar:
| |constant |logarithmic |linear| N-log-N |quadratic| cubic | exponential |
| n | O(1) | O(log n) | O(n) |O(n log n)| O(n^2) | O(n^3) | O(2^n) |
| 1 | 1 | 1 | 1| 1| 1| 1 | 2 |
| 2 | 1 | 1 | 2| 2| 4| 8 | 4 |
| 4 | 1 | 2 | 4| 8| 16| 64 | 16 |
| 8 | 1 | 3 | 8| 24| 64| 512 | 256 |
| 16 | 1 | 4 | 16| 64| 256| 4,096 | 65536 |
| 32 | 1 | 5 | 32| 160| 1,024| 32,768 | 4,294,967,296 |
| 64 | 1 | 6 | 64| 384| 4,069| 262,144 | 1.8 x 10^19 |
Acredito que algoritmos quânticos podem fazer vários cálculos "de uma só vez" via superposição ...
Duvido que seja uma resposta útil.
muitas pessoas tiveram a resposta correta (Não) Aqui está outra maneira de provar isso: Para ter uma função, você deve chamar a função e retornar uma resposta. Isso leva uma certa quantidade constante de tempo. MESMO QUE o restante do processamento levou menos tempo para entradas maiores, imprimir a resposta (que podemos assumir como um único bit) leva pelo menos tempo constante.
Se a solução existe, pode ser preparada e acessada em tempo constante = imediatamente. Por exemplo, usando uma estrutura de dados LIFO, se você souber que a consulta de classificação é por ordem inversa. Os dados já estão classificados, dado que o modelo apropriado (LIFO) foi escolhido.
Quais problemas ficam mais fáceis à medida que a população cresce? Uma resposta é uma coisa como bittorrent, em que a velocidade do download é uma função inversa do número de nós. Ao contrário de um carro, que diminui a velocidade do carregamento, uma rede de compartilhamento de arquivos como o bittorrent acelera os nós conectados.
Você não pode ir abaixo de O (1), no entanto O (k) onde k é menor que N é possível. Nós os chamamos de algoritmos de tempo sublinear . Em alguns problemas, o algoritmo de tempo sublinhado pode fornecer apenas soluções aproximadas para um problema específico. No entanto, às vezes, uma solução aproximada é ótima, provavelmente porque o conjunto de dados é muito grande ou muito caro em termos computacionais para calcular tudo.
Que tal isso:
void FindRandomInList(list l)
{
while(1)
{
int rand = Random.next();
if (l.contains(rand))
return;
}
}
À medida que o tamanho da lista aumenta, o tempo de execução esperado do programa diminui.
constains
é O (1)
O (1 / n) não é menor que O (1), basicamente significa que quanto mais dados você tiver, mais rápido será o algoritmo. Digamos que você obtenha uma matriz e sempre preencha até 10 100 elementos se ela tiver menos que isso e não faça nada se houver mais. Este não é O (1 / n), é claro, mas algo como O (-n) :) A notação O-big muito ruim não permite valores negativos.
Como foi apontado, além da possível exceção da função nula, não pode haver O(1/n)
funções, pois o tempo necessário terá que se aproximar de 0.
Obviamente, existem alguns algoritmos, como o definido por Konrad, que parecem ser menos do que O(1)
em pelo menos algum sentido.
def get_faster(list):
how_long = 1/len(list)
sleep(how_long)
Se você deseja investigar esses algoritmos, defina sua própria medida assintótica ou sua própria noção de tempo. Por exemplo, no algoritmo acima, eu poderia permitir o uso de várias operações "gratuitas" por um determinado período de vezes. No algoritmo acima, se eu definir t 'excluindo o tempo para tudo, menos o sono, então t' = 1 / n, que é O (1 / n). Provavelmente existem exemplos melhores, pois o comportamento assintótico é trivial. Na verdade, tenho certeza de que alguém lá fora pode ter sentidos que dão resultados não triviais.
A maioria das demais respostas interpreta big-O como sendo exclusivamente sobre o tempo de execução de um algoritmo. Mas como a pergunta não mencionou, achei que vale a pena mencionar a outra aplicação de big-O na análise numérica, que é sobre erro.
Muitos algoritmos podem ser O (h ^ p) ou O (n ^ {- p}), dependendo do tamanho da etapa (h) ou do número de divisões (n). Por exemplo, no método de Euler , você procura uma estimativa de y (h), considerando que conhece y (0) e dy / dx (a derivada de y). Sua estimativa de y (h) é mais precisa quanto mais próximo h estiver de 0. Portanto, para encontrar y (x) para algum x arbitrário, é preciso pegar o intervalo de 0 a x, dividi-lo até n pedaços e executar o método de Euler em cada ponto, para ir de y (0) a y (x / n) a y (2x / n) e assim por diante.
Portanto, o método de Euler é um algoritmo O (h) ou O (1 / n), em que h é tipicamente interpretado como um tamanho de etapa en é interpretado como o número de vezes que você divide um intervalo.
Você também pode ter O (1 / h) em aplicativos de análise numérica reais, devido a erros de arredondamento de ponto flutuante . Quanto menor o intervalo, mais cancelamentos ocorrem para a implementação de determinados algoritmos, mais perda de dígitos significativos e, portanto, mais erros, que são propagados pelo algoritmo.
Para o método de Euler, se você estiver usando pontos flutuantes, use uma etapa e o cancelamento suficientemente pequenos e adicione um número pequeno a um número grande, deixando o número grande inalterado. Para algoritmos que calculam a derivada subtraindo um ao outro dois números de uma função avaliada em duas posições muito próximas, aproximando y '(x) de (y (x + h) - y (x) / h), em funções suaves y (x + h) se aproxima de y (x) resultando em cancelamento grande e uma estimativa para o derivado com menos números significativos. Por sua vez, isso será propagado para qualquer algoritmo para o qual você precise da derivada (por exemplo, um problema de valor limite).
OK, pensei um pouco sobre isso, e talvez exista um algoritmo que possa seguir esta forma geral:
Você precisa calcular o problema do vendedor ambulante para um gráfico de 1000 nós; no entanto, você também recebe uma lista de nós que não pode visitar. À medida que a lista de nós invisíveis aumenta, o problema se torna mais fácil de resolver.
Eu vejo um algoritmo que é O (1 / n) reconhecidamente em um limite superior:
Você tem uma grande série de entradas que estão sendo alteradas devido a algo externo à rotina (talvez elas reflitam o hardware ou pode ser outro núcleo no processador que está fazendo isso.) E você deve selecionar uma aleatória, mas válida.
Agora, se não estivesse mudando, você simplesmente faria uma lista de itens, escolha um aleatoriamente e obtenha O (1). No entanto, a natureza dinâmica dos dados impede a criação de uma lista. Você simplesmente precisa fazer uma sondagem aleatória e testar a validade da sondagem. (E observe que, inerentemente, não há garantia de que a resposta ainda seja válida quando for devolvida. Isso ainda pode ter utilidades - por exemplo, a IA de uma unidade em um jogo. Ele poderia disparar contra um alvo que desapareceu de vista enquanto estava apertando o gatilho.)
Isso tem um desempenho de infinito no pior caso, mas um desempenho médio de caso que diminui à medida que o espaço de dados se enche.
Na análise numérica, os algoritmos de aproximação devem ter complexidade assintótica sub-constante na tolerância de aproximação.
class Function
{
public double[] ApproximateSolution(double tolerance)
{
// if this isn't sub-constant on the parameter, it's rather useless
}
}
Eu acho que menos de O (1) não é possível. Qualquer tempo gasto por algo é denominado O (1). Mas para O (1 / n), que tal a função abaixo. (Eu sei que existem muitas variantes já apresentadas nesta solução, mas acho que todas elas têm algumas falhas (não são importantes, explicam bem o conceito). Então, aqui está uma, apenas por uma questão de argumento:
def 1_by_n(n, C = 10): #n could be float. C could be any positive number
if n <= 0.0: #If input is actually 0, infinite loop.
while True:
sleep(1) #or pass
return #This line is not needed and is unreachable
delta = 0.0001
itr = delta
while delta < C/n:
itr += delta
Assim, à medida que n aumenta, a função leva cada vez menos tempo. Também é garantido que, se a entrada realmente for 0, a função levará uma eternidade para retornar.
Pode-se argumentar que será limitado pela precisão da máquina. portanto, sinc eit tem um limite superior, é O (1). Mas também podemos ignorar isso, inserindo entradas de n e C na string. E adição e comparação são feitas na string. A idéia é que, com isso, possamos reduzir n arbitrariamente pequenos. Portanto, o limite superior da função não é limitado, mesmo quando ignoramos n = 0.
Eu também acredito que não podemos apenas dizer que o tempo de execução é O (1 / n). Mas devemos dizer algo como O (1 + 1 / n)
Pode ser possível construir um algoritmo que seja O (1 / n). Um exemplo seria um loop que itera alguns múltiplos de f (n) -n vezes em que f (n) é alguma função cujo valor é garantido maior que n e o limite de f (n) -n à medida que n se aproxima do infinito é zero. O cálculo de f (n) também precisaria ser constante para todos os n. Eu não sei de imediato como seria f (n) ou qual aplicativo esse algoritmo teria, na minha opinião, porém, essa função poderia existir, mas o algoritmo resultante não teria outro objetivo senão provar a possibilidade de um algoritmo com O (1 / n).
Eu não sei sobre algoritmos, mas complexidades menores que O (1) aparecem em algoritmos aleatórios. Na verdade, o (1) (pouco o) é menor que O (1). Esse tipo de complexidade geralmente aparece em algoritmos aleatórios. Por exemplo, como você disse, quando a probabilidade de algum evento é da ordem 1 / n, eles o denotam com o (1). Ou quando eles querem dizer que algo acontece com alta probabilidade (por exemplo, 1 - 1 / n), eles denotam isso com 1 - o (1).
Se a resposta for a mesma, independentemente dos dados de entrada, você terá um algoritmo O (0).
ou em outras palavras - a resposta é conhecida antes dos dados de entrada serem enviados - a função pode ser otimizada - então O (0)
A notação Big-O representa a pior cenário para um algoritmo que não é a mesma coisa que seu tempo de execução típico. É simples provar que um algoritmo O (1 / n) é um algoritmo O (1). Por definição,
O (1 / n) -> T (n) <= 1 / n, para todos n> = C> 0
O (1 / n) -> T (n) <= 1 / C, uma vez que 1 / n <= 1 / C para todos n> = CO
(1 / n) -> O (1), pois a notação Big-O ignora constantes (ou seja, o valor de C não importa)
hashtable-contains
algoritmo que pode ser indicado como O (1) - e o pior caso pode ser dado com muita precisão como Theta (n)! Omega e Theta podem simplesmente ser usados para denotar outros limites, mas para repetir : eles não têm nada a ver com o melhor ou o médio.
Nada é menor que a notação O (1) Big-O implica a maior ordem de complexidade para um algoritmo
Se um algoritmo tem um tempo de execução de n ^ 3 + n ^ 2 + n + 5, então é O (n ^ 3) As potências mais baixas não têm importância aqui porque, como n -> Inf, n ^ 2 será irrelevante em comparação com n ^ 3
Da mesma forma que n -> Inf, O (1 / n) será irrelevante em comparação com O (1), portanto, 3 + O (1 / n) será o mesmo que O (1), tornando O (1) o menor valor computacional possível complexidade
inline void O0Algorithm() {}
Aqui está um algoritmo O (1 / n) simples. E até faz algo interessante!
function foo(list input) {
int m;
double output;
m = (1/ input.size) * max_value;
output = 0;
for (int i = 0; i < m; i++)
output+= random(0,1);
return output;
}
O (1 / n) é possível, pois descreve como a saída de uma função é alterada, considerando o tamanho crescente da entrada. Se estivermos usando a função 1 / n para descrever o número de instruções que uma função executa, não há requisitos para que a função siga zero instruções para qualquer tamanho de entrada. Pelo contrário, é que para cada tamanho de entrada, n acima de algum limite, o número de instruções necessárias é limitado acima por uma constante positiva multiplicada por 1 / n. Como não existe um número real para o qual 1 / n seja 0 e a constante seja positiva, não há razão para que a função seja restrita a receber 0 ou menos instruções.