赛派号

电瓶车控制器换一下大概多少钱 Un guide Python de la séquence de Fibonacci

Un guide Python de la séquence de Fibonacci

La Séquence de Fibonacci est une séquence assez célèbre de nombres entiers. La séquence apparaît naturellement dans de nombreux problèmes et a une belle définition récursive. Apprendre à le générer est une étape essentielle dans le parcours du programmeur pragmatique vers la maîtrise de la récursivité. Dans ce didacticiel, vous vous concentrerez sur l'apprentissage de ce qu'est la séquence de Fibonacci et sur la façon de la générer à l'aide de Python.

Dans ce didacticiel, vous apprendrez à :

Générez la séquence de Fibonacci à l'aide d'un algorithme récursifOptimisez l'algorithme récursif de Fibonacci en utilisant la mémoisationGénérez la séquence de Fibonacci à l'aide d'un algorithme itératif

Pour tirer le meilleur parti de ce didacticiel, vous devez connaître les bases de la notation Big O, de la programmation orientée objet, des méthodes spéciales de Python, des instructions conditionnelles, des fonctions et des structures de données de base telles que les listes, les files d'attente et les piles. Avoir une certaine familiarité ec ces concepts vous aidera grandement à comprendre les nouveaux que vous allez explorer dans ce didacticiel.

Allons-y !

Premiers pas ec la séquence de Fibonacci

Léonard de Fibonacci était un mathématicien italien qui a su rapidement répondre à cette question posée par l'empereur Frédéric II de Souabe : « Combien de couples de lapins obtient-on en un an, sans compter les cas de décès, en supposant que chaque couple donne naissance à un autre ? couple chaque mois et que les couples les plus jeunes sont capables de se reproduire dès le deuxième mois de leur vie ?

La réponse était la séquence suivante :

Le modèle commence après les deux premiers nombres, 0 et 1, où chaque nombre de la séquence est toujours la somme des deux nombres qui le précèdent. Les mathématiciens indiens connaissaient cette séquence depuis le VIe siècle et Fibonacci l'a exploitée pour calculer la croissance des populations de lapins.

F(n) est utilisé pour indiquer le nombre de couples de lapins présents au mois n, la séquence peut donc s'exprimer ainsi :

En terminologie mathématique, on appellerait cela une relation de récurrence, ce qui signifie que chaque terme de la séquence (au-delà de 0 et 1) est fonction des termes précédents.

Il existe également une version de la séquence où les deux premiers nombres sont tous deux 1, comme ceci :

Dans cette version alternative, F(0) vaut toujours implicitement 0, mais vous partez de F(1) et F(2) à la place. L’algorithme reste le même car vous additionnez toujours les deux nombres précédents pour obtenir le nombre suivant dans la séquence.

Pour les besoins de ce didacticiel, vous utiliserez la version de la séquence qui commence par 0.

Examen de la récursion derrière la séquence de Fibonacci

La génération de la séquence de Fibonacci est un problème récursif classique. La Récursion se produit lorsqu'une fonction se réfère à elle-même pour décomposer le problème qu'elle essaie de résoudre. Dans chaque appel de fonction, le problème devient plus petit jusqu'à ce qu'il atteigne un cas de base, après quoi il renverra ensuite le résultat à chaque appelant intermédiaire jusqu'à ce qu'il renvoie le résultat final à l'appelant d'origine.

Si vous vouliez calculer le nombre de Fibonacci F(5), vous devrez calculer ses prédécesseurs, F(4) et F( 3), d'abord. Et pour calculer F(4) et F(3), vous devrez calculer leurs prédécesseurs. La décomposition de F(5) en sous-problèmes plus petits ressemblerait à ceci :

Chaque fois que la fonction de Fibonacci est appelée, elle est divisée en deux sous-problèmes plus petits car c'est ainsi que vous ez défini la relation de récurrence. Lorsqu'il atteint le cas de base de F(0) ou F(1), il peut enfin renvoyer un résultat à son appelant.

