Récursivité : Introduction
Algorithmique et programmation/Récursivité/Introduction

Une page de Wikiversité.

 

Introduction
Computer-aj aj ashton 01.svg
Chapitre 1
Leçon : Récursivité
Retour au Sommaire
Chap. suiv. : Algorithmes récursifs

La récursivité c'est l'application de l'adage « Diviser pour régner » à l'algorithmique : pour résoudre un problème d'une taille donnée, on scinde ce problème en plusieurs sous-problèmes plus petits, on recommence avec chacun de ces sous-problèmes jusqu'à ce que tous les petits sous-...-sous-problèmes soient facilement résolubles.

[modifier] Premiers pas

Soyons un peu plus formels. On dit d'une définition qu'elle est récursive si elle fait appel à elle-même pour se définir. Une telle définition peut sembler se mordre la queue, ne rien pouvoir produire de concret. Il n'en sera rien si la définition est bien construite. C'est dans le domaine mathématique que l'on trouve de nombreuses définitions récursives :

 

 

Exemple : définition récursive de la factorielle

begin{array}{l r}
            n!=begin{cases}
            1       & mbox{si }n=0
            n(n-1)! & mbox{si }n>0
            end{cases}                   & n in N
            end{array}

Cet exemple montre les deux caractéristiques principales d'une définition récursive intéressante :

  1. Présence d'un cas particulier ne faisant pas un appel à la définition, mais donnant un résultat n! = 1si n = 0
  2. Présence d'un appel récursif mais avec un paramètre plus petit n! = n(n − 1)! si n > 0

Utiliser une définition récursive est souvent considéré comme élégant, concis et lisible.

[modifier] Algorithme récursif

Appliquons le même schéma aux algorithmes. Si nous disposons d'un algorithme A qui d'une manière directe fait appel à lui-même, ou d'une manière indirecte appelle un autre algorithme B qui lui appelle A, alors l'algorithme A sera dit récursif. Afin d'être correct, il doit exister au moins un cas dans lequel l'appel récursif ne se fait pas, et ce cas doit se produire.

 

 

Définition : Algorithme récursif

Un algorithme est récursif quand dans l'enchaînement des instructions qui le composent, l'une de celles-ci est un appel à ce même algorithme.

Cela donne pour notre exemple de factorielle le pseudocode suivant

Algo Intro Fact.JPG

Nous retrouvons en ligne 4 la condition d'arrêt : aucun appel récursif n'est mis en œuvre, et en ligne 6 l'appel récursif.

[modifier] Structure de donnée récursive

Si nous appliquons un schéma récursif à la création de structures de données, nous créons une structure de données récursive. Ces structures deviennent nécessaires dès que l'on a besoin de les agrandir dynamiquement. L'exemple le plus simple est sans doute celui de la liste. On peut implémenter une liste d'objets avec une structure élémentaire comme un tableau. Mais la gestion dynamique d'un tableau peut-être difficile à maintenir. Une liste peut alors être considérée sous une forme récursive, une liste est composée de

  1. un élément de la liste
  2. une sous-liste d'autres éléments

 

 

Exemple

Supposons que nous disposions de la liste de personnes suivante :
Alice, Bob, Charles, Damien

Une manière de représenter cette liste serait un tableau :

Alice Bob Charles Damien (vide) (vide)

Un tableau possédant une certaine longueur, il a ici été surdimensionné (2 cases sont vides)

Une autre manière de représenter cette liste serait une liste simplement chaînée :


Bigg(Alice, circ mapsto bigg(Bob, circ mapsto Big(Charles, circ mapsto big(Damien, circ mapsto bold{()}quad big) Big) bigg) Bigg)

 

 

Structure de donnée récursive

Une structure de donnée S est dite récursive si l'une de ses composantes est aussi une structure de donnée S ou si l'un de ses composantes est une structure de donnée faisant appel à S.


 

Algorithmique et programmation/Récursivité : Algorithmes récursifs
Algorithmique et programmation/Récursivité/Algorithmes récursifs

Une page de Wikiversité.

 

Algorithmes récursifs
Computer-aj aj ashton 01.svg
Chapitre 2
Leçon : Récursivité
Chap. préc. : Introduction
Chap. suiv. : Structure de données récursives

