|
Site |
Cours VB.net |
|
|
|
Récursivité. |
|
|
La récursivité c'est quoi?
Définition:
Exemple trouvé sur developpeur.journaldunet.com :
"C'est l'histoire d'un petit garçon qui ne voulait pas dormir et dont la mère lui raconte l'histoire de la petite grenouille qui ne voulait pas dormir et dont la mère lui raconte l'histoire de l'ourson qui ne voulait pas dormir et dont la mère lui raconte l'histoire du bébé écureuil qui s'est endormi, et l'ourson s'endormi, et la petite grenouille s'endormi, et le petit garçon s'endormi."
Cette histoire, permet de mieux voir ce qui se
produit lors de la récursivité: la procédure (le petit qui ne dort pas et à qui
on raconte une histoire) appelle, la même procédure (le petit qui ne dort pas et
à qui on raconte une histoire) qui appelle la même procédure..... on passe au
"niveau" suivant puis au suivant tant qu'on n'a pas atteint la condition d'arrêt
(ici, l'endormissement). Celle-ci atteinte, la récursion se termine pour les
autres niveaux en sens inverse en remontant.
Une procédure est
récursive si elle peut s'appeler elle même.
VB accepte les procédures récursives:
Sub Calcul()
..
Calcul()
..
End Sub
On voit ici que la procédure Calcul() s'appelle elle même: la ligne centrale appelle de nouveau la procédure Calcul() avec nouveaux paramètres, nouvelles variables locales, à la sortie de la procédure (après End Sub), retour à la 'version' précédente de la procédure Calcul() ou on retrouve les variables de la précédente version.
Une procédure non récursive appelle, elle, d'autres procédures.
Pourquoi utiliser la récursivité?
Une procédure récursive découpe le problème en morceaux plus petits et s'appelle elle même pour résoudre chacun des plus petits morceaux, elle résout une petite partie du problème elle même.
Sub Calcul(Gros)
If..
Résout petit problème
Else
Découpe
Calcul(Petit)
End If
End Sub
Ici 'Résout petit problème' s'appelle le point terminal ou le point d'arrêt, c'est la branche d'une condition qui n'appelle pas de nouveau la fonction Calcul(). C'est indispensable.
Si on n'est pas dans le cas du point d'arrêt, la procédure découpe le problème en plus petit morceaux et pour chaque morceau on appelle de nouveau le procédure:
Sub Calcul(Gros)
If..
Découpe
Calcul(Petit)
End If
End Sub
A un moment donné, la condition n'est pas remplie, cela correspond au point terminal.
On se rend compte qu'une bouche For Next peut être transformée en procédure récursive:
Exemple :
Créons une procédure qui ajoute N éléments par ordre décroissant (ajoute l'élément N puis N-1 puis ... 2 puis 1)
On l'appelle avec Calcul(10) par exemple:
Avec For:
Function Calcul(N As Integer)
Dim total As Integer
For i= N to 1 Step-1
total=total + i
Next i
Calcul=total
End Function
Avec la récursivité:
Function Calcul(N As Integer)
Dim total As Integer
If N>0 Then
total= N+ Calcul (N-1)
End If
Calcul= total
End Fonction
On l'appelle avec Calcul(10)
Mais la récursivité ne sert pas seulement à cela, elle sert à résoudre aussi des problèmes qui seraient extrêmement complexes en programmation non récursive.
Règles fondamentales d'une fonction récursive:
1-La récursivité doit s'arrêter à un moment donné.
Il doit y avoir un point terminal (ou point d'arrêt)
Il doit y avoir dans la fonction récursive, une expression conditionnelle dont au moins un des cas conduit à une expression évaluable.
Il doit donc y avoir un chemin non récursif (chemin ou la fonction ne s'appelle pas de nouveau).
Il doit y avoir un test qui survient obligatoirement et qui arrête le fonctionnement récursif sinon la fonction tourne sans fin (ou du moins, elle plante quand la pile est pleine).
Dans notre exemple 1, quand n=1 la récursivité s'arrête.
Dans notre exemple 2, quand il n'y a plus de parenthèse, on n'appelle plus la fonction Calcul, on fait le calcul simple et on sort de la fonction ce qui la fait 'remonter'.
2- A aucun moment les paramètres appelant de nouveau la fonction doivent être les mêmes que l'appel précédent.
Sinon cela tournera indéfiniment.
3-Le nombre d'appel récursif ne doit pas être très grand.
Sous peine de 'StackOverflow': la pile des appels qui stocke les adresses de retour de chaque appel récursif est pleine, elle dépassent ses capacités.
Certains ajoute dans le code de la fonction récursive 'un compteur de sécurité':
Sub Calcul(ByRef Compteur As Long)
If Compteur> LIMITE Then Exit Sub
Compteur= Compteur+1
..
Calcul(Compteur)
..
End Sub
Noter que le compteur est un paramètre ByRef, ce qui permet de toujours incrémenter la même variable.
Voir exemple sur les fractales.
4-La fonction récursive ne doit pas déclarer un grand nombre de variables ou d'objets.
Sous peine d'occuper une place trop importante en mémoire.
5-Limiter les fonctions récursives à une seule procédure, éviter plusieurs fonctions récursives imbriquées.
Sinon cela devient vite trop complexe.
6- Chaque fois qu'elle est appelée de manière récursive (par elle-même, donc), un ou plusieurs des arguments qui lui sont transmis doivent se rapprocher de la condition d'arrêt.
Sinon il n'y aura pas d'arrêt.
7- La complexité du problème doit être réduite à chaque
nouvel appel récursif.
8- Ne peut -on pas faire plus simple avec une boucle For Next?
Parfois une boucle simple remplace avantageusement une fonction récursive. Dans ce cas utiliser la boucle!!
C'est le cas de la fonction factorielle!!
Exemple 0: Inversion de chaîne
Soit une chaîne de caractères, on veut une fonction qui inverse cette chaîne: dernier caractère en premier, avant dernier en second...
Exemple: "abcd" retournera "dcba"
Principe de la fonction 'inverse' récursive:
La fonction 'inverse' met le dernier caractère au début et appelle la fonction 'inverse' avec comme paramètre la chaîne moins le dernier caractère.
Exemple "abcd", on met "d" au début et rappelle la fonction inverse avec comme paramètre "abc"
Point d'arrêt: si la chaîne est vide on retourne une chaîne vide.
Function inverse(ByVal st As String) As String
If st = "" Then
inverse = ""
Elseinverse = st.Substring(st.Length() - 1, 1) + inverse(st.Substring(0, st.Length() - 1))
End If
End Function
Exemple 1: calcul de 'Factorielle'.
On rappelle que N! (factorielle N)= 1*2*3*...*(N-2)*(N-1)*N
Exemple Factorielle 3 =1*2*3
Dim R As Long
R=Factorielle(3) 'retournera 6
Cette fonction n'est pas fournie par VB, créons une fonction Factorielle SANS récursivité:
Function Factorielle (ByVal N as Long) As Long
Dim i As Long
Resultat=1
For i= 1 to N
Resultat=i* Resultat
Next i
Return Resultat
end Function
Cela crée une fonction recevant le paramètre N et retournant un long.
La boucle effectue bien 1*2*3...*N-1*N.
Factorielle avec 'Récursivité':
Une autre manière de calculer une factorielle est d'utiliser la récursivité.
Comment faire?
On sait que N!= N * (N-1) * (N-2).... 3 * 2 * 1
on remarque donc que Factorielle N!= N * Factorielle(N-1)
N!= N*(N-1)! : en sachant que 1!=1
Créons la fonction:
Si N=1 la fonction retourne 1 sinon elle retourne N* factorielle(N-1)
Function Factorielle (ByVal N as Long) As Long
If N=1 then
Return 1
Else
Return N* Factorielle(N-1)
End If
end Function
Dans la fonction Factorielle on appelle la fonction Factorielle, c'est bien récursif.
Pour N=4:
La fonction 'descend' et appelle chaque fois la factorielle du nombre inférieur.
La fonction Factorielle est appelée 4 fois :
Factorielle (4) appelle Factorielle(3) qui appelle Factorielle(2) qui appelle Factorielle (1)
Puis la fonction remonte en retournant le résultat de chaque factorielle.
Factorielle (1) retourne 1
Factorielle (2)retourne 2 '2*factorielle(1)
Factorielle (3)retourne 6 '3*factorielle(2)
Factorielle (4) retourne 24 '4*factorielle(3)
Vb gère cela avec une pile des appels. il met dans une pile les uns aux dessus des autres les appels, quand il remonte, il dépile de haut en bas (Dernier rentré, premier sortie)
Attention: La pile a une taille maximum, si N est trop grand, on déclenche une erreur de type StackOverflow.
Exemple 2: calcul d'une expression avec parenthèses multiples:
Comment calculer la valeur de la chaîne "(4+2(2*8)-(5/(8+1)))"
Une partie du code nommée Calculsimple sait calculer une chaîne de type "8+1" ou "4+2" sans parenthèses.
Il faut gérer les parenthèses,: la sub découpe ce qui est entre parenthèse et s'appelle elle même pour calculer ce qui est entre parenthèses:
La sub calcul fait 2 choses:
Si il y a des parenthèses: appelle Calcul() avec comme paramètre la chaîne entre parenthèse puis remplace la chaîne entre parenthèse par sa valeur
Si il n'y a pas de parenthèses calcule l'expression simple (= - * /).
Sub Calcul(Chaine As String) As String
Si Chaine contient "("
Decouper ValeurEntreParenthese
Resultat=Calcul (ValeurEntreParenthese) 'Appel récursif
Remplacer (ValeurEntreParenthese) par Resultat
Sinon
CalculSimple
Fin Si
End Sub
Exemple 3: PGCD
On rappelle que le PGCD est le 'Plus Grand Commun Diviseur'.
Soit a et b 2 nombres.
Si b divise a => PGCD=b. sinon, PGCD(a,b) = PGCD(b, a mod b)
Function PGCD(ByVal P As Long, ByVal Q As Long) As Long
If Q Mod P = 0 Then
Return P
Else
Return PGCD(Q, P Mod Q)
End If
Exemple 4: Tri récursif:
Tri récursif
Le principe est que la fonction récursive scinde le tableau en 2 et pour chaque partie appelle de nouveau le tri récursif, la condition d'arrêt survient quand le dernier élément est < ou = au premier.
Cette méthode de tri récursif qui se nomme QuickSort est proportionnellement efficace au désordre du tableau à trier. Cette méthode mettra plus de temps (qu'une autre méthode) à trier un tableau qui est déjà en parti trié qu'un tableau rangé au hasard... Mais en cas de désordre intégral, c'est certainement la plus rapide.
Sub QuickSort(debut As Integer, fin As Integer) Dim pivot, gauche, droite, temp As Integer pivot = debut gauche = debut droite = fin do if t(gauche) >= t(droite) then 'échanger si nécessairet(droite) et t(gauche) temp = t(gauche) t(gauche) = t(droite) t(droite) = temp pivot = gauche + droite - pivot 'nouvelle position du pivot 'pivot est alors égal à droite ou à gauche car pivot était avant égal 'à gauche ou à droite End If if pivot = gauche then droite=droite-1 else gauche=gauche+1 loop until gauche = droite if debut < gauche - 1 then QuickSort(debut, gauche - 1) ' //appel récursif sur la partie gauche if fin > droite + 1 then QuickSort(droite + 1, fin) 'appel récursif sur la partie droite
End Sub
Comment l'utiliser:
On crée un tableau Public d'integer contenant les valeurs à trier:
Public t() As Integer = {10, 2, 7, 4, 1, 3, 12, 6}
Dim i As Integer
Call QuickSort(0, 7) 'paramètre= premier et dernier élément du tableau
Affichage du tableau trié dans une texteBox1
For I = 0 To 7
TextBox1.Text = TextBox1.Text + ControlChars.CrLf + t(i).tostring
Next
Exemple 5: Parcours de répertoires et sous répertoires:
On veut afficher dans une ListBox les noms des répertoires, sous-répertoires et fichiers:
On crée une routine AfficheTree qui affiche
Le nom du répertoire courant.
Le nom des fichiers du répertoire courant.
Qui parcourt les sous-répertoires et pour chacun d'eux appelle AfficheTree
Imports System.IO
Sub AfficheTree ( ByVal myDir As String, ByVal Optional Niveau As Integer =0)
'Affiche le répertoire myDir
List1.Items.Add(New String (" ", niveau *2) & myDir)
'Affiche les fichiers
For Each fichier As String In Directory.GetFiles( myDir)
List1.Items.Add(New String (" ", niveau *2+2) & fichier)
Next
'Parcourt les sous-répertoires
For each sousRepertoire As String In Directory.GetDirectories( myDir)
'Appel de manière récursive 'AfficheTree pour afficher le contenu des sous répertoires.
AfficheTree (sousRepertoire, niveau+1)
Next
End Sub
Exemple 6: Évaluation d'un nombre écrit en chiffre romain:
On veut taper III et voir s'afficher 3
taper M et voir s'afficher 1000
taper XLVIII et voir s'afficher 48
On remarque (je l'ai pas fait tout seul!!) que:
Pour un caractère romain, il a une valeur (I=1, V=5, X=10, L=50, C=100, D=500, M=1000)
Pour deux caractères, on compare leurs valeurs:
Si le premier est plus petit, on le soustrait au second: IX = 10 - 1 = 9
Si le premier est plus grand, on l'ajoute au second: VI = 5 + 1 = 6
Pour une suite de n caractères: en partant de la gauche, si le premier chiffre a une valeur inférieure au deuxième, alors on le soustrait de la valeur de tout le reste, sinon on l'additionne à la valeur de tout le reste..
Le programme va donc comparer la valeur des 2 caractères de gauche; il va ajouter (si la valeur du premier est plus grande) ou soustraire (si la valeur du premier est plus petite) la valeur du premier caractère à la valeur de la chaîne raccourcie du premier caractère.
Exemple: pour XLVIII
X plus petit que L donc -10 +valeur de (LVIII)
L plus grand que V donc -10 +50 + valeur de (VIII)
V plus grand que I donc -10 +50 + 5 + valeur de (III)
...
Il faut créer un routine nommée valeur qui calcule la valeur d'un caractère.
Et la routine Eval qui calcule l'expression
S'il y a un caractère dans la chaîne c passée en paramètre, on retourne sa valeur, s'il y en a plusieurs, on compare les 2 premiers caractères, et on additionne ou soustrait à la valeur du premier caractère l' Eval (appel récursif) du reste de la chaîne.
Function valeur(ByVal c As Char) As Integer
Select Case c
Case "M"c : valeur = 1000
Case "D"c : valeur = 500
Case "C"c : valeur = 100
Case "L"c : valeur = 50
Case "X"c : valeur = 10
Case "V"c : valeur = 5
Case "I"c : valeur = 1
End Select
End Function
Function eval(ByVal s As String) As Integer
Dim n As Integer
If s.Length = 1 Then
eval = valeur(s.Chars(0))
Else
n = valeur(s.Chars(0))
If n < valeur(s.Chars(1)) Then n = -n
eval = n + eval(s.Substring(1, s.Length - 1))
End If
End Function
Si on veut tester: créer dans une form 2 texteBox: TextDecimal, TextRomain et un bouton Button1
Private Sub Button1_Click
TextDecimal.Text = eval(TextRomain.Text).ToString
End Sub
Exemple 7: Suite de Fibonacci
On suppose que :
Sont notés en rouge, les couples productifs.
En janvier : 1 couple
En février : 1 couple
En mars : 1 + 1 = 2 couples
En avril : 1 + 2 = 3 couples
En mai : 2 + 3 = 5 couples
En juin : 3 + 5 = 8 couples
En juillet : 5 + 8 = 13 couples
En août : 8 + 13 = 21 couples
En septembre : 13 + 21 = 34 couples
En octobre : 21 + 34 = 55 couples
En novembre : 34 + 55 = 89 couples
En décembre : 55 + 89 = 144 couples
Les réponses constituent les nombres de la suite de Fibonacci : 1 - 1 - 2 - 3 - 5 - 8 - 13 - 21 - ..., dont chaque terme à partir du 3ème est la somme des deux précédents.
Function Fibonacci(ByVal n As Integer)
' si n > 91, cela entraîne un overflows sur les long.
Dim result As Long = 0
If n < = 2 Then
result = 1
Elseresult = Fibonacci(n - 1) + Fibonacci(n - 2)
End If
Return result
End Function
Programme itératif correspondant:
Function Fibonacci2(ByVal n As Integer)
Dim u, v, w, i As Long
If n <= 0 Then Return 0
If n = 0 Then Return 1
u = 0
v = 1
For i = 2 To n
w = u + v
u = v
v = w
Next
Return v
End Function
Exemple
8: Fractales
Calculs de fractales
Les fonctions fractales sont des fonctions récursives théoriquement infinies...
Le Flocon de Koch, du nom du mathématicien suédois Helge Von Koch (1870-1924), est une courbe fermée, reproduisant un triangle équilatéral à des échelles de plus en plus petites. En répétant ce processus une infinité de fois, la courbe obtenue possède alors un périmètre infini mais une aire limitée. Pour ce faire, chaque segment formant un triangle équilatéral est lui-même décomposé en un triangle équilatéral dont la base mesure un tiers du segment, centrée et confondue à ce segment.
On va donc créer une fonction récursive nommée 'flocon' qui décompose un segment en ajoutant un triangle puis qui pour chaque segment appelle la fonction 'flocon.
Comme on ne peut pas afficher des points infiniment petits, on va ajouter une condition d'arrêt qui est déclenchée par le nombre d'appel récursif. Si la condition d'arrêt est remplie, on dessine le segment.
Voici la fonction récursive:
Private Sub Flocon(ByRef gh As Graphics, ByVal a As Point, ByRef b As Point, ByRef n As Integer)
'procédure récursive pour dessiner la fractale de Von Koch
Dim d, c, e As Point
Dim Couleur As Color = Color.Aqua
If n = 0 Then
'Condition de sortie de la récursivitégh.DrawLine(New Pen(Color.Red), a.X, a.Y, b.X, b.Y)
Else
'Appel récursifc = Tiers(a, b)
d = Tiers(b, a)
e = Sommet(c, d)
Flocon(gh, a, c, n - 1)
Flocon(gh, c, e, n - 1)
Flocon(gh, e, d, n - 1)
Flocon(gh, d, b, n - 1)
End If
End Sub
Pour que cela fonctionne, il faut les trois routines suivantes:
Private Function Sommet(ByRef a As Point, ByRef b As Point) As Point
'Calcule le sommet du triangle équilatéral
'dont la base est définie par 2 pointsSommet.x = (b.x + a.x) / 2 + (b.y - a.y) * Racine3Sur2
Sommet.y = (b.y + a.y) / 2 - (b.x - a.x) * Racine3Sur2
End Function
Private Function Milieu(ByRef a As Point, ByRef b As Point) As Point
'Calcule le milieu d'un segment
Milieu.x = (b.x + a.x) / 2
Milieu.y = (b.y + a.y) / 2
End Function
Private Function Tiers(ByRef a As Point, ByRef b As Point) As Point
'Calcul le premier tiers d'un segment
'Attention: Le résultat est orientéTiers.x = (b.x - a.x) / 3 + a.x
Tiers.y = (b.y - a.y) / 3 + a.y
End Function
Pour utiliser cet exemple il faut dans un formulaire un PictureBox nommé PictureBox1 pour afficher la fractale, un TextBox1 permettant de saisir le nombre d'appel récursif( ne pas dépasser 9 en pratique), et un bouton command1 exécutant le calcul et l'affichage.
Mettre en haut du module:
Const Racine3Sur2 As Double = 0.866025404
Private Sub Command1_Click(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles Command1.Click
Dim newBitmap As Bitmap = New Bitmap(400, 400) 'créons un BitMap
Dim g As Graphics = Graphics.FromImage(newBitmap) 'créons un Graphics et y mettre le BitMap
Dim t As Integer
'déclarons les 3 points du triangle initial
Dim a(2) As Point
Dim b(2) As Point
'donnons une valeur à 2 points, calculons le troisième
a(0).X = 164
a(0).Y = 10
b(1).X = 20
b(1).Y = 260
b(0) = Sommet(a(0), b(1))
a(1) = b(0)
a(2) = b(1)
b(2) = a(0)
'Pour chaque coté
For t = 0 To 2
'Appelons la fonction récursive
Flocon(g, a(t), b(t), Val(Text1.Text))
Next t
'Affichons
PictureBox1.Image = newBitmap
End Sub
Code issu d'un code VB6 situé sur CodeS-SourceS VBFrance.com écrit par D. Thuler et transcrit par moi en VB.Net.Merci David.
Autres exemples:
Recherche de chemin dans un labyrinthe.
Le principe est que la fonction récursive teste le déplacement à droite, à gauche, en haut, en bas. La condition d'arrêt se produit quand on a trouvé la sortie; les endroits ou on est déjà passé sont notés.
L'article http://recursivite.developpez.com/ donne des exemples et des explications extraordinaires de programme récursifs EN PASCAL. Si vous avez le courage de les traduire en VB , envoyez les moi!!
Exemple trouvé sur developpeur.journaldunet.com
|
|
|
|
|