Afin de calculer le cinquième nombre de la séquence de Fibonacci, vous résolvez des problèmes plus petits mais identiques jusqu'à atteindre les cas de base, où vous pouvez commencer à renvoyer un résultat :

Les sous-problèmes colorés sur ce diagramme représentent des solutions répétitives au même problème. Si vous remontez plus haut dans l’arborescence, vous trouverez dantage de ces solutions répétitives. Cela signifie que pour générer une séquence de Fibonacci de manière récursive, vous devez calculer de nombreux nombres intermédiaires encore et encore. C’est l’un des problèmes fondamentaux de l’approche récursive de la séquence de Fibonacci.

Générer la séquence de Fibonacci de manière récursive en Python

L'algorithme le plus courant et le plus minimal pour générer la séquence de Fibonacci nécessite que vous codiez une fonction récursive qui s'appelle autant de fois que nécessaire jusqu'à ce qu'elle calcule le nombre de Fibonacci souhaité :

>>> def fibonacci_of(n): ... if n in {0, 1}: # Base case ... return n ... return fibonacci_of(n - 1) + fibonacci_of(n - 2) # Recursive case ... >>> [fibonacci_of(n) for n in range(15)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Dans fibonacci_of(), vous vérifiez d'abord le cas de base. Vous renvoyez ensuite la somme des valeurs résultant de l’appel de la fonction ec les deux valeurs précédentes de n. La compréhension de la liste à la fin de l'exemple génère une séquence de Fibonacci ec les quinze premiers nombres.

Cette fonction tombe rapidement dans le problème de répétition que vous ez vu dans la section ci-dessus. Le calcul devient de plus en plus coûteux à mesure que n grandit. Le temps requis augmente de façon exponentielle car la fonction calcule encore et encore de nombreux sous-problèmes identiques.

Remarque : N'essayez pas cette fonction chez vous ec un nombre supérieur à 50. Selon votre matériel, vous pourriez attendre longtemps ant de voir le résultat, si vous arrivez au bout.

Pour calculer F(5), fibonacci_of() doit s'appeler quinze fois. Pour calculer F(n), la profondeur maximale de l'arbre des appels est n, et comme chaque appel de fonction produit deux appels de fonction supplémentaires, le la complexité temporelle de cette fonction récursive est O(2n).

La plupart de ces appels sont redondants car vous ez déjà calculé leurs résultats. F(3) apparaît deux fois et F(2) apparaît trois fois. F(1) et F(0) sont des cas de base, vous pouvez donc les appeler plusieurs fois. Vous souhaiterez peut-être éviter cette répétition inutile, ce qui fait l’objet des sections suivantes.

Optimisation de l'algorithme récursif pour la séquence de Fibonacci

Il existe au moins deux techniques que vous pouvez utiliser pour rendre l'algorithme permettant de générer la séquence de Fibonacci plus efficace, en d'autres termes, pour que le calcul prenne moins de temps. Ces techniques garantissent que vous ne calculez pas les mêmes valeurs encore et encore, ce qui rendait l’algorithme d’origine si inefficace. On les appelle mémorisation et itération.

Mémorisation de l'algorithme récursif

Comme vous l'ez vu dans le code ci-dessus, la fonction Fibonacci s'appelle plusieurs fois ec la même entrée. Au lieu d'oir un nouvel appel à chaque fois, vous pouvez stocker les résultats des appels précédents dans quelque chose comme une mémoire cache. Vous pouvez utiliser une liste Python pour stocker les résultats des calculs précédents. Cette technique est appelée mémoisation.

La mémorisation accélère l'exécution de fonctions récursives coûteuses en stockant les résultats précédemment calculés dans un cache. De cette façon, lorsque la même entrée se reproduit, la fonction n'a qu'à rechercher le résultat correspondant et à le renvoyer sans oir à relancer le calcul. Vous pouvez qualifier ces résultats de mis en cache ou mémorisés :

