web-dev-qa-db-fra.com

Récurrence vs itération (séquence de Fibonacci)

J'ai deux méthodes différentes, l'une calcule la séquence de Fibonacci à l'élément nième en utilisant l'itération et l'autre fait la même chose en utilisant la méthode récursive .


L'exemple de programme ressemble à ceci:

import Java.util.Scanner;

public class recursionVsIteration {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);

        //nth element input
        System.out.print("Enter the last element of Fibonacci sequence: ");
        int n = sc.nextInt();

        //Print out iteration method
        System.out.println("Fibonacci iteration:");
        long start = System.currentTimeMillis();
        System.out.printf("Fibonacci sequence(element at index %d) = %d \n", n, fibIteration(n));
        System.out.printf("Time: %d ms\n", System.currentTimeMillis() - start);

        //Print out recursive method
        System.out.println("Fibonacci recursion:");
        start = System.currentTimeMillis();
        System.out.printf("Fibonacci sequence(element at index %d) = %d \n", n, fibRecursion(n));
        System.out.printf("Time: %d ms\n", System.currentTimeMillis() - start);
    }

    //Iteration method
    static int fibIteration(int n) {
        int x = 0, y = 1, z = 1;
        for (int i = 0; i < n; i++) {
            x = y;
            y = z;
            z = x + y;
        }
        return x;
    }

    //Recursive method
    static int fibRecursion(int  n) {
        if ((n == 1) || (n == 0)) {
            return n;
        }
        return fibRecursion(n - 1) + fibRecursion(n - 2);
    }
}

J'essayais de savoir quelle méthode est la plus rapide. J'en suis venu à la conclusion que la récursivité est plus rapide pour la plus petite quantité de nombres, mais à mesure que la valeur de nième élément augmente, la récursivité devient plus lente et l'itération devient plus rapide. Voici les trois résultats différents pour trois différents n :


Exemple # 1 (n = 10)

Enter the last element of Fibonacci sequence: 10
Fibonacci iteration:
Fibonacci sequence(element at index 10) = 55 
Time: 5 ms
Fibonacci recursion:
Fibonacci sequence(element at index 10) = 55 
Time: 0 ms

Exemple # 2 (n = 20)

Enter the last element of Fibonacci sequence: 20
Fibonacci iteration:
Fibonacci sequence(element at index 20) = 6765 
Time: 4 ms
Fibonacci recursion:
Fibonacci sequence(element at index 20) = 6765 
Time: 2 ms

Exemple # 3 (n = 30)

Enter the last element of Fibonacci sequence: 30
Fibonacci iteration:
Fibonacci sequence(element at index 30) = 832040
Time: 4 ms
Fibonacci recursion:
Fibonacci sequence(element at index 30) = 832040
Time: 15 ms

Ce que je veux vraiment savoir, c'est pourquoi tout d'un coup, l'itération est devenue plus rapide et la récursion est devenue plus lente. Je suis désolé si j'ai raté une réponse évidente à cette question, mais je suis encore nouveau dans la programmation, je ne comprends vraiment pas ce qui se passe derrière cela et j'aimerais savoir. Veuillez fournir une bonne explication ou me diriger dans la bonne direction afin que je puisse trouver la réponse moi-même. De plus, si ce n'est pas un bon moyen de tester quelle méthode est la plus rapide, faites-le moi savoir et proposez-moi une méthode différente.

Merci d'avance!

21
msmolcic

Par souci de concision, Soit F(x) être le Fibonacci récursif

F(10) = F(9)                      + F(8)
F(10) = F(8)        + F(7)        + F(7) + F(6)
F(10) = F(7) + F(6) + F(6) + F(5) + 4 more calls.
....

Vous appelez donc F(8) deux fois, F(7) 3 fois, F(6) 5 fois) , F(5) 7 fois .. et ainsi de suite

Donc, avec des entrées plus grandes, l'arbre devient de plus en plus gros.

14
Chris Cudmore

Cet article fait une comparaison entre la récursivité et l'itération et couvre leur application sur la génération de nombres de fibonacci.

Comme indiqué dans l'article,

La raison de ces performances médiocres est la forte poussée des registres dans le mauvais niveau de chaque appel récursif.

ce qui signifie essentiellement qu'il y a plus de frais généraux dans la méthode récursive.

Jetez également un œil à Mémorisation

6
Nima Vaziri

Lorsque vous effectuez l'implémentation récursive de l'algorithme fibonacci, vous ajoutez des appels redondants en recalculant les mêmes valeurs encore et encore.

fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
fib(3) = fib(2) + fib(1)

Notez que fib(2) sera calculé de manière redondante à la fois pour fib(4) et pour fib(3). Cependant, cela peut être surmonté par une technique appelée mémorisation , qui améliore l'efficacité des fibonacci récursifs en stockant les valeurs, que vous avez calculées une fois. D'autres appels de fib(x) pour des valeurs connues peuvent être remplacés par une simple recherche, éliminant ainsi le besoin d'appels récursifs supplémentaires.

C'est la principale différence entre les approches itératives et récursives, si vous êtes intéressé, il existe également d'autres, plus algorithmes efficaces de calcul des nombres de fibonacci.

6
Warlord

Pourquoi la récursivité est-elle plus lente?

