Applications Delphi Leilclic490
Applications Delphi Leilclic490
ère
Cours de I
Applications Delphi
page 48 de 87
page 49 de 87
7 Delphi
7.1 Introduction
Après son lancement, Delphi se présente sous la forme de 4 fenêtres.
La première fenêtre occupe la partie supérieure de l'écran. Elle correspond à l'environnement de
programmation proprement dit.
Cette fenêtre contient :
• la barre de titre ;
• la barre de menu de Delphi ;
• une zone « barre d'outils » (sur la gauche) ;
• une zone contenant les divers composants regroupés par familles.
La seconde fenêtre se trouve par défaut à gauche de l'écran : c'est l'inspecteur d'objets. Il permet de
visualiser, pour chaque objet ou composant, les propriétés et les événements auxquels l'objet peut
répondre.
La troisième fenêtre constitue la fiche principale de la future application Delphi. Il s'agit, au départ,
d'une fenêtre vide dans laquelle on placera les divers objets.
La dernière fenêtre, cachée sous la précédente constitue l’éditeur proprement dit, contenant le code
source de l'application.
Pour démarrer une nouvelle application, il faut choisir l'option New Application du menu File.
Pour sauvegarder une application, il faut choisir l'option Save All du menu File.
Une règle à suivre absolument est de créer un répertoire par application. Comme Delphi crée
plusieurs fichiers pour une application donnée, il est plus facile de les retrouver s'ils ne sont pas
enregistrés avec d'autres fichiers de noms pratiquement identiques.
Lors du premier « tout enregistrement » de l'application, une fenêtre permet de choisir
l'emplacement de sauvegarde et même de le créer.
Pour exécuter une application, il faut choisir l'option Run du menu Run. Si les options d'auto-
enregistrement ont été sélectionnées et que l'application n'a encore jamais été sauvegardée, la
fenêtre d'enregistrement s'affiche. L'application est ensuite compilée puis exécutée, si elle ne
contient pas d'erreur.
page 51 de 87
7.3.3 Les événements
Pour chaque objet, il peut survenir certains événements, qui déclenchent des réactions.
Dans l’exemple de la voiture, lorsqu’on tourne la clé dans le contact ou lorsqu’on appuie sur
l’accélérateur, la voiture respectivement démarre ou accélère.
Pour les objets informatiques il leur arrive des événements auxquels ils peuvent réagir.
Par exemple, un bouton peut avoir les événements OnMouse… (événements liés à la souris),
OnKey… (événements liés au clavier), OnEnter (réception du focus), On Exit (perte du focus), …
Les événements existants pour un objet sont visibles dans l’inspecteur d’objet.
Fiche (TForm)
Name: frmMain
Etiquette (TLabel)
Name: lblMoy
Caption: 0
page 52 de 87
7.4.2 Les conversions de types
Les données inscrites dans les boîtes d’édition sont de type texte (string). Nous devons donc les
transformer afin de pouvoir effectuer des calculs.
Voici quelques fonctions permettant d’effectuer certaines conversions :
7.4.3 Le traitement
Après la saisie des données dans les boîtes d'édition, l'utilisateur va cliquer sur le bouton
btnCalcul. À cet instant l'événement OnClick du bouton est généré et la méthode
btnCalculClick est lancée. Nous allons donc entrer les instructions à effectuer dans la méthode
btnCalculClick :
7.4.4 Exercices
Exercice 7-1
Ecrivez un programme qui affiche le plus grand de trois nombres réels A, B, C.
Exercice 7-2
Ecrivez un programme qui calcule la somme d'une série de nombres entrés au clavier, en utilisant
deux boîtes d’édition et un bouton pour la remise à zéro de la somme.
Exercice 7-3
Réalisez le programme PUISSANCE qui calcule et affiche la puissance XN (puissance X exposant
N pour un réel X et un entier N positif, négatif ou zéro).
Pour les cas où XN ne se laisse pas calculer, affichez un message d'erreur !
Exercice 7-4
a) Réalisez un programme qui permet de simplifier une fraction.
b) Utilisez une partie du programme réalisé sous a) pour faire un programme qui additionne
deux fractions.
page 53 de 87
7.5 Calcul de la factorielle
Comme premier programme essayons d’implémenter en Delphi le calcul de la factorielle.
1 ⋅ K ⋅ x si x ≥ 1
Rappelons que pour tout entier naturel x, x!=
1 si x = 0
Pour élaborer ce calcul nous pouvons utiliser le programme développé dans le cours de 2e et
l’incorporer dans celui en Delphi.
lblTitre
lblEgal
edtNombre lblResultat
btnOk btnExit
Ce formulaire est un nouvel objet que nous appellerons Tformulaire, de capture (Caption) :
Factorielle (algorithme itératif). Il est composé des propriétés suivantes :
lblEgal TLabel !=
lblResultat TLabel
Il est évident que tous ces champs possèdent encore davantage de propriétés. Nous n’avons
énuméré ici que les plus importantes.
page 54 de 87
7.5.2 Code
Une fois ce formulaire établi, nous pouvons écrire le code nécessaire pour calculer la factorielle.
Rappelons que nous y utiliserons le code Pascal établi en 2e (voir également les « Algorithmes
obligatoires »).
Delphi s’occupera de la déclaration du formulaire et de ses propriétés.
La seule partie du code que nous devons écrire est celle de la procédure btnOkClick qui va être
exécutée, comme son nom le dit, après que l’utilisateur ait poussé sur le bouton Ok. Nous dirons
que la procédure s’exécute après l’événement onClick appliqué à la propriété btnOk.
Le tout se trouvera dans l’unité Unit1.
Voici une possibilité de code pour la procédure en question.
Bien entendu, cette procédure suppose que la fonction factorielle(n:integer):integer est définie.
type
Tformulaire = class(TForm)
lblTitre: TLabel;
page 55 de 87
edtNombre: TEdit;
lblEgal: TLabel;
btnOk: TButton;
lblResultat: TLabel;
procedure btnOkClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
formul: Tformulaire;
implementation
{$R *.dfm}
//fonction permettant de calculer une factorielle
function factorielle(n:integer):integer;
var fact:integer;
begin
fact:=1;
while n>1 do
begin
fact:=fact*n;
n:=n-1
end;
result:=fact
end;
end.
page 56 de 87
7.5.5 Remarques
− La méthode [Link] aura comme seule commande Applica-
[Link]. De cette manière l’événement Click lié au bouton btnExit aura
comme effet net d’arrêter l’application.
− La saisie fautive respectivement d’un nombre négatif ou décimal ne conduira pas à un message
d’erreur de la part du programme mais nous affichera un résultat erroné. Nous laissons au
lecteur le soin de corriger le programme pour l’améliorer de ce point de vue.
lblA TLabel a=
lblB TLabel b=
lblC TLabel c=
page 57 de 87
edtB TEdit valeur de b
− Nous venons déjà de remarquer que le résultat sera affiché dans la TGroupBox gbRes.
Mais les étiquettes (labels) lblTexte, lblX1 et lblX2 sont invisibles pour l’instant.
Si nous avons un résultat à afficher, lblTexte contiendra une des phrases suivantes :
Il n'y a pas de solution réelle ! ,
Il existe une solution réelle ! ou
Il y a deux solutions réelles différentes ! (ceci en fonction du résultat du calcul), tandis que
lblX1 et lblX2 contiendront les valeurs des racines éventuelles. Comme actuellement ces
étiquettes ne contiennent pas de texte (caption vide), elles n’apparaîtront pas à l’écran.
7.6.2 Code
Une fois ce formulaire établi nous écrivons le code suivant :
page 58 de 87
unit Unit1;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Buttons;
type
Tformulaire = class(TForm)
gbCoeff: TGroupBox;
lblA: TLabel;
lblB: TLabel;
lblC: TLabel;
edtA: TEdit;
edtB: TEdit;
edtC: TEdit;
btnCalcul: TButton;
gbRes: TGroupBox;
lblTexte: TLabel;
lblX1: TLabel;
lblX2: TLabel;
btnExit: TButton;
procedure btnCalculClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
private
{ Private-Declarations}
public
{ Public-Declarations }
end; //Tformulaire
var formulaire: Tformulaire;
implementation
{$R *.DFM}
procedure [Link](Sender: TObject);
var a,b,c,disc : real;
begin
a:=StrtoFloat([Link]);
b:=StrtoFloat([Link]);
c:=StrtoFloat([Link]);
disc:=b*b-4*a*c;
if disc < 0 then
begin
[Link]:='Il n''y a pas de solution réelle !';
[Link]:='';
[Link]:='';
end
else if round(disc*1000000) = 0 then //un nombre reel n’est jamais 0
begin
[Link]:='Il existe une solution réelle !';
[Link]:='x = ' + FloattoStr(-b/(2*a));
[Link]:='';
end
else if disc > 0 then
begin
[Link]:='Il y a deux solutions réelles différentes !';
[Link]:='x1 = ' + FloattoStr((-b-sqrt(disc))/(2*a));
page 59 de 87
[Link]:='x2 = ' + FloattoStr((-b+sqrt(disc))/(2*a));
end;
end;
end.
page 60 de 87
Nous essayons de traduire ceci en Delphi.
page 61 de 87
7.7.3 Code
Une fois ce formulaire établi, nous devons programmer le code nécessaire.
Voici un exemple d’implémentation.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;
type
TfrmMatricule = class(TForm)
lblTitre: TLabel;
lblSaisie: TLabel;
lblResultat: TLabel;
edtSaisie: TEdit;
edtResultat: TEdit;
btnVerif: TButton;
lblResultat: TLabel;
btnExit: TButton;
implementation
{$R *.dfm}
procedure [Link](Sender: TObject);
var
a1,a2,a3,a4,m1,m2,j1,j2,n1,n2,nc,c: integer;
sum : integer;
s : string;
begin
s:=[Link];
if length(s) <> 11 then
ShowMessage('Numéro de matricule mal saisi')
else
begin
a1:= StrToInt(copy(s,1,1));
a2:= StrToInt(copy(s,2,1));
a3:= StrToInt(copy(s,3,1));
a4:= StrToInt(copy(s,4,1));
m1:= StrToInt(copy(s,5,1));
m2:= StrToInt(copy(s,6,1));
j1:= StrToInt(copy(s,7,1));
page 62 de 87
j2:= StrToInt(copy(s,8,1));
n1:= StrToInt(copy(s,9,1));
n2:= StrToInt(copy(s,10,1));
nc:= StrToInt(copy(s,11,1));
sum := 5*a1+4*a2+3*a3+2*a4+7*m1+6*m2+5*j1+4*j2+3*n1+2*n2;
sum := sum mod 11;
if sum = 1 then s:='faux'
else
begin
if sum = 0 then c:= 0
else c:= 11-sum;
if nc=c then s:='correct'
else s:='faux';
end;
[Link]:=s;
end;
end;
procedure [Link](Sender: TObject);
begin
[Link];
end;
end.
page 63 de 87
7.8 Une petite machine à calculer
Dans ce prochain exercice nous nous proposons de mettre en œuvre une petite machine à calculer,
qui effectuera les 4 opérations élémentaires.
Nous appellerons, comme toujours Tformulaire l’objet que nous allons définir ci-dessous.
Nom type text caption
btnButton0 TButton 0
btnButton1 TButton 1
btnButton2 TButton 2
btnButton3 TButton 3
btnButton4 TButton 4
btnButton5 TButton 5
btnButton6 TButton 6
btnButton7 TButton 7
btnButton8 TButton 8
btnButton9 TButton 9
btnButtonclear TButton C
btnButtondiv TButton /
btnButtonequal TButton =
btnButtonminus TButton -
page 64 de 87
btnButtonmult TButton *
btnButtonplus TButton +
7.8.2 Code
Une possibilité de code pour cette machine à calculer est le suivant :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms,Dialogs, StdCtrls;
type
Tformulaire = class(TForm)
edtNum: TEdit;
btnButton7: TButton;
btnButton1: TButton;
btnButton9: TButton;
btnButton8: TButton;
btnButton6: TButton;
btnButton5: TButton;
btnButton2: TButton;
btnButton4: TButton;
btnButton3: TButton;
btnButton0: TButton;
btnButtonmult: TButton;
btnButtondiv: TButton;
btnButtonclear: TButton;
bnButtonminus: TButton;
btnButtonplus: TButton;
btnButtonequal: TButton;
btnButtonArret: TButton;
procedure FormCreate(Sender: TObject);
procedure btnButton0Click(Sender: TObject);
procedure btnButtonplusClick(Sender: TObject);
procedure bnButtonminusClick(Sender: TObject);
procedure btnButtonmultClick(Sender: TObject);
procedure btnButtondivClick(Sender: TObject);
procedure btnButtonclearClick(Sender: TObject);
procedure btnButtonequalClick(Sender: TObject);
procedure btnButtonArretClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
flag, n1, n2, op : integer;
n3 : real;
page 65 de 87
end;
var
formulaire: Tformulaire;
implementation
{$R *.dfm}
page 66 de 87
procedure [Link](Sender: TObject);
begin
n1:=StrToInt([Link]);
flag := 1;
op := 3;
end;
page 67 de 87
Les mêmes variables flag, op, n1, n2 et n3 sont utilisées dans toutes les procédures, et
sont donc déclarées dans l’en-tête du programme (variables globales).
La variable flag est initialisée à 0 lors du lancement du formulaire (événement FormCreate du
formulaire). Si flag = 0, le chiffre correspondant à la touche numérique actionnée est concaténé
à la chaîne de caractères se trouvant déjà dans [Link].
Si par contre flag = 1, le chiffre correspondant à la touche numérique actionnée est copié dans
[Link] tout en écrasant le contenu antérieur.
En tapant sur la touche ( = ), le contenu de la propriété Text de l’objet edtNum est copié dans la
variable n2 et consitue le deuxième opérande. L’opération définie par le code contenu dans la
variable op est alors effectuée à l’aide de la structure alternative à choix multiples (instruction
case ... of ...).
Il reste à remarquer que la calculatrice ne respecte pas la priorité des opérations.
page 68 de 87
Nous référençons une cellule par Cells[colonne,ligne].
Attention : nous devons faire attention que le comptage des lignes et des colonnes commence,
comme si souvent, par 0.
lblEg1 TLabel =
lblEg2 TLabel =
page 69 de 87
lblInv TLabel inv
Les composants de type StringGrid qui servent à introduire des matrices doivent avoir l’option
goEditing avec la valeur True.
Le champ lbOp de type ListBox sert à énumérer les différentes opérations et à donner à
l’utilisateur la possibilité de choisir.
Après l’élaboration de ce formulaire nous pouvons écrire le code nécessaire. Voici un exemple
possible.
unit Unit1 ;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, Grids, Buttons ;
type
Tformulaire = class(TForm)
lblTitre : Tlabel ;
sgMat1 : TstringGrid ;
sgMat2 : TstringGrid ;
sgMatRes : TstringGrid ;
sgMat3 : TstringGrid ;
sgMatInv : TstringGrid ;
lblEg1: Tlabel;
lblEg2: Tlabel;
lblInv: Tlabel;
lbOp: TlistBox;
btnEff: Tbutton;
btnInv: Tbutton;
btnArret: Tbutton;
page 70 de 87
procedure btnEffClick(Sender: Tobject);
procedure btnInvClick(Sender: Tobject);
procedure btnArretClick(Sender: Tobject);
private
{ Private declarations }
public
{ Public declarations }
end;//Tformulaire
var
formulaire: Tformulaire;
implementation
{$R *.dfm}
procedure [Link](Sender: TObject);
var a,i,j : integer;
begin
if [Link]=0 then
for i:=0 to 1 do
for j:=0 to 1 do
begin
a:=StrToInt([Link][i,j])+StrToInt([Link][i,j]);
[Link][i,j]:=IntToStr(a);
end;
//j
//i
//fi index=0
if [Link]=1 then
for i:=0 to 1 do
for j:=0 to 1 do
begin
a:= StrToInt([Link][i,j])-StrToInt([Link][i,j]);
[Link][i,j]:=IntToStr(a);
end;
//j
//i
//fi index=1
if [Link]=2 then
for i:=0 to 1 do
for j:=0 to 1 do
begin
a:=StrToInt([Link][0,j])*StrToInt([Link][i,0])
+StrToInt([Link][1,j])*StrToInt([Link][i,1]);
[Link][i,j]:=IntToStr(a);
end;
//j
//i
//fi index=0
end;//btnEffClick
ItemIndex opération
0 addition
1 soustraction
2 multiplication
Pour le reste, le contenu des différentes parties des procédures correspond aux règles
mathématiques qui définissent les opérations sur les matrices.
page 72 de 87
8 La récursivité
8.1 Exemple
La fonction suivante calcule une puissance de base réelle non nulle et d’exposant naturel :
function puissance(x:real;m:integer):real;
begin
if m=0 then result:=1
else result:=x*puissance(x,m-1)
end;
Cette fonction présente une grande différence par rapport à toutes les fonctions que nous avons
définies précédemment. Dans la définition même on trouve déjà un appel à la fonction puissance. Il
s’agit ici d’un mécanisme très puissant, présent dans tous les langages de programmation
modernes : la récursivité. Le fonctionnement exact de ce mécanisme ainsi que les conditions
d’utilisation seront étudiées en détail dans les paragraphes suivants. Remarquons cependant qu’il
existe un lien étroit entre la récursivité en informatique et la récurrence en mathématique. La
définition de la fonction puissance présentée ici est une transcription quasi directe des formules
x 0 = 1
m m −1
, valables pour x non nul5.
x = x ⋅ x
La fonction puissance du paragraphe précédent est bien sûr une fonction récursive directe.
Le mécanisme de la récursivité est très puissant, mais il faut une certaine expérience pour pouvoir
l’utiliser dans de bonnes conditions. Dans la suite nous allons élucider principalement les aspects
suivants :
• Sous quelles conditions et pourquoi une fonction récursive donne-t-elle le résultat attendu ?
Comment vérifier qu’une telle fonction est correcte ?
• Comment le système gère-t-il une fonction récursive ? C’est-à-dire comment est-ce que ce
mécanisme fonctionne en pratique ?
• Est-ce qu’une fonction récursive est « meilleure » ou « moins bonne » qu’une fonction
itérative (normale) ?
Il est clair que ces différents aspects ne sont pas indépendants les uns des autres, mais qu’il faut une
vue d’ensemble pour bien les comprendre.
5
La fonction «puissance» donne un résultat incorrect si x et m sont nuls. De plus, il est nécessaire que m soit un
entier positif !
page 73 de 87
8.3 Etude détaillée d’un exemple
Dans ce paragraphe nous revenons à la fonction « puissance » de la page précédente et nous allons
commencer par étudier quelques exemples d’exécution.
puissance(7,0) : donne bien sûr comme résultat 1 vu que la condition m=0 est vérifiée.
puissance(7,1) : la condition m=0 n’est pas vérifiée et la fonction calcule donc d’abord
«x*puissance(x,m-1)» c’est-à-dire «7*puissance(7,0)» ce qui donne dans une
deuxième étape «7*1=7».
puissance(7,2) : ici la condition m=0 n’est pas non plus vérifiée et la fonction calcule donc
aussi d’abord «x*puissance(x,m-1)» c’est-à-dire «7*puissance(7,1)» . Suivent
ensuite les deux étapes précédentes. A la fin de la troisième étape le résultat obtenu est
«7*puissance(7,1)=7*[7*puissance(7,0)]=7*7*1=49».
Il est important de remarquer ici que le système refait chaque fois toutes les étapes et « ne se
souvient pas » des appels de fonctions précédents. L’exécution de puissance(7,12) nécessite
13 passages dans la fonction : d’abord 12 appels récursifs et ensuite un dernier passage où m=0.
L’exemple puissance(7,-2) est particulièrement intéressant. La condition m=0 n’est pas
vérifiée et la fonction calcule donc «x*puissance(x,m-1)» c’est-à-dire «7*puissan-
ce(7,-3)». Ensuite elle va évaluer «7*puissance(7,-4)», «7*puissance(7,-5)»,
«7*puissance(7,-6)», etc. Il est clair que cet appel ne va certainement pas donner le résultat
1/49. Mais la situation est plus grave, l’exécution de la fonction ne va pas donner de faux résultat,
mais cette exécution ne va pas se terminer6 vu que la condition m=0 ne sera plus jamais vérifiée.
Pour qu’une fonction ou une procédure récursive s’arrête, il est nécessaire que le code vérifie les
conditions suivantes :
• Pour une ou plusieurs valeurs des données, appelées « cas de base », la fonction calcule
directement (sans appel récursif) le résultat.
• Dans le code de la fonction il doit être assuré que chaque suite d’appels récursifs va
toujours finir par atteindre un cas de base.
Il est clair que le fait que la fonction s’arrête, signifie seulement qu’elle va fournir un résultat, mais
non pas que ce résultat est correct. L’arrêt de la fonction est une condition préalable !
Dans notre exemple, il est donc nécessaire de préciser que la fonction « puissance » ne convient pas
pour les exposants négatifs7.
Dans une démonstration par récurrence en mathématiques la situation est semblable : Pour montrer
qu’une propriété est vraie pour tout entier naturel, on montre d’abord qu’elle est vraie pour le cas de
base n=0 et ensuite on montre que si la formule est vraie pour le naturel n, alors elle reste vraie pour
n+1. La véracité de la propriété pour n=4 est alors ramenée successivement à n=3, n=2, n=1
jusqu’au cas de base n=0, que l’on sait vérifié.
6
Dans cette situation le système risque d’entrer dans un état indéfini provoqué par un débordement de mémoire. Dans
le pire cas il sera nécessaire de redémarrer l’ordinateur (RESET) avec toutes les conséquences que cela peut impliquer.
Dans un cas plus favorable, Delphi détecte le problème et avorte l’exécution de la fonction.
7
Elle ne convient pas non plus pour les exposants non entiers, mais ceux-ci sont de toute façon exclus vu que la
variable exposant m est de type integer.
page 74 de 87
8.4 Fonctionnement interne
Dans ce paragraphe on va chercher une réponse à la question comment le système gère l’exécution
d’une procédure récursive. La bonne compréhension de ce mécanisme est importante pour pouvoir
rédiger des programmes efficaces.
Revenons à l’exemple de la fonction factorielle qui calcule la factorielle d’un nombre naturel
donné. Cet exemple, déjà implémenté de façon itérative au chapitre précédent, se prête
particulièrement bien à être programmé de façon récursive vu que la définition mathématique de la
fonction se base sur des formules de récurrence.
0!= 1 0!= 1
ou bien
n!= n ⋅ (n − 1)! si n ∈ N 0 n!= n ⋅ (n − 1) ⋅ (n − 2) ⋅ K ⋅ 3 ⋅ 2 ⋅1 si n ∈ N 0
function factorielle(n:integer):integer;
begin
if n=0 then result:=1 else result:=n*factorielle(n-1)
end;
On peut l’intégrer dans le programme Delphi du chapitre précédent. Le reste du code reste
identique.
Lors de l’exécution de factorielle(1) le système exécute la fonction jusqu’à l’appel récursif
« factorielle(n-1) » A ce stade le système mémorise l’état actuel de toutes les variables
locales de la fonction. Lors de l’exécution de « factorielle(n-1) » le système recommence
à exécuter la fonction factorielle avec le paramètre n-1=0. Lors de ce deuxième passage dans la
fonction, les variables locales sont réinitialisées, leurs anciennes valeurs ne sont pas accessibles à ce
moment mais elles sont sauvegardées pour une réutilisation ultérieure. Maintenant on a donc n=0 et
le système termine ce passage dans la fonction avec result:=1. Ensuite le système revient dans
le premier passage de la fonction à l’endroit factorielle(n-1). Les variables locales
récupèrent leur ancienne valeur et l’expression n*factorielle(n-1) est évaluée avec n=1 et
factorielle(n-1)=1. La variable result prend la valeur 1 et l’exécution quitte
définitivement la fonction.
Pour des appels de fonction avec des arguments plus grands, par exemple factorielle(15) le
système doit donc conserver autant de copies de toutes les variables locales qu’il y a d’appels
récursifs avant d’atteindre le cas de base. Cela peut nécessiter une quantité appréciable de mémoire
et de temps d’exécution pour la gestion qui en découle.
page 75 de 87
égal à n*(n-1)! par l’hypothèse de récurrence et qui est encore égal à n! par la définition
mathématique de la factorielle.
Cette démonstration ne prend pas en compte des problèmes de dépassement de mémoire, inhérents
au système Delphi, si n est « trop grand ».
Pour se faire une idée de la situation, il est conseillé de tester les 2 fonctions. En prenant
successivement les arguments 5, 10, 20, 30, 40, 50…, on va finir par constater la même chose
indépendamment de l’ordinateur utilisé. Même sur un ordinateur rapide la fonction récursive va
finir par devenir terriblement lente alors que la fonction itérative va rester rapide (résultat immédiat)
même sur un ordinateur très faible ! Pourquoi ?
8
Iteratif: algortihme basé sur une boucle.
page 76 de 87
Que fait la fonction itérative ? Si n>1, cette fonction effectue exactement n-1 additions sur des
nombres du type int64, et un nombre approximativement proportionnel à n d’affectations, de
soustractions et de comparaisons. Le temps d’exécution de ces opérations est négligeable par
rapport à la croissance fulgurante des résultats : c’est-à-dire la fonction sera limitée par la capacité
de représentation du type int64 avant qu’on remarque un quelconque ralentissement dans
l’exécution !
Que fait la fonction récursive ? Etudions les exécutions de la fonction pour les arguments 2 à 5.
Pour fibo(2) [résultat : 2], la condition n’est pas vérifiée et la fonction calcule donc l’expression
fibo(1)+fibo(0). Les deux appels récursifs donnent directement le résultat 1 et l’exécution se
termine donc « assez rapidement » après seulement 3 passages (chaque fois 1 appel avec les
arguments 0, 1 et 2) dans la fonction.
Pour fibo(3) [résultat : 3], la fonction calcule d’abord fibo(2)+fibo(1). Le premier appel
nécessite trois passages (voir alinéa précédent) dans la fonction et le deuxième appel donne
directement 1. Cela fait donc un total de 5 appels.
Pour fibo(4) [résultat : 5], la fonction calcule fibo(3)+fibo(2). Le nombre d’appels se
calcule par 5 (pour fibo(3)) + 3 (pour fibo(2)) + 1 (pour fibo(4)) = 9.
Sans entrer dans tous les détails : Pour fibo(10) [résultat : 89], il faut 177 appels, pour
fibo(20) [résultat : 10946], il faut 21891 appels et pour fibo(30) [résultat : 1346269], il faut
2692537 appels.
Il est clair que le nombre d’appels de fonctions doit être supérieur ou égal au résultat. En effet, les
deux seuls cas de bases donnent le résultat 1 et l’appel récursif consiste à ajouter deux résultats
intermédiaires. Le résultat final est donc atteint en ajoutant tant de fois le nombre 1. A côté du
nombre impressionnant de calculs que la fonction effectue, il ne faut pas oublier la quantité
d’espace mémoire nécessaire pour sauvegarder toutes les valeurs intermédiaires de la variable n.
page 77 de 87
Un certain nombre de problèmes admettent une solution récursive « très élégante » (algorithme
simple à rédiger et à comprendre, mais pas nécessairement efficace). Nous verrons des exemples de
ce genre dans le chapitre sur les recherches et les tris9.
Pour certains de ces problèmes, une formulation à l’aide de boucles est très compliquée. Mais une
telle formulation existe toujours. Dans le pire des cas, il est nécessaire de « simuler » l’algorithme
récursif en stockant toutes les valeurs intermédiaires des variables dans un tableau. C’est de cette
façon-là que le programme Delphi lui-même évalue les fonctions récursives ; en effet le langage
machine, seul langage compris par le processeur, est un langage relativement faible qui ne connaît
pas le mécanisme de la récursivité.
8.8 Exercices
Exercice 8-1
a) Ecrivez un programme qui calcule la fonction d’Ackermann à deux variables naturelles définie
par les égalités :
Ack (0 , m ) = m + 1
Ack (k , 0 ) = Ack (k − 1,1) si k ≥ 1
Ack (k , m ) = Ack (k − 1, Ack (k , m − 1)) si k , m ≥ 1
b) Essayez cette fonction pour quelques arguments. Par exemple ack(3,5)=253,
ack(4,0)=13, ack(4,1)=65533,
Exercice 8-2
Complétez la fonction puissance pour qu’elle calcule aussi correctement des puissances à exposant
entier négatif.
Exercice 8-3
Ecrivez une version récursive de la fonction puissance rapide.
a 0 = 1
Aide : utilisez les formules a 2 n = a 2 n ( )
2 n +1
a = a 2n ⋅ a
Expliquez pour quelles valeurs de a et de n, l’exécution va s’arrêter et donner un résultat correct.
Exercice 8-4
Ecrivez une version récursive de la fonction pgcd.
Exercice 8-5
Ecrivez un programme récursif qui transforme un nombre binaire en notation décimale.
Exercice 8-6
Ecrivez un programme récursif qui transforme un nombre décimal en notation binaire.
9
Voir par exemple « recherche dichotomique » et « quicksort ».
page 78 de 87
9 Comptage et fréquence
9.1 Algorithme de comptage
On veut réaliser une fonction qui compte le nombre d’occurrences d’une chaîne de caractères dans
une liste donnée.
Pour réaliser cet algorithme on parcourt la liste élément après élément et on le compare avec la
chaîne trouvée. Si les deux chaînes sont identiques, on augmente un compteur.
function compter(liste:TListbox;chaine:string):integer;
var
i,r:integer;
begin
r:=0;
for i:=0 to [Link]-1 do
if [Link][I] = chaine then r:=r+1;
result:=r;
end;
Exercice 9-1
Réalisez une fonction qui permet de compter le nombre d’occurrences d’une lettre dans une chaîne
de caractères donnée.
9.2 Fréquence d’une lettre dans une liste
Soit une liste dont les éléments sont des lettres. On veut savoir combien de fois chaque lettre est
représentée dans la liste. Ce type d’algorithme est un élément très important des algorithmes de
compression, comme par exemple celui de Huffman.
La solution proposée utilise de manière un peu inhabituelle les tableaux, mais de ce fait-là devient
très élégante. On utilise comme indice du tableau des lettres.
Le résultat de l’analyse de la liste se trouve dans une deuxième liste passée comme paramètre et a le
format suivant : lettre : nb. d’occurrences.
procedure frequence(liste:TListbox; var resultat:TListbox);
var
i:integer;
c:char;
tableau:array['A'..'Z'] of integer;
element:string;
begin
for c:='A' to 'Z' do tableau[c]:=0;
for i:=0 to [Link]-1 do
begin
element:=[Link][i];
tableau[element[1]]:=tableau[element[1]]+1;
end;
[Link];
for c:='A' to 'Z' do
[Link](c+' : '+inttostr(tableau[c]));
end;
page 79 de 87
Exercice 9-2
Réalisez un sous-programme qui permet de compter le nombre d’occurrences des différentes lettres
dans une chaîne de caractères donnée. Le résultat du sous-programme est un tableau.
10 Recherche et tri
10.1 Introduction
Les algorithmes de recherche et de tri ont une très grande importance en informatique. C’est surtout
dans le contexte des bases de données que ces algorithmes sont utilisés. Nous allons traiter en
premier lieu les algorithmes de tri car pour fonctionner certains algorithmes de recherche présument
des données triées.
Bien que l’illustration des différents algorithmes se base sur un petit nombre de données, il ne faut
pas oublier que ces algorithmes sont en général appliqués sur un nombre très grand de données
( plus que 10000, comme par exemple dans un annuaire téléphonique ).
[Link] Exercice
Exécutez pas à pas les deux algorithmes en utilisant la liste suivante :
E ;X ;E ;M ;P ;L ;E.
page 81 de 87
10.3.2 Tri par insertion
[Link] Idée
On considère un élément après l’autre dans la liste et on cherche sa position dans la liste déjà triée.
Ensuite on l’insère à la juste position. De ce fait les éléments suivants de la liste doivent être
déplacés.
[Link] Exercice
Exécutez pas à pas les deux algorithmes en utilisant la liste suivante :
C;A;R;T;O;O;N.
page 82 de 87
10.3.3 Tri rapide
[Link] Introduction
Le tri rapide (ang. : Quicksort) est l’algorithme de tri le plus utilisé. Il est réputé pour sa vitesse de
tri qui pour des listes de grande taille est souvent bien supérieure à celle d’autres algorithmes de tri.
Nous allons développer le tri rapide en plusieurs étapes.
[Link] Idée
L’algorithme de tri va diviser la liste en deux parties. Ces parties qui ne sont pas forcément de taille
égale, sont alors triées séparément par le même algorithme. L’important dans cet algorithme est la
stratégie comment la liste est divisée en deux sous-listes.
page 83 de 87
La fonction division se présente de la manière suivante :
function division(var liste: TListbox;g,d:integer):integer;
var i,j : integer;
candidat :string;
begin
candidat:=[Link][d];
j:=d-1; i:=g;
while i<=j do
begin
if [Link][i]< candidat then i:=i+1
else if [Link][j]> candidat then j:=j-1
else begin echange(liste,i,j); i:=i+1 ; j:=j-1; end;
end;
echange(liste,i,d);
result:=i;
end;
Pour commencer, on choisit l’élément qui se trouve à la fin de liste comme candidat. On va
déterminer la position définitive du candidat dans la liste et s’arranger que tous les éléments de la
liste qui se trouvent avant sont plus petits et tous ceux qui se trouvent après sont plus grands que le
candidat.
La boucle parcourt la liste en commençant par le début (if) jusqu’au premier élément supérieur au
candidat, ensuite (else if) elle parcourt la liste en commençant par la fin jusqu’au premier
élément inférieur au candidat. Ces 2 éléments sont alors (else) échangés et on recommence.
A la fin de la boucle le candidat est mis à sa place définitive et l’indice de cette place est rendu
comme résultat de la fonction. On vérifie facilement qu’au cas où le candidat se trouvait à sa bonne
place (lorsqu’il est le plus grand élément de la liste), alors i=d et le dernier échange n’a aucun effet.
[Link] Idée
Si l’on dispose de deux listes triées, on peut assez facilement les fusionner10 (mettre ensemble) afin
d’obtenir une seule liste triée. Un tri par fusion utilisant cette approche peut être implémenté de
façon récursive et de façon itérative. Les deux versions utilisent la même procédure fusion.
10
La fusion est en quelque sorte une généralisation de l’insertion où l’on fusionne aussi deux listes triées, dont l’une
n’est composée que d’un seul élément.
page 84 de 87
éléments des deux-sous-listes (le plus à gauche et le plus à droite) et replace le plus petit des deux
dans la liste principale et ainsi de suite.
procedure fusion(var liste: TListbox; g,m,d: integer);
var i,j,k : integer;
tt : array[1..1000] of string; {mieux: déclarer tt globalement}
begin
for i:=g to m do tt[i]:=[Link][i];
for j:=m+1 to d do tt[d+m+1-j]:=[Link][j];
i:=g; j:=d;
for k:=g to d do
if tt[i]<tt[j]
then begin [Link][k]:=tt[i]; i:=i+1 end
else begin [Link][k]:=tt[j]; j:=j-1 end;
end;
[Link] Solution récursive
Il suffit de diviser la liste en deux, de trier séparément les deux parties et de fusionner les deux
parties triées.
procedure tri_fusion_re(var liste: TListbox;g,d:integer);
var m:integer;
begin
if g<d then
begin
m:=(g+d) div 2;
tri_fusion_re(liste,g,m);
tri_fusion_re(liste,m+1,d);
fusion(liste,g,m,d);
end;
end;
[Link] Solution itérative
La version itérative commence par considérer des sous-listes de longueur 1 qui sont ensuite
fusionnées 2 à 2. Ensuite la liste n’est composée que de sous-listes de longueur 2 déjà triées. Celles-
ci sont encore fusionnées 2 à 2 et ainsi de suite. D’étape en étape la longueur des sous-listes triées
double (variable step). Toute la liste est triée lorsqu’il n’existe plus qu’une seule grande sous-
liste.
procedure tri_fusion_it(var liste: TListbox);
var i,m,step:integer;
begin
m:=[Link];
step:=1;
i:=0;
while step<m do
begin
while (i+2*step-1)<m do
begin
fusion(liste,i,i+step-1,i+2*step-1) ;
i:=i+2*step;
end;
if (i+step)<=m then fusion(liste,i,i+step-1,m-1);
{s'il reste une liste et une partie d'une 2e liste}
page 85 de 87
step:=step*2;
i:=0 ;
end;
end;
page 86 de 87
[Link] Solution récursive
function dicho_r(liste:TListbox;cle:string;g,d:integer):integer;
var
milieu:integer;
begin
if g>d then result:=-1
else
begin
milieu:=(g+d) div 2;
if [Link][milieu] = cle then result:=milieu
else if cle<[Link][milieu] then
result:= dicho_r(liste,cle,g,milieu-1)
else
result:= dicho_r(liste,cle,milieu+1,d);
end;
end;
function dicho_i(liste:TListbox;cle:string):integer;
var
milieu,g,d:integer;
begin
g:=0;
d:=[Link]-1;
milieu:=(g+d) div 2;
while (cle<>[Link][milieu]) and (g<=d) do
begin
milieu:=(g+d) div 2;
if cle<[Link][milieu] then d:=milieu-1
else g:=milieu+1;
end;
if cle=[Link][milieu] then result:=milieu
else result:=-1;
end;
page 87 de 87