Avec la mémorisation, il vous suffit de parcourir l'arbre d'appels de profondeur n une fois après votre retour du cas de base, en récupérant toutes les valeurs calculées précédemment surlignées en jaune, F (2) et F(3), du cache plus tôt.

Le chemin orange montre qu'aucune entrée de la fonction Fibonacci n'est appelée plus d'une fois. Cela réduit considérablement la complexité temporelle de l'algorithme de O exponentiel (2n) à O linéaire (n).

Même pour les cas de base, vous pouvez remplacer l'appel de F(0) et F(1) par la simple récupération des valeurs directement depuis le cache aux indices 0 et 1, de sorte que vous finissez par appeler la fonction seulement six fois au lieu de quinze !

Voici une traduction possible de cette optimisation en code Python :

>>> cache = {0: 0, 1: 1} >>> def fibonacci_of(n): ... if n in cache: # Base case ... return cache[n] ... # Compute and cache the Fibonacci number ... cache[n] = fibonacci_of(n - 1) + fibonacci_of(n - 2) # Recursive case ... return cache[n] >>> [fibonacci_of(n) for n in range(15)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Dans cet exemple, vous utilisez un dictionnaire Python pour mettre en cache les nombres de Fibonacci calculés. Initialement, cache contient les valeurs de départ de la séquence de Fibonacci, 0 et 1. Dans la fonction, vous vérifiez d'abord si le nombre de Fibonacci pour la valeur d'entrée actuelle de n est déjà dans le cache. Si tel est le cas, vous renvoyez le numéro disponible.

S'il n'y a pas de numéro de Fibonacci pour la valeur actuelle de n, alors vous le calculez en appelant fibonacci_of() de manière récursive et en mettant à jour le cache. La dernière étape consiste à renvoyer le numéro de Fibonacci demandé.

Explorer un algorithme itératif

Et si vous n’iez même pas besoin d’appeler la fonction récursive de Fibonacci ? Vous pouvez en fait utiliser un algorithme itératif pour calculer le nombre à la position n dans la séquence de Fibonacci.

Vous sez que les deux premiers nombres de la séquence sont 0 et 1 et que chaque nombre suivant de la séquence est la somme de ses deux prédécesseurs précédents. Ainsi, vous pouvez simplement créer une boucle qui additionne les deux nombres précédents, n - 1 et n - 2, ensemble pour trouver le nombre à la position n dans la séquence.

Les nombres violets en gras dans le diagramme ci-dessous représentent les nouveaux nombres qui doivent être calculés et ajoutés au cache à chaque étape itérative :

Pour calculer le nombre de Fibonacci à la position n, vous stockez les deux premiers nombres de la séquence, 0 et 1, dans le cache. Ensuite, calculez les nombres suivants consécutivement jusqu'à ce que vous puissiez renvoyer cache[n].

Générer la séquence de Fibonacci en Python

Maintenant que vous connaissez les bases de la génération de la séquence de Fibonacci, il est temps d’approfondir et d’explorer dantage les différentes façons d’implémenter l’algorithme sous-jacent en Python. Dans les sections suivantes, vous découvrirez comment implémenter différents algorithmes pour générer la séquence de Fibonacci en utilisant la récursivité, la programmation orientée objet Python et également l'itération.

Utiliser la récursivité et une classe Python

Votre première approche pour générer la séquence de Fibonacci utilisera une classe Python et une récursivité. Un antage de l'utilisation de la classe par rapport à la fonction récursive mémorisée que vous ez vue précédemment est qu'une classe conserve l'état et le comportement (encapsulation) ensemble au sein du même objet. Dans l'exemple de fonction, cependant, cache est un objet complètement distinct, vous n'en ez donc aucun contrôle.

Vous trouverez ci-dessous le code qui implémente votre solution basée sur les classes :