On peut distinguer plusieurs catégories d'algorithmes récursifs en considérant

 

  • Le mode d'appel : direct/indirect
  • Le nombre de paramètres sur lesquels porte la récursion : arité
  • Le nombre d'appels récursifs : ordre de récursion
  • Le genre de retour : terminal/non terminal

Sommaire

[masquer]

[modifier] Mode d'appel

Comme nous l'avons déjà vu dans l'introduction, un algorithme récursif qui dans son corps s'appelle lui-même est dit direct. L'exemple de la factorielle est trivial. Un algorithme récursif est dit indirect s'il est défini par un ensemble de fonctions qui s'appellent en chaîne.

 

 

Algorithme récursif indirect pour déterminer la parité d'un entier

Pour construire une définition récursive de la parité, remarquons tout d'abord que 0 est un entier pair, et qu'il n'est pas impair. Ensuite remarquons que un entier n est pair (resp. impair) ssi l'entier n-1 est impair (resp. pair).

Algo FcRec PairImpair.png

Si l'on trace Pair(3) on obtiendra

begin{matrix}
            Pair(3) & leftarrow& operatorname{underline{Faux}}
            & searrow  & uparrow
            &          & Impair(2) & leftarrow& operatorname{underline{Faux}}
            &          &           & searrow  & uparrow
            &          &           &          & Pair(1)   & leftarrow& operatorname{underline{Faux}}
            &          &           &          &           & searrow  & uparrow
            &          &           &          &           &           & Impair(0)
            end{matrix}

[modifier] Arité

L'arité d'un algorithme est le nombre de paramètres d'entrée. Un algorithme récursif peut effectuer des appels récursifs en modifiant un nombre quelconque non nul de ses paramètres. Dans l'exemple de la fonction factorielle, l'algorithme prend un paramètre d'entrée et le modifie lors des appels récursifs. Dans le cas de la puissance entière (proposé en exercice), l'algorithme prend deux entrées, la base et l'exposant. Dans les appels récursifs seul l'exposant est modifié. Voici un exemple de récursion sur deux paramètres.

 

 

Algorithme récursif de calcul du PGCD de deux entiers

Algo FcRec PGCD.png

[modifier] Ordre de récursion

Jusqu'à présent, tous les algorithmes récursifs évoqués ne font qu'un appel récursif à la fois, c'est-à-dire que pour déterminer la valeur d'un appel, on n'a besoin que d'un appel récursif. Ces algorithmes sont dits d'ordre 1. Certains algorithmes font plusieurs appels récursifs, comme la version naïve du calcul de la suite de Fibonnaci. Celle-ci doit faire deux appels récursifs, une fois avec le paramètre n-1 et une seconde fois avec le paramètre n-2 pour calculer une valeur de retour. C'est un algorithme récursif d'ordre 2.

 

 

Algorithme récursif pour calculer le nième terme de la suite de Fibbonacci

Algo FcRec Fibb 1.png

[modifier] Récursion terminale

Pour déterminer si un algorithme est terminal, il faut regarder comment il génère les valeurs de retour. Si ce sont soit des constantes, soit des valeurs directement issue d'un appel récursif, sans aucune autre modification, alors l'algorithme est dit terminal. Dans le cas contraire, il sera dit non-terminal. Cette notion est importante si l'on veut déterminer une version itérative de l'algorithme concerné. Dans les exemples précédents, factorielle est non terminal car, bien que le cas d'arrêt renvoie une constante, celui de l'appel récursif quant à lui modifie la valeur renvoyée en la multipliant par n avant de lui-même renvoyer une valeur.


 

Algorithmique et programmation/Récursivité : Structure de données récursives
Algorithmique et programmation/Récursivité/Structure de données récursives

Une page de Wikiversité.

 

Structure de données récursives
Computer-aj aj ashton 01.svg
Chapitre 3
Leçon : Récursivité
Chap. préc. : Algorithmes récursifs
Chap. suiv. : Algorithmes récursifs avancés

