Entendo a notação Big-O, mas não sei como calculá-la para muitas funções. Em particular, tenho tentado descobrir a complexidade computacional da versão ingênua da sequência de Fibonacci:
int Fibonacci(int n)
{
if (n <= 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Qual é a complexidade computacional da sequência de Fibonacci e como é calculada?
Respostas:
Você modela a função de tempo para calcular
Fib(n)
como soma do tempo para calcularFib(n-1)
mais o tempo para calcularFib(n-2)
mais o tempo para adicioná-los (O(1)
). Isso pressupõe que avaliações repetidas do mesmoFib(n)
levam o mesmo tempo - ou seja, nenhuma memorização é usada.T(n<=1) = O(1)
T(n) = T(n-1) + T(n-2) + O(1)
Você resolve essa relação de recorrência (usando funções geradoras, por exemplo) e termina com a resposta.
Como alternativa, você pode desenhar a árvore de recursão, que terá profundidade
n
e descobrirá intuitivamente que essa função é assintoticamente . Você pode provar sua conjectura por indução.O(2
n
)
Base:
n = 1
é óbvioSuponha , portanto
T(n-1) = O(2
n-1
)
T(n) = T(n-1) + T(n-2) + O(1)
que é igual aT(n) = O(2
n-1
) + O(2
n-2
) + O(1) = O(2
n
)
No entanto, como observado em um comentário, esse não é o limite. Um fato interessante sobre essa função é que T (n) é assintoticamente o mesmo que o valor de,
Fib(n)
uma vez que ambos são definidos comof(n) = f(n-1) + f(n-2)
.As folhas da árvore de recursão sempre retornam 1. O valor de
Fib(n)
é a soma de todos os valores retornados pelas folhas na árvore de recursão, que é igual à contagem de folhas. Como cada folha levará O (1) para calcular,T(n)
é igual aFib(n) x O(1)
. Consequentemente, o limite restrito para essa função é a própria sequência de Fibonacci (~ ). Você pode descobrir esse limite usando funções geradoras, como eu mencionei acima.θ(1.6
n
)
fonte
Apenas pergunte a si mesmo quantas instruções precisam ser executadas para
F(n)
serem concluídas.Pois
F(1)
, a resposta é1
(a primeira parte do condicional).Pois
F(n)
, a resposta éF(n-1) + F(n-2)
.Então, qual função satisfaz essas regras? Tente n (a> 1):
a n == a (n-1) + a (n-2)
Divida por a (n-2) :
a 2 == a + 1
Resolver
a
e obter(1+sqrt(5))/2 = 1.6180339887
, também conhecido como a proporção áurea .Portanto, leva tempo exponencial.
fonte
Eu concordo com pgaur e rickerbh, a complexidade recursiva-fibonacci é O (2 ^ n).
Cheguei à mesma conclusão de uma maneira bastante simplista, mas acredito que o raciocínio ainda é válido.
Primeiro, trata-se de descobrir quantas vezes a função fibonacci recursiva (F () de agora em diante) é chamada ao calcular o número de N-ésimo fibonacci. Se for chamado uma vez por número na sequência de 0 a n, teremos O (n); se for chamado n vezes para cada número, obteremos O (n * n) ou O (n ^ 2), e assim por diante.
Portanto, quando F () é chamado para um número n, o número de vezes que F () é chamado para um determinado número entre 0 e n-1 cresce à medida que nos aproximamos de 0.
Como primeira impressão, parece-me que, se colocarmos de maneira visual, desenhar uma unidade por vez que F () é chamado para um determinado número, o molhado recebe uma espécie de forma de pirâmide (ou seja, se centralizarmos as unidades horizontalmente) ) Algo assim:
Agora, a pergunta é: com que rapidez a base dessa pirâmide aumenta à medida que n cresce?
Vamos considerar um caso real, por exemplo F (6)
Vemos que F (0) é chamado 32 vezes, o que é 2 ^ 5, que para este caso de amostra é 2 ^ (n-1).
Agora, queremos saber quantas vezes F (x) é chamado, e podemos ver que o número de vezes que F (0) é chamado é apenas uma parte disso.
Se movermos mentalmente todos os *'s das linhas F (6) para F (2) para a linha F (1), veremos que as linhas F (1) e F (0) agora têm o mesmo comprimento. O que significa que o total de vezes que F () é chamado quando n = 6 é 2x32 = 64 = 2 ^ 6.
Agora, em termos de complexidade:
fonte
Há uma discussão muito boa sobre esse problema específico no MIT . Na página 5, eles afirmam que, se você assumir que uma adição requer uma unidade computacional, o tempo necessário para calcular Fib (N) está intimamente relacionado ao resultado de Fib (N).
Como resultado, você pode pular diretamente para uma aproximação muito próxima da série Fibonacci:
e dizer, portanto, que o pior desempenho do algoritmo ingênuo é
PS: Há uma discussão sobre a expressão de forma fechada do número N-ésimo de Fibonacci na Wikipedia, se você quiser obter mais informações.
fonte
Você pode expandi-lo e ter uma visulização
fonte
<
no final? Como você conseguiuT(n-1) + T(n-1)
?T(n-1) > T(n-2)
Então eu posso mudarT(n-2)
e colocarT(n-1)
. Eu só vou conseguir um limite mais alto que ainda é válido paraT(n-1) + T(n-2)
É delimitada na extremidade inferior por
2^(n/2)
e na extremidade superior por 2 ^ n (como observado em outros comentários). E um fato interessante dessa implementação recursiva é que ela possui um forte limite assintótico da própria Fib (n). Esses fatos podem ser resumidos:O limite apertado pode ser reduzido ainda mais usando sua forma fechada, se você quiser.
fonte
As respostas da prova são boas, mas eu sempre tenho que fazer algumas iterações manualmente para realmente me convencer. Então, desenhei uma pequena árvore de chamada no meu quadro branco e comecei a contar os nós. Divido minhas contagens em nós totais, nós folha e nós interiores. Aqui está o que eu tenho:
O que salta imediatamente é que o número de nós foliares é
fib(n)
. O que levou mais algumas iterações para perceber é que o número de nós internos éfib(n) - 1
. Portanto, o número total de nós é2 * fib(n) - 1
.Como você reduz os coeficientes ao classificar a complexidade computacional, a resposta final é
θ(fib(n))
.fonte
1
de um únicoFib(n)
tempo de acumulador , mas é interessante que ainda seja exatamenteθ(Fib(n))
.0
, no entanto: os casos base de recursão são0
e1
, porque o fazemFib(n-1) + Fib(n-2)
. Então, provavelmente a3 * Fib(n) - 2
partir dessa resposta só-link é mais preciso para o número total de nós, não2 * Fib(n) - 1
.A complexidade do tempo do algoritmo recursivo pode ser melhor estimada através do desenho da árvore de recursão. Nesse caso, a relação de recorrência para o desenho da árvore de recursão seria T (n) = T (n-1) + T (n-2) + O (1) observe que cada passo leva O (1), o que significa tempo constante, pois ele faz apenas uma comparação para verificar o valor de n em se o bloco.
Aqui vamos dizer que cada nível da árvore acima é indicado por i, portanto,
digamos que, com um valor particular de i, a árvore termina, esse caso seria quando ni = 1, daí i = n-1, significando que a altura da árvore é n-1. Agora vamos ver quanto trabalho é feito para cada uma das n camadas da árvore. Observe que cada etapa leva O (1) tempo, conforme declarado na relação de recorrência.
já que i = n-1 é a altura da árvore, o trabalho realizado em cada nível será
Portanto, o trabalho total realizado somará o trabalho realizado em cada nível, portanto, será 2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 2 ^ 3 ... + 2 ^ (n-1) desde que i = n-1. Por séries geométricas, essa soma é 2 ^ n, portanto, a complexidade do tempo total aqui é O (2 ^ n)
fonte
Bem, de acordo com mim, é
O(2^n)
que nessa função apenas a recursão está demorando um tempo considerável (dividir e conquistar). Vemos que, a função acima continuará em uma árvore até que as folhas se aproximem quando atingirmos o nívelF(n-(n-1))
ieF(1)
. Então, aqui, quando anotamos a complexidade de tempo encontrada em cada profundidade da árvore, a série de somatórios é:essa é a ordem de
2^n [ O(2^n) ]
.fonte
A versão ingênua de recursão de Fibonacci é exponencial por design devido à repetição no cálculo:
Na raiz, você está computando:
F (n) depende de F (n-1) e F (n-2)
F (n-1) depende de F (n-2) novamente e F (n-3)
F (n-2) depende de F (n-3) novamente e F (n-4)
em cada chamada recursiva de nível 2 que está desperdiçando muitos dados no cálculo, a função de tempo terá a seguinte aparência:
T (n) = T (n-1) + T (n-2) + C, com constante C
T (n-1) = T (n-2) + T (n-3)> T (n-2) então
T (n)> 2 * T (n-2)
...
T (n)> 2 ^ (n / 2) * T (1) = O (2 ^ (n / 2))
Este é apenas um limite inferior que, para os fins de sua análise, deve ser suficiente, mas a função em tempo real é um fator de constante pela mesma fórmula de Fibonacci e pela forma fechada é conhecida por ser exponencial da proporção áurea.
Além disso, você pode encontrar versões otimizadas do Fibonacci usando programação dinâmica como esta:
Isso é otimizado e executa apenas n etapas, mas também é exponencial.
As funções de custo são definidas de Tamanho da entrada até o número de etapas para resolver o problema. Quando você vê a versão dinâmica do Fibonacci ( n etapas para calcular a tabela) ou o algoritmo mais fácil para saber se um número é primo ( sqrt (n) para analisar os divisores válidos do número)). você pode pensar que esses algoritmos são O (n) ou O (sqrt (n)), mas isso simplesmente não é verdade pelo seguinte motivo: A entrada para o seu algoritmo é um número: n , usando a notação binária, o tamanho da entrada para um inteiro n é log2 (n) , fazendo uma alteração variável de
vamos descobrir o número de etapas em função do tamanho da entrada
então o custo do seu algoritmo em função do tamanho da entrada é:
e é por isso que o custo é exponencial.
fonte
É simples de calcular diagramando as chamadas de função. Simplesmente adicione as chamadas de função para cada valor de n e veja como o número cresce.
O Big O é O (Z ^ n) onde Z é a proporção áurea ou cerca de 1,62.
Os números de Leonardo e de Fibonacci se aproximam dessa proporção à medida que aumentamos n.
Ao contrário de outras questões do Big O, não há variabilidade na entrada e o algoritmo e a implementação do algoritmo estão claramente definidos.
Não há necessidade de um monte de matemática complexa. Simplesmente faça um diagrama das chamadas de função abaixo e ajuste uma função aos números.
Ou, se você estiver familiarizado com a proporção áurea, a reconhecerá como tal.
Essa resposta é mais correta do que a resposta aceita, que afirma que se aproximará de f (n) = 2 ^ n. Isso nunca será. Abordará f (n) = golden_ratio ^ n.
fonte
http://www.ics.uci.edu/~eppstein/161/960109.html
tempo (n) = 3F (n) - 2
fonte