# fibonacci_class.py class Fibonacci: def __init__(self): self.cache = [0, 1] def __call__(self, n): # Validate the value of n if not (isinstance(n, int) and n >= 0): raise ValueError(f'Positive integer number expected, got "{n}"') # Check for computed Fibonacci numbers if n < len(self.cache): return self.cache[n] else: # Compute and cache the requested Fibonacci number fib_number = self(n - 1) + self(n - 2) self.cache.append(fib_number) return self.cache[n]

Voici un aperçu de ce qui se passe dans le code :

La Ligne 3 définit la classe Fibonacci.

La Ligne 4 définit l'initialiseur de classe, .__init__(). C'est une méthode spéciale que vous pouvez utiliser pour initialiser vos instances de classe. Les méthodes spéciales sont parfois appelées méthodes dunder, abréviation de méthodes de double trait de soulignement.

La Ligne 5 crée l'attribut d'instance .cache, ce qui signifie que chaque fois que vous créez un objet Fibonacci, il y aura un cache pour celui-ci. Cet attribut contient initialement les premiers nombres de la séquence de Fibonacci.

La Ligne 7 définit une autre méthode spéciale, .__call__(). Cette méthode transforme les instances de Fibonacci en objets appelables.

Les lignes 9 et 10 valident la valeur de n à l'aide d'une instruction conditionnelle. Si n n'est pas un nombre entier positif, alors la méthode génère une ValueError.

La Ligne 13 définit une instruction conditionnelle pour vérifier les nombres de Fibonacci qui ont déjà été calculés et sont disponibles dans .cache. Si le numéro à l'index n est déjà dans .cache, alors la ligne 14 le renvoie. Sinon, la ligne 17 calcule le nombre et la ligne 18 l'ajoute à .cache afin que vous n'ayez pas à le calculer à nouveau.

La Ligne 20 renvoie le nombre de Fibonacci demandé.

Pour essayer ce code, continuez et enregistrez-le dans fibonacci_class.py. Exécutez ensuite ce code dans votre shell interactif :