La structure de donnée récursive est monnaie courante dans l'informatique de tous les jours; elle apparaît dès que l'on parle de liste, file, arbre, graphe, ... Peu de langages de programmation n'en permettent pas l'utilisation, que ce soit explicitement par l'intermédiaire de pointeurs, ou implicitement par des références. Nous nous bornerons ici à en décrire succinctement le principe, et à en présenter les plus courantes.

Sommaire

[masquer]

[modifier] Principe

La plupart des langages permettent de créer de nouveaux types de données qui sont la réunion de plusieurs autres types de données (struct en C/C++, record en Pascal, classes pour les langages objets, ...). Rendre un type auto référant, consiste simplement à créer un type T et à le munir d'au moins un membre dont le type sera T. Évidemment, tout comme pour les algorithmes, le cas où un type T contient un membre de type S, et que ce dernier contient un membre de type T est englobé par le définition. La représentation classique d'une instance de ce type est formalisée par

begin{array}{ll}
begin{array}{|l|r|}
hline
donnacute ees &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}

La partie données est l'information maintenue par ce type, la case blanche représente une référence vers ce même type et la flèche indique que cette référence est utilisée et pointe vers l'instance concernée. Si la référence ne pointe pas vers une instance alors cela est formalisé ainsi :

begin{array}{ll}
begin{array}{|l|r|}
hline
donnacute ees & bullet
hline
end{array} &
end{array}

Une telle instance indique une terminaison (fin de la liste, feuille d'un arbre)

begin{array}{ll}
begin{array}{|lcl|}
hline
& Chat &
hline
quad bullet quad  vline & bullet  & vline quad quad
hline
end{array} &
& !!!!!!!!! searrow
& begin{array}{|lcl|}
hline
& Chien &
hline
quad bullet quad  vline & bullet  & vline quad bullet quad
hline
end{array}
end{array}

Représente une instance d'un type agrégeant une chaîne de caratères, trois références. Cette instance contient contient la chaîne de caractères Chat. Les deux premières références ne sont pas utilisées, on dit que leur valeur est nulle, elles ne pointent nulle part. La dernière référence pointe vers une instance du même type contenant la chaîne de caractère Chien. L'ordre des références est en général important.

[modifier] Les listes chaînées

[modifier] Chaînage simple

Les listes simplement chaînées permettent de représenter des listes d'éléments quelconques.

begin{array}{l}
begin{array}{ll|l}
operatorname{underline{type}}  noeud & = & operatorname{underline{structure}}
& & quad m_1 : type_1
& & quad cdots
& & quad m_n : type_n
& & quad suivant : noeud
& & operatorname{underline{fstructure}}
end{array}
begin{array}{ll|l}
operatorname{underline{type}}  liste  & = & operatorname{underline{structure}}
& & quad tete : noeud
& & operatorname{underline{fstructure}}
end{array}
end{array}

Une instance particulière appelée tête de liste est une référence sur le premier élément de la liste. La liste d'entiers (5, 2, 17, pourrait être représentée ainsi :

operatorname{Tete}longrightarrow
begin{array}{ll}
begin{array}{|l|r|}
hline
5 &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}
begin{array}{ll}
begin{array}{|l|r|}
hline
2 &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}
begin{array}{ll}
begin{array}{|l|r|}
hline
17 &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}
begin{array}{ll}
begin{array}{|l|r|}
hline
8 & bullet
hline
end{array} &
end{array}

On dispose entre autres des fonctions d'accès suivantes pour écrire nos algorithmes : begin{array}{ll}
creacute er_liste & mbox{cr}acutembox{e}mbox{e une liste initialement vide (i.e. Tete est nulle)}
creacute er_noeud( valeur, suivant ) & mbox{initialise un nouveau noeud}
ajouter_tete( liste, noeud ) & mbox{ajoute }noeudmbox{ en t}hatmbox{e}mbox{te de liste}
ajouter_queue( liste, noeud ) & mbox{ajoute }noeudmbox{ en fin de liste}
effacer_tete( liste ) & mbox{efface la t}hatmbox{e}mbox{te de liste}
effacer_queue( liste ) & mbox{efface la queue de liste}
suivant( noeud ) & mbox{renvoie le noeud suivant}
end{array}

[modifier] Chaînage double