Lorsque vous appelez à nouveau votre fonction elle-même (comme récursivité), le compilateur alloue nouvel enregistrement d'activation (pensez simplement comme une pile ordinaire) pour cette nouvelle fonction. Cette pile est utilisée pour conserver vos états, variables et adresses. Le compilateur crée une pile pour chaque fonction et ce processus de création se poursuit jusqu'à ce que le scénario de base soit atteint. Ainsi, lorsque la taille des données devient plus grande, compilateur a besoin d'un grand segment de pile pour calculer l'ensemble du processus. Le calcul et la gestion de ces enregistrements sont également comptabilisés au cours de ce processus.

De plus, en récursivité, le segment de pile est levé pendant l'exécution. Le compilateur ne sait pas combien de mémoire sera occupée pendant la compilation.

C'est pourquoi si vous ne gérez pas correctement votre cas de base, vous obtiendrez StackOverflow exception :).

4
Saiful Azad

Si quelqu'un est intéressé par une fonction itérative avec tableau:

public static void fibonacci(int y)
{
    int[] a = new int[y+1];
    a[0] = 0;
    a[1] = 1;
    System.out.println("Step 0: 0");
    System.out.println("Step 1: 1");
    for(int i=2; i<=y; i++){
        a[i] = a[i-1] + a[i-2];
        System.out.println("Step "+i+": "+a[i]);
    }
    System.out.println("Array size --> "+a.length);
}

Cette solution se bloque pour la valeur d'entrée 0.

Raison: le tableau a sera initialisé 0+1=1 mais l'affectation consécutive de a[1] entraînera une exception d'index hors limites.

Soit ajoutez une instruction if qui renvoie 0 sur y=0 ou initialiser le tableau par y+2, ce qui gâchera 1 int mais toujours d'espace constant et ne change pas grand O.

2
Alan Meile

Chaque fois que vous recherchez le temps nécessaire pour terminer un algorithme particulier, il vaut mieux toujours opter pour la complexité du temps.

Évaluez la complexité du temps sur le papier en termes de O (quelque chose).

En comparant les deux approches ci-dessus, la complexité temporelle de l'approche itérative est O(n) tandis que celle de l'approche récursive est O (2 ^ n).

Essayons de trouver la complexité temporelle de fib(4)

Approche itérative, la boucle est évaluée 4 fois, sa complexité temporelle est donc O(n).

Approche récursive,

                               fib(4)

             fib(3)              +               fib(2)

      fib(2)   +    fib(1)           fib(1)     +       fib(0)

fib(1)  +  fib(0)

donc fib () est appelé 9 fois, ce qui est légèrement inférieur à 2 ^ n lorsque la valeur de n est grande, voire petite également (rappelez-vous que BigOh (O) s'occupe de upper bound).

En conséquence, nous pouvons dire que l'approche itérative évalue dans polynomial time, alors que récursif évalue dans exponential time

2
Varunnuevothoughts

En utilisant la récursion comme vous l'avez fait, la complexité temporelle est O(fib(n)) ce qui est très cher. La méthode itérative est O(n) Cela ne s'affiche pas car a) vos tests sont très courts, le code ne sera même pas compilé b) vous avez utilisé de très petits nombres.

Les deux exemples deviendront plus rapides plus vous les exécuterez. Une fois qu'une boucle ou une méthode a été appelée 10 000 fois, elle doit être compilée en code natif.

2
Peter Lawrey

L'approche récursive que vous utilisez n'est pas efficace. Je vous suggère d'utiliser la récursivité de la queue. Contrairement à votre approche, la récursion de queue ne conserve qu'un seul appel de fonction dans la pile à un moment donné.

public static int tailFib(int n) {
    if (n <= 1) {
        return n;
    }
    return tailFib(0, 1, n);
}

private static int tailFib(int a, int b, int count) {
    if(count <= 0) {
        return a;
    }
    return tailFib(b, a+b, count-1);
}

public static void main(String[] args)  throws Exception{
    for (int i = 0; i <10; i++){
        System.out.println(tailFib(i));
    }
}
1
gorros

Je préfère utiliser une solution mathématique en utilisant le nombre d'or. prendre plaisir

private static final double GOLDEN_NUMBER = 1.618d;

public long fibonacci(int n) {
    double sqrt = Math.sqrt(5);

    double result = Math.pow(GOLDEN_NUMBER, n);

    result = result - Math.pow(1d - GOLDEN_NUMBER, n);

    result = Math.round(result / sqrt);

    return Double.valueOf(result).longValue();
}
1
Aldo Infanzon

J'ai une solution récursive que vous où les valeurs calculées sont stockées pour éviter les calculs inutiles supplémentaires. Le code est fourni ci-dessous,

public static int fibonacci(int n) {

        if(n <=  0) return 0;
        if(n == 1) return 1;

        int[] arr = new int[n+1];

        // this is faster than using Array
        // List<Integer> lis = new ArrayList<>(Collections.nCopies(n+1, 0));

        arr[0] = 0;
        arr[1] = 1; 

        return fiboHelper(n, arr);
    }

    public static int fiboHelper(int n, int[] arr){

        if(n <= 0) {
            return arr[0];
        }

        else if(n == 1) {
            return arr[1];
        }

        else {

            if( arr[n-1] != 0 && (arr[n-2] != 0 || (arr[n-2] == 0 && n-2 == 0))){    
                return arr[n] = arr[n-1] + arr[n-2]; 
            }

            else if (arr[n-1] == 0 && arr[n-2] != 0 ){
                return arr[n] = fiboHelper(n-1, arr) + arr[n-2]; 
            }

            else {
                return  arr[n] = fiboHelper(n-2, arr) + fiboHelper(n-1, arr );
            } 

        }             
    }
1
Arefe