>>> from fibonacci_class import Fibonacci >>> fibonacci_of = Fibonacci() >>> fibonacci_of(5) 5 >>> fibonacci_of(6) 8 >>> fibonacci_of(7) 13 >>> [fibonacci_of(n) for n in range(15)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Ici, vous créez puis appelez une instance de la classe Fibonacci nommée fibonacci_of. Le premier appel utilise 5 comme argument et renvoie 5, qui est le sixième nombre de Fibonacci car vous utilisez des indices de base zéro.

Cette implémentation de l'algorithme de séquence de Fibonacci est assez efficace. Une fois que vous ez une instance de la classe, l'attribut .cache contient les nombres déjà calculés d'un appel à l'autre.

Visualisation de l'algorithme de séquence de Fibonacci mémorisé

Vous pouvez comprendre efficacement comment chaque appel à une fonction Fibonacci récursive est géré à l'aide d'une représentation de pile d'appels. La façon dont chaque appel est placé sur la pile et retiré reflète exactement la façon dont le programme s'exécute. Cela montre clairement à quel point le calcul de grands nombres prendra beaucoup de temps si vous n’optimisez pas l’algorithme.

Dans une pile d'appels, chaque fois qu'une fonction renvoie un résultat, un cadre de pile représentant l'appel de fonction est retiré de la pile. Chaque fois que vous appelez une fonction, vous ajoutez un nouveau cadre de pile en haut de la pile. En général, cette opération a une complexité spatiale de O(n) car il n'y a pas plus de n frames de pile sur la pile d'appels à un moment donné. seule fois.

Remarque : Il existe un éditeur de code convivial appelé Thonny qui vous permet de visualiser la pile d'appels d'une fonction récursive de manière graphique. Vous pouvez consulter Thonny : l'éditeur Python adapté aux débutants pour en soir plus.

Pour visualiser l'algorithme de Fibonacci récursif mémorisé, vous utiliserez un ensemble de diagrammes représentant la pile d'appels. Le numéro d'étape est indiqué par l'étiquette bleue sous chaque pile d'appels.

Supposons que vous souhaitiez calculer F(5). Pour ce faire, vous transférez le premier appel à la fonction sur la pile d'appels :

Pour calculer F(5), vous devez calculer F(4) comme indiqué par la relation de récurrence de Fibonacci, vous ajoutez donc ce nouvel appel de fonction à la pile :

Pour calculer F(4), vous devez calculer F(3), vous ajoutez donc un autre appel de fonction à la pile :

Pour calculer F(3), vous devez calculer F(2), vous ajoutez donc encore un autre appel de fonction à la pile d'appels :

Pour calculer F(2), vous devez calculer F(1), vous l'ajoutez donc à la pile. Comme F(1) est un cas de base, il renvoie immédiatement 1 et vous supprimez cet appel de la pile :

Vous commencez maintenant à dérouler les résultats de manière récursive. F(1) renvoie le résultat à sa fonction appelante, F(2). Pour calculer F(2), vous devez également calculer F(0) :

Vous ajoutez F(0) à la pile. Puisque F(0) est un cas de base, il revient immédiatement, vous donnant 0. Vous pouvez maintenant le supprimer de la pile d'appels :

Ce résultat de l'appel de F(0) est renvoyé à F(2). Vous ez maintenant ce dont vous ez besoin pour calculer F(2) et le supprimer de la pile :

Le résultat de F(2) est renvoyé à son appelant, F(3). F(3) a également besoin des résultats de F(1) pour terminer son calcul, vous le rajoutez donc à la pile :

F(1) est un cas de base et sa valeur est disponible dans le cache, vous pouvez donc renvoyer le résultat immédiatement et supprimer F(1) de la pile :

Vous pouvez compléter le calcul pour F(3), qui vaut 2 :

Vous supprimez F(3) de la pile après oir terminé son calcul et renvoyez le résultat à son appelant, F(4). F(4) a également besoin du résultat de F(2) pour calculer sa valeur :

Vous poussez l'appel à F(2) sur la pile. C'est là que le cache astucieux entre en jeu. Vous l'ez déjà calculé, vous pouvez donc simplement récupérer la valeur du cache, évitant ainsi un appel récursif pour calculer à nouveau le résultat de F(2). Le cache renvoie 1 et vous supprimez F(2) de la pile :

F(2) est renvoyé à son appelant, et maintenant F(4) a tout ce dont il a besoin pour calculer sa valeur, qui est 3 :

Ensuite, vous supprimez F(4) de la pile et renvoyez son résultat à l'appelant final et original, F(5) :

F(5) a maintenant le résultat de F(4) et également le résultat de F(3). Vous poussez un appel F(3) sur la pile, et le cache astucieux revient en jeu. Vous ez précédemment calculé F(3), il ne vous reste donc plus qu'à le récupérer dans le cache. Il n'y a pas de processus récursif pour calculer F(3). Il renvoie 2 et vous supprimez F(3) de la pile :

Maintenant, F(5) a toutes les valeurs dont il a besoin pour calculer sa propre valeur. Vous obtenez 5 en ajoutant 3 et 2, et c'est la dernière étape ant de retirer l'appel F(5) de la pile. Cette action met fin à votre séquence d'appels de fonctions récursifs :

La pile d'appels est maintenant vide. Vous ez terminé la dernière étape pour calculer F(5) :

Représenter les appels de fonction récursifs à l'aide d'un diagramme de pile d'appels vous aide à comprendre tout le trail qui se déroule en coulisses. Cela vous permet également de voir combien de ressources une fonction récursive peut consommer.

Rassembler tous ces diagrammes vous permet de visualiser à quoi ressemble l’ensemble du processus :

Vous pouvez cliquer sur l'image ci-dessus pour zoomer sur les étapes individuelles. Si vous ne mettez pas en cache les nombres de Fibonacci calculés précédemment, certaines étapes de la pile dans ce diagramme seraient bien plus grandes, ce qui signifie qu'elles prendraient plus de temps pour renvoyer un résultat à leurs appelants respectifs.

Utiliser l'itération et une fonction Python

L'exemple des sections précédentes implémente une solution récursive qui utilise la mémorisation comme stratégie d'optimisation. Dans cette section, vous allez coder une fonction qui utilise l’itération. Le code ci-dessous implémente une version itérative de votre algorithme de séquence de Fibonacci :

# fibonacci_func.py def fibonacci_of(n): # Validate the value of n if not (isinstance(n, int) and n >= 0): raise ValueError(f'Positive integer number expected, got "{n}"') # Handle the base cases if n in {0, 1}: return n previous, fib_number = 0, 1 for _ in range(2, n + 1): # Compute the next Fibonacci number, remember the previous one previous, fib_number = fib_number, previous + fib_number return fib_number

Désormais, au lieu d'utiliser la récursion dans fibonacci_of(), vous utilisez l'itération. Cette implémentation de l'algorithme de séquence de Fibonacci s'exécute en temps linéaire O(n). Voici une ventilation du code :

La Ligne 3 définit fibonacci_of(), qui prend un entier positif, n, comme argument.

Les Lignes 5 et 6 effectuent la validation habituelle de n.

Les lignes 9 et 10 gèrent les cas de base où n vaut 0 ou 1.

La Ligne 12 définit deux variables locales, previous et fib_number, et les initialise ec les deux premiers nombres de la séquence de Fibonacci.

La Ligne 13 démarre une boucle for qui itère de 2 à n + 1. La boucle utilise un trait de soulignement (_) pour la variable de boucle car il s'agit d'une variable jetable et vous n'utiliserez pas cette valeur dans le code.

La Ligne 15 calcule le prochain nombre de Fibonacci dans la séquence et mémorise le précédent.

La Ligne 17 renvoie le nombre de Fibonacci demandé.

Pour essayer ce code, revenez à votre session interactive et exécutez le code suivant :

>>> from fibonacci_func import fibonacci_of >>> fibonacci_of(5) 5 >>> fibonacci_of(6) 8 >>> fibonacci_of(7) 13 >>> [fibonacci_of(n) for n in range(15)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Cette implémentation de fibonacci_of() est assez minime. Il utilise le déballage itérable pour calculer les nombres de Fibonacci pendant les boucles, ce qui est assez efficace en termes de mémoire. Cependant, chaque fois que vous appelez la fonction ec une valeur différente de n, elle doit recalculer la séquence. Pour résoudre ce problème, vous pouvez utiliser des fermetures et faire en sorte que votre fonction mémorise les valeurs déjà calculées entre les appels. Vas-y, essaies!

Conclusion

La séquence de Fibonacci peut vous aider à améliorer votre compréhension de la récursivité. Dans ce didacticiel, vous ez appris ce qu'est la séquence de Fibonacci. Vous ez également découvert certains algorithmes courants pour générer la séquence et comment les traduire en code Python.

La séquence de Fibonacci peut être un excellent tremplin et un point d’entrée dans le monde de la récursion, qui est une compétence fondamentale à posséder en tant que programmeur.

Dans ce didacticiel, vous ez appris à :

Générez la séquence de Fibonacci à l'aide d'un algorithme récursifOptimisez votre algorithme de Fibonacci récursif à l'aide de la mémoisationGénérez la séquence de Fibonacci à l'aide d'un algorithme itératif

Vous ez également visualisé l’algorithme récursif mémorisé pour mieux comprendre son fonctionnement en coulisses. Pour ce faire, vous ez utilisé un diagramme de pile d'appels.

Une fois que vous maîtriserez les concepts de ce didacticiel, vos compétences en programmation Python s'amélioreront ainsi que votre pensée algorithmique récursive.

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至lsinopec@gmail.com举报,一经查实,本站将立刻删除。

上一篇 没有了

下一篇没有了