Si dans la définition du nœud on ajoute une référence precedent, avec la contrainte que pour un nœud N qui n'est ni la tête ni la queue suivant(precedent(N))=precedent(suivant(N))=N, alors on obtient une liste doublement chaînée. Elle peut se parcourir aussi bien de la tête vers la queue que dans l'autre sens.

begin{array}{l}
begin{array}{ll|l}
operatorname{underline{type}}  noeud & = & operatorname{underline{structure}}
& & quad m_1 : type_1
& & quad cdots
& & quad m_n : type_n
& & quad suivant : noeud
& & quad precedent : noeud
& & operatorname{underline{fstructure}}
end{array}
begin{array}{ll|l}
operatorname{underline{type}}  liste_{chainage double}  & = & operatorname{underline{structure}}
& & quad tete : noeud
& & operatorname{underline{fstructure}}
end{array}
end{array}

 

operatorname{Tete}longrightarrow
begin{array}{lll}
& begin{array}{|l|c|r|}
hline
bullet & 5 &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}
longleftarrow
!!!!!!!!!!!!!begin{array}{lll}
& begin{array}{|l|c|r|}
hline
& 2 &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}
longleftarrow
!!!!!!!!!!!!!begin{array}{lll}
& begin{array}{|l|c|r|}
hline
& 17 &
hline
end{array} &
!!!!!!!!longrightarrow
end{array}
longleftarrow
!!!!!!!!!!!!!begin{array}{lll}
& begin{array}{|l|c|r|}
hline
& 8 & bullet
hline
end{array}
end{array}

[modifier] Pile, file, etc.

Les piles et les files sont simplement des listes simplement ou doublement chaînées, seules leurs fonctions d'accès diffèrent. En ce qui concerne la pile, on peut uniquement empiler (ajouter en tête), et dépiler (récupération de la valeur de tête, destruction de celle-ci). Quant à la file, on peut seulement enfiler (ajouter en tête) et défiler (récupération de la valeur de queue, destruction de celui-ci). Parmi les implémentations particulières de listes, on pourra citer, les ensembles (chaque valeur est présente une et une seule fois dans la liste), les listes de hashage, les matrices creuses.

[modifier] Remarques

Les listes apportent un comportement dynamique qui fait parfois défaut aux tableaux. Les tableaux de dimension supérieure à 1 peuvent avantageusement être remplacés par des listes de listes.

[modifier] Les arbres

[modifier] Les arbres binaires

Un arbre binaire représente une structure hiérarchique, comme un arbre généalogique. Le nœud de l'arbre est composé de deux références (tout comme un nœud de liste doublement chaînée), on les nomme souvent FG pour fils gauche et FD pour fils droit. Il faut voir ces liens comme un chemin que l'on emprunte à partir de la racine jusqu'à un nœud. La seule contrainte à propos de ce chemin est qu'il ne doit en exister qu'un à partir de la racine vers un nœud quelconque.

begin{array}{l}
begin{array}{ll|l}
operatorname{underline{type}}  noeud & = & operatorname{underline{structure}}
& & quad m_1 : type_1
& & quad cdots
& & quad m_n : type_n
& & quad FG : noeud
& & quad FD : noeud
& & operatorname{underline{fstructure}}
end{array}
begin{array}{ll|l}
operatorname{underline{type}}  arbre  & = & operatorname{underline{structure}}
& & quad racine : noeud
& & operatorname{underline{fstructure}}
end{array}
end{array}

On définit des propriétés telles que le degré d'un nœud (nombre de reférences utilisées, i.e. n'ayant pas une valeur nulle), la hauteur d'un arbre (nombre maximum de descendants), le niveau d'un nœud (distance le séparant de la racine). Un nœud de degré 0 est appelé feuille, un nœud de degré supérieur à 0 est appelé nœud interne.

 

 

Arbre binaire

