web-dev-qa-db-fra.com

Pourquoi la séquence de Fibonacci est-elle grande? O(2^n) au lieu de O (logn)?

J'ai pris des mathématiques discrètes (dans lesquelles j'ai appris sur le théorème principal, Big Theta/Omega/O) il y a quelque temps et il me semble avoir oublié la différence entre O(logn) et O (2 ^ n) ( pas dans le sens théorique de Big Oh). Je comprends généralement que les algorithmes tels que la fusion et le tri rapide sont O(nlogn) parce qu'ils divisent à plusieurs reprises le tableau d'entrée initial en sous-tableaux jusqu'à ce que chaque sous-tableau ait la taille 1 avant de revenir en arrière dans l'arborescence, ce qui donne une récursivité. arbre qui est de hauteur logn + 1. Mais si vous calculez la hauteur d'un arbre récursif en utilisant n/b ^ x = 1 (lorsque la taille du sous-problème est devenue 1 comme indiqué dans une réponse ici ), il semble que vous obtenez toujours que la hauteur de l’arbre est log (n).

Si vous résolvez la séquence de Fibonacci en utilisant la récursivité, je pense que vous obtiendrez également un arbre de taille logn, mais pour une raison quelconque, le Big O de l'algorithme est O (2 ^ n). Je pensais que la différence était peut-être due au fait que vous deviez vous souvenir de tous les nombres de fib de chaque sous-problème pour obtenir le nombre de fib réel, ce qui signifie que la valeur de chaque nœud doit être rappelée, mais il semble que, dans le tri par fusion, la valeur de chaque nœud doit également être utilisé (ou au moins trié). Cela ne ressemble toutefois pas à la recherche binaire, où vous ne visitez que certains nœuds en vous basant sur des comparaisons effectuées à chaque niveau de l’arbre, je pense donc que c’est de là que vient la confusion.

Donc précisément, qu'est-ce qui fait que la séquence de Fibonacci a une complexité temporelle différente de celle d'algorithmes comme le tri par fusion/fusion?

6
loremIpsum1771

Les autres réponses sont correctes, mais n'indiquez pas clairement. D'où vient la grande différence entre l'algorithme de Fibonacci et les algorithmes diviser pour régner? En effet, la forme de l’arbre de récurrence pour les deux classes de fonctions est la même: c’est un arbre binaire.

Le truc à comprendre est en réalité très simple: considérons le size de l’arbre de récursivité en fonction de la taille d’entrée n.

Rappelons quelques faits de base sur les arbres binaires first:

  • Le nombre de feuilles n est un arbre binaire est égal au nombre de nœuds non-feuilles plus un. La taille d'un arbre binaire est donc 2n-1.
  • Dans un arbre binaire parfait, tous les nœuds non-feuilles ont deux enfants.
  • La hauteur h pour un arbre binaire parfait à n feuilles est égale à log(n), pour un arbre binaire aléatoire: h = O(log(n)) et pour un arbre binaire dégénéré h = n-1.

Intuitivement:

  • Pour trier un tableau d'éléments n avec un algorithme récursif, l'arbre de récursivité a n leaves . Il s’ensuit que la largeur de l’arbre estn, la hauteur de l’arbre estO(log(n))en moyenne etO(n)dans le pire des cas.

  • Pour calculer un élément de séquence Fibonacci k avec l'algorithme récursif, l'arbre de récursion a k levels (pour savoir pourquoi, considérons que fib(k) appelle fib(k-1), qui appelle fib(k-2), etc.). Il en résulte que hauteur de l’arbre estk. Pour estimer une limite inférieure de la largeur et du nombre de nœuds dans l’arbre de récursivité, supposons que, puisque fib(k) appelle également fib(k-2), il existe donc un parfait arbre binaire de hauteur k/2 faisant partie de l’arbre de récursivité. Si extrait, ce sous-arbre parfait aurait 2k/2 nœuds feuilles. Donc, la largeur de l'arbre de récurrence est au moins O(2^{k/2}) ou, de manière équivalente,2^O(k).

La différence cruciale est que:

  • pour les algorithmes diviser pour régner, la taille d'entrée est le width de l'arborescence binaire.
  • pour l'algorithme de Fibonnaci, la taille en entrée est la hauteur de l'arbre.

Par conséquent, le nombre de nœuds dans l’arborescence est O(n) dans le premier cas, mais 2^O(n) dans le second. L'arbre de Fibonacci est beaucoup plus grand par rapport à la taille d'entrée.

Vous mentionnez théorème principal ; Cependant, le théorème ne peut pas être appliqué à l'analyse de la complexité de Fibonacci car il ne s'applique qu'aux algorithmes où l'entrée est en fait divisée à chaque niveau de récursivité. Fibonacci ne pas divise l'entrée; En fait, les fonctions de niveau i produisent presque deux fois plus d'entrées pour le niveau suivant i+1.

13
kfx

Pour répondre au cœur de la question, c’est "pourquoi Fibonacci et non Mergesort", vous devez vous concentrer sur cette différence cruciale:

  • L'arbre que vous obtenez de Mergesort a N éléments pour chaque niveau et il existe des niveaux de log (n).
  • L'arbre que vous obtenez de Fibonacci a N niveaux en raison de la présence de F(n-1) dans la formule pour F (N) et le nombre d'éléments pour chaque niveau peut varier considérablement très bas (près de la racine ou près des feuilles les plus basses) ou très haut. Ceci, bien sûr, est dû au calcul répété des mêmes valeurs.

Pour voir ce que je veux dire par "calcul répété", regardez l'arbre pour le calcul de F (6):

 fib(6) computation tree
Photo de l'arbre de Fibonacci de: http://composingprograms.com/pages/28-efficiency.html

Combien de fois voyez-vous que F(3) a été calculé?

4
wil93

Considérez l'implémentation suivante

int fib(int n)
{
    if(n < 2)
      return n;
    return fib(n-1) + fib(n-2)
}

Notons T(n) le nombre d'opérations que fib effectue pour calculer fib(n). Étant donné que fib(n) appelle fib(n-1) et fib(n-2), cela signifie que T(n) est au moins T(n-1) + T(n-2). Cela signifie que T(n) > fib(n). Il existe une formule directe de fib(n) qui est une constante du pouvoir de n. Par conséquent, T(n) est au moins exponentiel. QED.

3
Armen Tsirunyan

Avec l’algo récursif, vous avez environ 2 ^ N opérations (additions) pour fibonacci (N). Ensuite c'est O (2 ^ N).

Avec un cache (mémorisation), vous avez environ N opérations, puis c'est O (N).

Les algorithmes avec complexité O (N log N) sont souvent une conjonction d'itération sur chaque élément (O (N)), de fractionnement de recurse et de fusion ... Split by 2 => vous enregistrez des récursions de N. 

Si j'ai bien compris, l'erreur dans votre raisonnement est que l'utilisation d'une implémentation récursive pour évaluer f(n)f désigne la séquence de Fibonacci, la taille d'entrée est réduite d'un facteur 2 (ou d'un autre facteur), ce qui n'est pas le cas. Chaque appel (à l'exception des «cas de base» 0 et 1) utilise exactement 2 appels récursifs, car il n'est pas possible de réutiliser des valeurs calculées précédemment. À la lumière de la présentation du théorème maître sur Wikipedia , la récurrence

f(n) = f (n-1) + f(n-2)

est un cas pour lequel le théorème maître ne peut pas être appliqué.

2
Codor

La complexité du temps de tri de la fusion est O (n log (n)). Le meilleur cas de tri rapide est O (n log (n)), le pire des cas O (n ^ 2).

Les autres réponses expliquent pourquoi Fibonacci naïf récursif est O (2 ^ n).

Si vous lisez que Fibonacci (n) peut être O (log (n)), cela est possible si vous le calculez par itération et quadrature répétée en utilisant la méthode de la matrice ou la méthode de la séquence de lucas. Exemple de code pour la méthode de séquence lucas (notez que n est divisé par 2 sur chaque boucle):

/* lucas sequence method */
int fib(int n) {
    int a, b, p, q, qq, aq;
    a = q = 1;
    b = p = 0;
    while(1) {
        if(n & 1) {
            aq = a*q;
            a = b*q + aq + a*p;
            b = b*p + aq;
        }
        n /= 2;
        if(n == 0)
            break;
        qq = q*q;
        q = 2*p*q + qq;
        p = p*p + qq;
    }
    return b;
}
2
rcgldr