begin{matrix}
            & & & & & Racine
            & & & & & downarrow
            & & & & & begin{array}{|ccc|}
            hline
            & 8 &
            hline
            & vline  &
            hline
            end{array}
            & & & &   & &
            & & & swarrow & & & & searrow
            & & begin{array}{|ccc|}
            hline
            & 2 &
            hline
            & vline  &
            hline
            end{array}& & & & & & begin{array}{|ccc|}
            hline
            & 17 &
            hline
            & vline  &  bullet
            hline
            end{array}
            & swarrow & & searrow & & & & swarrow
            begin{array}{|ccc|}
            hline
            & 1 &
            hline
            bullet & vline  &  bullet
            hline
            end{array} & & & &
            begin{array}{|ccc|}
            hline
            & 5 &
            hline
            bullet & vline  &  bullet
            hline
            end{array} & &
            begin{array}{|ccc|}
            hline
            & 10 &
            hline
            bullet & vline  &  bullet
            hline
            end{array}
            end{matrix}

qui le plus souvent est simplifié en

begin{matrix}
            & & & & & Racine
            & & & & & downarrow
            & & & & & 8
            & & & &   & &
            & & & swarrow & & & & searrow
            & & 2 & & & & & & 17
            & swarrow & & searrow & & & & swarrow
            1 & & & & 5 & & 10
            end{matrix}

[modifier] Arbres n-aires

Un arbre qui est composé de nœuds pouvant avoir au maximum n références est dit arbre n-aire. On le retrouve dans l'arborescence du disque dur, par exemple. Un arbre unaire est une liste, on dit qu'il a dégénéré en liste. Tout arbre n-aire peut être tansformé en arbre binaire.

[modifier] Graphes

Si l'on construit une structure avec un nombre quelconque de références, sans contraintes particulières (cycle, connexité, ...) alors on obtient un graphe. Un exemple courant de graphe est une carte routière où les villes sont les nœuds et les routes les références. Les graphes sont des outils d'une puissance extraordinaire, mais leur maîtrise est complexe (mais pas compliqué).



 

Exercice : Introduction
Algorithmique et programmation/Récursivité/Exercices/Introduction

Une page de Wikiversité.

Introduction
Computer-aj aj ashton 01.svg
Exercice 1
Leçon : Récursivité
Chapitre du cours : Introduction

Cet exercice est de niveau 0.

[modifier] Puissance entière d'un entier

La fonction puissance entière peut être définie ainsi : begin{array}{l r}
x^n=begin{cases}
1           & mbox{si }n=0
x.x^{(n-1)} & mbox{si }n>0
end{cases} &
x,n in N
end{array}

1. Cette définition respecte-t-elle les caractéristiques d'une définition récursive intéressante ?
2. Écrivez l'algorithme de la fonction ipower(x,n) correspondante.

 

 

Exercice : Algorithmes récursifs
Algorithmique et programmation/Récursivité/Exercices/Algorithmes récursifs

Une page de Wikiversité.

Algorithmes récursifs
Computer-aj aj ashton 01.svg
Exercice 1
Leçon : Récursivité
Chapitre du cours : Algorithmes récursifs

Cet exercice est de niveau 0.

[modifier] Fibbonacci revisité

Nous avons vu un algorithme (dit naïf) qui calcule le nième termde la suite de Fibbonacci :

 

 

Algorithme récursif pour calculer le nième terme de la suite de Fibbonacci

Algo FcRec Fibb 1.png

 

 

 

 

Algorithme récursif pour calculer le nième terme de la suite de Fibbonacci (version 2)

Algo FcRec Exercice Fibbo2.png

 

 

 

Exercice : Structure de données récursives
Algorithmique et programmation/Récursivité/Exercices/Structure de données récursives

Une page de Wikiversité.

Structure de données récursives
Computer-aj aj ashton 01.svg
Exercice 3
Leçon : Récursivité
Chapitre du cours : Structure de données récursives

Cet exercice est de niveau 0.

Sommaire

[masquer]

[modifier] Structure de données récursives : une structure de type dictionnaire

[modifier] Introduction

Nuvola apps important.svg À lire absolument, avant de commencer l'exercice

[modifier] Arbre n-aire

[modifier] Transformer un arbre n-aire en arbre binaire

[modifier] Question de point de vue

[modifier] Arbre binaire

[modifier] Le dictionnaire sous forme arbre binaire

[modifier] Notes

  1. tirée du TLFi : le trésor de la langue française informatisé[1]

Aujourd'hui sont déjà 2 visiteurs (12 hits) Ici!
Ce site web a été créé gratuitement avec Ma-page.fr. Tu veux aussi ton propre site web ?
S'inscrire gratuitement