TD Asd
TD Asd
Liste de tableaux 1
Préface 1
2 Complexité algorithmique 29
2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.2 Série d’exercices du TD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Exercice 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Exercice 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1
TABLE DES MATIÈRES TABLE DES MATIÈRES
Exercice 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Exercice 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Exercice 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.3 Complexité théorique et temps d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2
TABLE DES MATIÈRES TABLE DES MATIÈRES
Bibliographie 104
3
Préface
L’objectif de ce document est de fournir aux étudiants un support sur lequel ils peuvent s’appuyer pour assimiler
les concepts liés à l’algorithmique avancée. Plus particulièrement, on vise faire apprendre aux étudiants la
meilleure façon d’écrire un algorithme commençant par le choix de la structure de données la plus appropriée à
la résolution d’un problème donné et arrivant à l’écriture des instructions de l’algorithme.
L’un des défis à relever dans ce support est de faire habituer l’apprenant à donner l’intérêt nécessaire à
la phase de choix des structures de données pour la représentation des données à utiliser avant de passer à la
conception de l’algorithme. Ceci est bien détaillé dans les chapitres 3 et4.
Ce support est organisé en quatre chapitres, qui s’articule autours du programme officiel des universités
algériennes pour la deuxième année du tronc commun Mathématique et Informatique (MI). Ces chapitres sont :
– Chapitre 1 : Fonctions procédures et passage des paramètres
– Chapitre 2 : Complexité algorithmique
– Chapitre 3 : Types abstraits de données
– Chapitre 4 : Structures de données linéaires : liste, pile et file
La notion des arbres est présentée dans deux chapitres différents :
– Dans le troisième chapitre, comme TAD à définir, où on détaille la signature de son TAD et on implémente
les opération de base sur les arbres.
– Dans le quatrième chapitre, on présente là le cas particulier de l’arbre binaire à implémenter par des
structures linéaires.
Finalement, il est à noter que les solutions proposées dans ce support sont construite autour des solutions
types faites et proposées par l’équipe de formation du module, supervisée par Pr. F. Belala. Ces solutions ont
été enrichies, détaillées et illustrées par l’auteur. Ainsi, pour reconnaitre l’effort fourni par les collègues, on
mentionne localement la source de l’information.
Un remerciement spécial va aux collègues enseignants du module ASD qui ont contribué à la correction
des solutions proposées dans ce manuscrit, en particulier Dr. Hichem Talbi.
1
Chapitre
1
Fonctions, procédures et récursivité
1.1 Introduction
Ce chapitre traite les concepts liés aux sous-programmes, fonctions et procédures avec plus d’intérêt donné à la
notion de récursivité. De plus, on s’intéresse à faire apprendre à l’étudiant les trois modes principaux de passage
des paramètres : ‘Donnée’, ‘Résultat’ et ‘Donnée/Résultat’.
On présente dans cette section des solutions possibles pour les exercices contenus dans la série du TD 12 .
Exercice 1
1. Écrire un sous-algorithme (procédure ou fonction) qui calcule la somme des éléments positifs d’un tableau
d’entiers de taille N. Préciser le nombre et le type des paramètres considérés.
2. Dérouler la fonction ou la procédure pour des données de votre choix
3. Écrire un algorithme principal qui permet de :
– Créer un tableau T contenant 200 éléments de type entiers
– Faire la somme des éléments positifs de T (utiliser le sous-algorithme donné dans 1).
– Afficher la somme calculée.
Solution :
1. Écrire un sous-algorithme (procédure ou fonction) qui calcule la somme des éléments positifs
d’un tableau d’entiers de taille N. Préciser le nombre et le type des paramètres considérés.
Le sous-programme à proposer peut être soit une fonction ou bien une procédure. Pour chaque cas, on
1
Ce manuscrit est mis à jours périodiquement ; les exercices omis de la série de TD de l’année en cours seront insérés dans la partie
dédiée aux résultats supplémentaires.
2
Presque tous les énoncés sont proposés par Pr. F. Belala
2
Chapitre 1 : Fonctions, procédures et récursivité
commence par déterminer le nombre et le type des paramètres à passer à cette routine3 4 . Il est important de
rappeler que, dans la majorité des cas, si une tâche est faite par une fonction elle peut être aussi accomplie par
une procédure, et vice versa. La seule différence réside dans la façon de passer les données au sous-programme
et la façon de retourner le résultat vers le programme appelant 5 . En effet, l’idée clé est que la fonction retourne
le résultat calculé dans son nom.
Par définition, une fonction doit retourner au moins un résultat. Cependant, certains langages de programmation,
tels que Python et Matlab, permettent de retourner plusieurs résultats par l’instruction ‘Retourner’. Même
pour les langages permettant de retourner un seul résultat, il est possible de retourner plus qu’un résultat en
procédant comme suit : un résultat est passé directement dans le nom de la fonction, alors que les autres, s’il y
en a, sont passés par le biais de variables en mode de passage de paramètres ‘par résultat’.
Passant à la fonction demandée dans la question de l’exercice, SommePositifs ; il est bien clair qu’il y a un
seul résultat à retourner par cette fonction : la somme des éléments positifs du tableau. En ce qui concerne la
définition des paramètres à passer à la fonction, on se pose deux questions :
1. Que doit le programme appelant passer au programme appelé comme donnée(s) pour que ce dernier puisse
accomplir la tâche ?
2. Est il permis au programme appelé de changer les valeurs des données qui sont lui passées ?
La réponse à la première question permet de décider le nombre et le type des paramètres à passer
au programme secondaire, tandis que la réponse à la deuxième question détermine le mode de passage des
paramètres. Dans l’exemple de la question :
1. On doit passer le tableau Tab des entiers comme premier paramètre et la taille N de ce tableau comme
deuxième paramètre. Ce deuxième paramètre est essentiel pour la compilation et l’exécution du sous-
programme. Vu que la taille N du tableau T ab sera utilisée dans un test ou une boucle, elle doit être passée
comme paramètre séparément de la déclaration du tableau. Il est évident que N est la taille de T ab, mais
on doit fournir cette taille à la machine pour qu’elle puisse faire le traitement en question.
2. Le programme appelé utilisera le contenu du tableau pour calculer la somme des nombres positifs, il n’aura
pas besoin de changer son contenu et de garder des changements dedans. Donc, le mode de passage le plus
approprié est le passage par ‘Donnée’. La taille N du tableau T ab est aussi passée par ‘Donnée’.
Ainsi, la signature6 de cette fonction est comme suit :
Fonction sommePositifs (Donnée Tableau Tab de N entiers, Donnée N : entier) : entier
Pour le corps de la fonction, on parcourt le tableau et on compare chacun des élément avec le zéro. Si
l’élément courant est supérieur à zéro, on rajoute la valeur de l’élément à la somme. Une fois on termine le
parcours du tableau, on retourne le résultat. La fonction sommePositifs est déclarée comme suit :
3
routine = sous-programme, sous-algorithme
4
Dans ce manuscrit les termes ‘algorithme’, ‘routine’, ‘code’ at ‘programme’ sont interchangeables.
5
Le programme appelant n’est pas forcement le programme principal ; il peut lui même être un sous-programme.
6
La signature d’un sous-algorithme est composée de son nom, ses paramètres formels et leurs types, et le type de retour dans le cas
d’une fonction.
3
Chapitre 1 : Fonctions, procédures et récursivité
Fin
a
On peut écrire également : somPositifs ← Som
Comme déjà mentionné, tout traitement accomplis par une fonction pourrait être traduit en procédure et vice
versa 7 . Pour ce faire on procède comme suit :
1. De la fonction vers la procédure :
– Le résultat retourné par l’instruction ‘Retourner’ deviendra un paramètre à passer par ‘Résultat’.
– IL n’y a pas un type de retour.
– Les autres paramètres garderont leur mode de passage.
2. De la procédure vers la fonction :
– L’un des paramères passés par ‘Résultat’ (ou ‘Donnée/Résultat’8 ) est choisi pour être retourné par
l’instruction ‘Retourner’9 .
– Le type de retour de la fonction est le type de ce paramètre retourné par la fonction.
– Les autres paramètres garderont leur mode de passage.
On applique donc cette méthode pour convertir la fonction sommePositifs en procédure. Le résultat
retourné par l’instruction ‘Retourner’ est la variable Som, donc cette variable sera passée comme paramète en
mode ‘Résultat’. De plus, l’instruction ‘Retourner’ ainsi que le type de retour de la fonction ne doivent pas
figurer dans la procédure. On obtient la procédure suivante :
7
IL est parfois inutile de convertir une procédure en fonction si la procédure ne fait pas de calcul ; mais ça reste possible dans
certains langages de programmation comme Java qui retourne un type ‘void’ comme type de retourne vide.
8
Voir ce détail dans la partie cours publiée sur la plateforme e-learning de l’université Abdelhamid Mehri - Constantine 2.
9
S’il y a plusieurs paramètres passés par résultats (ou ‘Donnée/Résultat’), on choisira celui qui répond au rôle principal de la
fonction.
4
Chapitre 1 : Fonctions, procédures et récursivité
Procédure sommePositifs(Donnée Tab : Tableau de N entiers, Résultat Som : entier, Donnée N : entier)
Début
Som←0
Pour i ← 1 à N :
Si (T ab(i) ≥ 0) :
Som←Som+T ab(i)
FSi
FPour
Fin
15 -3 -2 0 6
i TabEff(i) Som
1 15 15
2 -3 15
3 -2 15
4 0 15
5 6 21
6 / 21
10
Il est à remarquer que la valeur de i est égale à 6 à ma sortie de la boucle.
5
Chapitre 1 : Fonctions, procédures et récursivité
Exercice 2
Un nombre entier est dit équilibré si l’ordre des chiffres qui le constituent reste le même qu’on le lise de gauche
à droite ou de droite à gauche, comme par exemple : 123321, 6578756 sont des nombres équilibrés. Ecrire la
fonction itérative Equilibre (. . . ) qui teste si un nombre entier donné est équilibré.
Solution :
L’idée de base de la solution proposée ici est de passer le nombre testé dans un tableau à la fonction ‘Equilibre’.
Aussi, il est essentiel de passer la taille de ce tableau, qui indique le nombre de chiffre de cet entier, comme
paramètre. Les deux paramètres sont passés par ‘Donnée’, vu qu’on ne doit garder aucun changement appliqué
par le sous-programme sur leurs valeurs. La solution proposée est la suivante :
6
Chapitre 1 : Fonctions, procédures et récursivité
Exercice 3
D1 = 0
Dn = n ∗ Dn−1 + (−1)n
Solution :
Dans cette structure, on distingue entre les paramètres formels ParsForm, donnés dans la signature de la
fonction, et les paramètres effectifs ParsEff2, utilisés par la fonction si la condition d’arret des appels, cas trivial,
n’est pas vérifiées. Les valeurs des paramètres effectifs ParsEff2 sont également différentes de leaurs valeurs dans
l’appel initial ParsEff1 (fait dans le programme appelant). Le cas trivial représente le point de sortie des appels
récursifs.
On applique maintenant cette façon de raisonnement à notre exercice. Le cas trivial de la formule
‘Dérangement’ est D1 , donc la valeur de n=1. Si la valeur d’entrée est égale à 1, la fonction ‘Dérangement’ retourne
7
Chapitre 1 : Fonctions, procédures et récursivité
comme résultat la valeur 0, sinon, elle applique la deuxième équation de la formule : Dn = n ∗ Dn−1 + (−1)n .
De cette formule, on peut facilement déduire que dans le bloc ‘Sinon’ l’instruction ‘Retourner’ s’applique sur la
valeur n ∗ Dn−1 + (−1)n . Par conséquent, l’appel interne de la fonction Dérangement prend comme paramètre
la valeur n − 1. Remplissant maintenant la structure précédente de la fonction récursive, on obtient :
a
Ce test permet de définir la valeur de (−1)n
FSi
Fin
a
Ce test permet de définir la valeur de (−1)n
FPour
Retourner dn
Fin
Exercice 4
8
Chapitre 1 : Fonctions, procédures et récursivité
Solution :
15 -3 -2 10 6
Étape 1 : passage des paramètres : Les trois paramètres sont passés par ‘Donnée’, la machine dans
ce cas crée dans l’espace réservé au sous-algorithme des copies des variables correspondantes utilisées dans
l’appel et les nomme comme défini dans l’algorithme appelé. La figure 1.1 donne une illustration de ce qui se
passe en mémoire lors du passage des paramètres.
9
Chapitre 1 : Fonctions, procédures et récursivité
…
N5
Ecrire(sommePosRec(Tab_Exemple,1,N))
…
Tab_Exemple
N 5
Tab
N 5 i 1
Une autre façon de traiter ce problème d’une manière récursive est d’exploiter uniquement la taille du
tableau qui change de N à N-1, N-2 · · · , 1. Dans ce cas, les éléments du tableau sont parcouru dans l’ordre
inverse, si l’élément courant est positif on retourne sa valeur plus le résultat d’appel de la fonction pour les
éléments qui restent, sinon on retourne le résultat d’appel de la fonction pour le reste des éléments. La fonction
sommePosRec2 implémente cette idée.
10
Chapitre 1 : Fonctions, procédures et récursivité
Affiche la valeur
31 à l’écran Ecrire(sommePosRec(Tab_Exemple,1,N))
Tab_Exemple
5 N
5 N
i 1
i>N Faux
T(i)≥0 Vrai
Retourner(T(i)+sommePositifsRec(T,i+1,N))
5 N
i 2
i>N Faux
T(i)≥0 Faux
Retourner(sommePositifsRec(T,i+1,N))
Espace réservé au programme appelé sommePosRec
16
T
5 N
i 3
i>N Faux
T(i)≥0 Faux
11
Chapitre 1 : Fonctions, procédures et récursivité
Retourner(sommePositifsRec(T,i+1,N))
Espace réservé au programme appelé sommePosRec
5 N
i 4
16 i>N Faux
T(i)≥0 Faux
Retourner(sommePositifsRec(T,i+1,N))
Espace réservé au programme appelé sommePosRec
i 5
6 5 N
i>N Faux
T(i)≥0 Vrai
Retourner(T(i)+sommePositifsRec(T,i+1,N))
Espace réservé au programme appelé sommePosRec
5 N
i 6
i>N Vrai
0
Retourner(0) /*Cas trivial
12
Chapitre 1 : Fonctions, procédures et récursivité
L’avantage de cette solution par rapport à la précédente est qu’elle utilise deux paramètres au lieu de trois,
chose qui peut créer la différence au moment d’exécution en termes de l’espace de stockage et même le temps
d’exécution 11 . La réponse à la troisième question de l’exercice 1 en utilisant la fonction récursive devient ainsi :
52125
534435
53145
Figure 1.5: Exemples d’un nombre non-équilibrés, test par symétrie récursive
Comme peut être vu des deux figures, on réitère la comparaison de l’élément en cours (à la position i du
tableau) jusqu’à aboutir à l’une des deux configurations suivantes :
11
À cause du temps nécessaire pour le passage des paramètres et la gestion de la mémoire.
13
Chapitre 1 : Fonctions, procédures et récursivité
1. On n’a qu’un seul élément ou zéro élément dans le tableau, on retourne ‘vrai’ dans ce cas.
2. L’élément courant, i, et celui en symétrie avec lui, à la position N-i+1, ne sont pas similaire ; on retourne
‘faux’ dans ce cas.
En utilisant une solution algorithmique, on ne va pas supprimer les éléments du tableau en les ecrasant de
la mémoire, on enlève pas des espaces de stockage. Le tableau restera le même lors de tous les appels récursifs,
comme vu dans les figures 1.1, 1.2 et 1.3. La non-considération d’un élément du tableau est implémentée à
travers la manipulation des paramètres. Par exemple, le tableau dans l’exemple de somePosRec est composé de
deux éléments : l’espace de stockage T et le nombre d’éléments à considérer dans cet espace. Donc, le tableau
lors du premier appel, fait par le programme appelant, est l’union des deux éléments : (T,5) ; il devient lors
du deuxième appel équivalent) l’union (T,4), ..., (T,1). D’une façon pareille, dans la fonction EquilibreRec,
proposée dans la suite, on traite le tableau comme un triplet : (T,i,N) : (T,1,5) lors du premier appel, et puis
(T,2,4), T(3,3). La condition ‘le nombre d’éléments est égal à 1 ou 0’ est exprimée par : i=(N+1) div 2 12 . La
solution sera donc :
L’appel de cette fonction se fera initialement comme suit : ‘x←EquilbreRec(T,1,N)’, i=1 dans ce cas, il
change à chaque appel récursif, mais N est fixé à 5. Une autre solution possible utilisant deux indices pour
parcourir le tableau. En plus de i, on utilise j qui représente l’indice obtenu par symétrie. La solution est la
suivante :
a
Cette deuxième condition indique qu’on a plus d’éléments dans le tableau, par contre la première est pour le cas ou on a a
un seul élément dans le tableau.
FSi
Fin
12
L’opérateur div est pour la division naturelle
14
Chapitre 1 : Fonctions, procédures et récursivité
La deuxième condition utilisée dans le cas trivial indique que tous les éléments sont parcourus. L’appel
initial affect 1 à i et N à j. Contrairement à la version précédente, j change sa valeur ici d’une façon décrémentale.
Exercice 5
1. Écrire une fonction récursive permettant de déterminer le maximum de deux entiers positifs donnés (sans
utiliser l’opération de comparaison).
2. Dérouler la fonction pour les deux nombres 3 et 7.
Solution :
1. Ecrire une fonction récursive permettant de déterminer le maximum de deux entiers positifs
donnés (sans utiliser l’opération de comparaison).
Une solution possible est de soustraire récursivement ‘un’, 1, des deux éléments et d’appeler à nouveau
la fonction Maximum en lui passant les résultats de cette soustraction jusqu’à aboutir à l’annulation de l’un
des deux. L’autre nombre, en lui ajoutant des uns avec le même nombre d’appels récursifs, est le résultat de
la comparaison, voir l’exemple de déroulement donnée dans la réponse à la question 2. La fonction récursive
résultant est la suivante :
15
Chapitre 1 : Fonctions, procédures et récursivité
Exercice 6
Le nombre de partitions d’un entier naturel p (6=0) en q (6=0) parties est défini par :
Si p>q, NP(p, q) = NP(p-1, q-1) + NP(p-q, q)
Si p < q, NP(p, q)= 0
NP(p, p)= NP(p, 1)= 1
Par exemple, les partitions de 5 en 3 parties sont : 3+1+1, 2+2+1.
1. Ecrire une fonction récursive NP ( ? ?) qui calcule le nombre de partitions d’un entier naturel p en q parties.
2. Dérouler la fonction récursive NP pour p=5 et q=3
Solution :
1. Ecrire une fonction récursive NP ( ? ?) qui calcule le nombre de partitions d’un entier naturel
p en q parties.
La formule décrivant le problème pourra être directement utilisée pour définir le (les) cas trivial (triviaux) et les
paramètres des appels récursifs.
• Cas triviaux :
Selon la définition donnée, on arrête les appels récursif dans l’un des trois cas suivants :
– Si p<q, on retourne 0.
– Si p=q, on retourne 1.
– Si q=1, on retourne 1.
16
Chapitre 1 : Fonctions, procédures et récursivité
Tant qu’aucun cas trivial n’est atteint, on appelle la fonction ‘NP’ comme donnée par la formule : nbrPart(p,
q) = nbrPart(p-1, q-1) + nbrPart(p-q, q)
En considérant ces deux points, on définit la fonction calculant le nombre de partitions de p en q partitions
comme suit :
Figure 1.7: Déroulement de la fonction nbrPart pour p=5 et q=3 (Proposée par Pr. F. Belala)
1. Écrire une fonction récursive Maximum (T, · · · ) qui détermine le maximum d’un tableau T de N réels.
2. Soit une suite S numérique de N nombres. On suppose l’existence des fonctions suivantes :
– Reste (S) : qui retourne le reste des éléments de la suite S sans son premier élément.
– Elem (S) : qui retourne le premier élément de S.
– Vide ?(S) : qui teste si la suite S ne contient aucun élément.
17
Chapitre 1 : Fonctions, procédures et récursivité
– Proposez une fonction récursive Maximum (S) qui retourne le nombre maximal de cette suite en utilisant
ces fonctions.
3. Donnez l’implémentation des 3 fonctions précédentes avec une représentation interne de la suite par un
tableau de N entiers. Commenter les résultats.
Solution :
1. Écrire une fonction récursive Maximum(T, · · · ) qui détermine le maximum d’un tableau T de
N réels.
2. Soit une suite S numérique de N nombres. On suppose l’existence des fonctions suivantes :
– Reste(S) : qui retourne le reste des éléments de la suite S sans son premier élément.
– Elem(S) : qui retourne le premier élément de S.
– Vide ?(S) : qui teste si la suite S ne contient aucun élément.
Proposez une fonction récursive Maximum (S) qui retourne le nombre maximal de cette suite
en utilisant ces fonctions.
L’implémentation, des 3 fonctions déclarées, dépend de la représentation interne de la suite numérique,
ainsi à la question 2, nous avons donné uniquement les entêtes de ces 3 fonctions. Maintenant, avec une
représentation interne de la suite numérique par un tableau de N entiers nous pouvons les implémenter comme
suit :
Pour la structure de données :
Type : suite-numérique = Tableau de N entier
ou bien :
Type : suite-numérique = enregistrement
(
N : entier
Tab : Tableau de N entiers
Fin
Pour les fonctions :
18
Chapitre 1 : Fonctions, procédures et récursivité
a
On suppose que les éléments sont stockés dans l’ordre inverse. Reste élimine le dernier élément de la suite.
a
La fonction Taille est supposée prédéfinie. Elle restourne le nombre d’éléments d’un tableau.
Fin
ab
Fonction Reste(Donnée S : suite-numérique, Donnée DebSuite : entier ) : suite-numérique, entier
DebSuite ← DebSuite+1
Retourner S, DebSuite
Fin
a
Le paramètre DebSuite indique le début de la suite dans le tableau contenant ses éléments.
b
La suite est l’ensemble du tableau plus la position du premier élément dans ce tableau.
19
Chapitre 1 : Fonctions, procédures et récursivité
La fonction Maximum (de la question 2) reste inchangée quelque soit l’implémentation de la suite
numérique, c’est une solution générale et générique ; seules les fonctions de base dépendent de l’implémentation.
Ainsi, cette fonction Maximum générique peut être réutilisée pour d’autres implémentations de la suite numérique
(e.g : une liste ou une pile chainées), alors que la fonction Maximum de la question 1 est complètement dépendante
de l’implémentation.
Dans cette section, on présente quelques exercices supplémentaires dont leur résolution permet plus de maitrise
des concepts liées aux passages des paramètres et à la récursivité. Les solutions proposées dans ces sections ne
seront pas détaillées, en effet on ne vise pas donner des réponses type, on stresse le but de chaque exercice dans
sa solution.
Exercice 8
On considère, pour effectuer la recherche du plus grand élément dans un tableau de N éléments entiers, la
recherche séquentielle et la recherche dichotomique. La recherche dichotomique du plus grand élément d’un
tableau T qui contient n éléments se fait ainsi :
Si T contient un seul élément :
C’est fini ;
Sinon :
– Couper T en deux tableaux T1 et T2 de taille "presque" identiques
– Chercher m1, le max de T1
– Chercher m2, le max de T2
– Retourner le max de m1 et m2
20
Chapitre 1 : Fonctions, procédures et récursivité
Solution :
L’idée de base de l’algorithme de recherche du maximum par dichotomie (A ne pas confondre avec le tri
dichotomique) est de subdiviser le tableau en deux à chaque fois et de comparer le maximum des deux parties.
Ce processus est répété jusqu’à obtenir un seul élément dans le tableau. La figure 1.8 résume ce principe.
98
56 6 8 98 54 -2 7
98
56
56 6 8 98 54 -2 7
56 8 98 7
56 6 8 98 54 -2 7
6 8 98 54 -2 7
6 8 98 54 -2 7
Une manière simple de résoudre ce problème est de créer à chaque appel deux nouveaux tableaux vers
lesquels les parties gauche et droite sont recopiées respectivement. Cette solution n’est pas pratique pour deux
raisons :
• Si le tableau initial est assez grand, on aura besoin d’un grand espace mémoire pour permettre l’exécution
du programme.
• Le temps d’exécution aussi augmente considérablement.
On peut faire face à ces limites par la bonne utilisation des paramètres. En effet ; au lieu de créer à chaque
fois deux sous-tableaux, on utilise le même tableau lors de l’appel, mais avec deux paramètres indiquant où le
sous tableau est situé. La figure 1.9 illustre cette idée, où d est l’indice du début du tableau en cours, alors que
f représente l’indice de la fin.
21
Chapitre 1 : Fonctions, procédures et récursivité
1
D=1
F=7
D=2
56 6 8 98 54 -2 7 F=2 56 6 8 98 54 -2 7
D=1 D=3
F=3 56 6 8 98 54 -2 7 F=3 56 6 8 98 54 -2 7
2
D=4 56 6 8 98 54 -2 7
F=7 D=4
56 6 8 98 54 -2 7
F=4
4
D=1 D=5
F=1 56 6 8 98 54 -2 7 F=5 56 6 8 98 54 -2 7
D=2 56 6 8 98 54 -2 7
F=3 D=6
F=6 56 6 8 98 54 -2 7
3
D=4
F=5 56 6 8 98 54 -2 7
D=7
F=7 56 6 8 98 54 -2 7
D=6 56 6 8 98 54 -2 7
F=7
On peut passer comme paramètres à la fonction récursive rechMaxDicho, en plus du tableau T, deux
autres éléments marquant respectivement l’adresse du début et celle de la fin. Comme vu dans l’illustration de
la figure 1.9, tous les appels récursifs travaille sur des tableaux de même taille et de même contenu, mais la
subdivision se fait logiquement et pas physiquement. La fonction récursive devient ainsi :
Fonction rechMaxDicho(Donnée T : Tableau de N entiers, Données indDeb : entier, Donnée indFin : entier) : réel
Si (indDeb=indFin) :
Retourner T(indDeb)
Sinon
Si (rechMaxDicho(T, indDeb,(indDeb+indFin) div 2) > rechMaxDicho(T,((indDeb+indFin) div 2) + 1,
indFin)) :
Retourner rechMaxDicho(T, indDeb,(indDeb+indFin) div 2)
Sinon
Retourner rechMaxDicho(T, ((indDeb+indFin) div 2) + 1, indFin)
FSi
FSi
Fin
22
Chapitre 1 : Fonctions, procédures et récursivité
Exercice 9
1. Écrire une fonction récursive nbrChiffres qui calcule le nombre de chiffres d’un nombre entier positif. Par
exemple, nbrChiffres(1203) = 4.
2. Donner l’image mémoire relative à l’exécution de nbrChiffres(1203).
3. Traduire cette fonction en C.
Solution :
N= 1203 N= 120 N= 12 N= 1
N= 1203
nbrChiffres=4 nbrChiffres=3 nbrChiffres=2 nbrChiffres=1
4
@retour 3 2 1
@retour 1 @retour 2 @retour 3
r
Listing 1.1: Code C de la fonction qui calcule le nombre de chiffres d’un entier
1 #include <stdio.h>
2 #include <math.h>
3 #include <stdbool.h>
4
5 int nbrChiffres(int n)
6 {
7 if (n<=9)
8 return(1);
9 else
10 n=n/10;
11 return(1 + nbrChiffres(n));
12 }
13
14 int main() {
23
Chapitre 1 : Fonctions, procédures et récursivité
15 int nt;
16 printf("Entrer le nombre n: ");
17 scanf("%d", &nt);
18 printf("Le nombre de chiffres de cet entier est: %d",nbrChiffres(nt));
19 return 0;
20 }
Exercice 10
1. Écrire une fonction itérative qui teste si un nombre est premier ou non.
2. Écrire une fonction récursive qui teste si un nombre est premier ou non.
Solution :
Exercice 11
Soit l’algorithme, dû à Euclide, qui détermine le PGCD de 2 nombres naturels A et B. Si un des nombres est
nul, l’autre est le PGCD, sinon il faut soustraire le plus petit du plus grand et laisser le plus petit inchangé.
Puis, recommencer ainsi avec la nouvelle paire jusqu’à ce que l’un des deux nombres soit nul. Dans ce cas,
l’autre nombre est le PGCD.
24
Chapitre 1 : Fonctions, procédures et récursivité
Solution :
Exercice 12
1. Écrire une fonction récursive qui permet la conversion d’un entier du décimal en binaire.
2. Généraliser cette solution pour la conversion vers d’autres bases.
25
Chapitre 1 : Fonctions, procédures et récursivité
Solution :
Exercice 13
1. Écrire une fonction récursive qui reçoit un entier n et retourne la valeur A(n) selon la formule suivante :
A(0) = 1
B(0) = 0
A(n) = n − B(A(n − 1)), Sin > 0
B(n) = n − A(B(n − 1)), Sin > 0
26
Chapitre 1 : Fonctions, procédures et récursivité
Le déroulement de ce code pour la valeur N=3 est donnée dans la figure 1.11.
27
Chapitre 1 : Fonctions, procédures et récursivité
1.4 Conclusion
Dans ce chapitre, on a traité les fonction, les procédures, le passage des paramètres et la récursivité à travers la
résolution de treize exercices. Les solutions proposées ont été détaillée, mais pour augmenter le bénéfice des
étudiants, on présente la traduction de ces solutions en langages de programmation (C, python, etc.) dans
l’annexe 1.
28
Chapitre
2
Complexité algorithmique
2.1 Introduction
Ce chapitre traite la notion de complexité temporelle des algorithme. L’analyse d’algorithmes est détaillée à
travers cinq exercice qui présentent les cas souvent rencontrés. Les trois notation O, T heta et Ω sont aussi
expliquées dans l’un des exercice où la complexité au pire des cas est abordée.
En plus, une section a été dédiée à la comparaison du temps théorique (ordre de grandeur de la complexité)
au temps réel pour deux algorithmes implémentés en langage de programmation Matlab.
Les exercices proposés dans cette section ont été proposés initialement par Pr. F. Belala. Les tableaux résumant
les solutions on été donnés par Pr. F. Belala, tandi que l’organisation, les explications et les illustrations
présentée ici sont celles de l’auteur.
Exercice 1
Algorithme exComp1
Déclarations :
···
Début
I1 i←1
I2 TQ (i≤N) :
I3 j←1
I4 TQ (j<i) :
I5 j←j+1
FTQ
I6 i←i+1
FTQ
Fin
29
Chapitre 2 : Complexité algorithmique
Solution :
D’une façon similaire à l’exercice précédent, on commence par donner une table (Table 2.1) qui résume le calcul
de la complexité de l’algorithme exComp2 ; et puis on détaille ces calculs.
Complexité de l’instruction I1 :
L’instruction I1 s’exécute une seule fois, son coût est égale à θ(1). Il est important de distinguer entre les
notations θ et O. La première représente une estimation ; par exemple θ(1) représente une estimation du temps
nécessaire pour une seule opération élémentaire et θ(n) est une estimation du temps nécessaire pour effectuer n
opérations élémentaires. D’autre part, O exprime l’ordre de grandeur, O(1) représente donc toute complexité
constante et O(n) représente une complexité d’ordre polynomial. Par conséquent, on utilise dans ce support
la notation θ quand on calcule le nombre (approximatif) d’instructions de l’algorithme et on fait appel à la
notation O quand on passe à l’analyse de l’ordre de grandeur d’une portion de code.
La complexité de la boucle I2 (TQ · · · FTQ) est le résultat de la somme de la complexité du test i≤N et
la complexité des instructions internes ( I3 , I4 (son test + I5 ) et I6 ). Pour calculer la complexité d’un
algorithme, on commence par le calcul du nombre d’instructions et on passe ensuite à mesurer l’ordre de
grandeur.
→ Nombre de test dans la boucle TQ i≤N :
Le test de la boucle s’exécute N+1 fois. On observe que i prend la valeur initiale 1, s’incrément par 1 à
chaque itération et sa valeur finale est fixée à N+1. Il est important à noter qu’on doit bien savoir comment la
variable de contrôle de la boucle change ses valeurs, il n’y a pas donc une formule universelle pour calculer la
complexité d’une boucle. Le nombre d’opérations dans la clause de test est donc égal à (N+1) opérations.
→ Nombre de fois d’entrée dans la boucle TQ · · · FTQ :
Pour la boucle TQ, le nombre de fois d’exécutions du bloc interne est une fois moins le nombre d’exécution du
test. Si le test s’exécute K fois, on rentre dans la boucle K-1 fois. Dans cette première boucle, le test s’exécute
(N+1) fois, donc le bloc interne s’exécute N fois. Attention, juste multiplier la complexité des instructions
internes par N ne donne pas toujours le bon résultat, car N entrées dans la boucle n’implique pas forcément une
multiplication par N (voir exemple de l’exercice 2).
30
Chapitre 2 : Complexité algorithmique
Complexité de l’instruction I3 :
L’instruction I3 fait une seule opération élémentaire, elle se répète dans la boucle N fois ; donc elle fait
globalement N opérations élémentaire.
Complexité de l’instruction I6 :
L’instruction I6 exécute une opération élémentaire et s’exécute dans la boucle I2 qui s’exécute N fois.
Le coût de l’algorithme exComp1 est la somme des complexité des différentes instructions qui le composent. On
a donc :
CoûtexComp1 (N ) = θ(1)∗[1+(N +1)+N +(N ∗(N +1)/2)+(N ∗(N −1)/2)+N ] = θ(1)∗(N 2 +3N +1) ' O(N 2 ).
Exercice 2
31
Chapitre 2 : Complexité algorithmique
Algorithme exComp2
Déclarations :
···
Début
I1 i←1
I2 TQ (i≤N) :
I3 j←1
I4 TQ (j≤M) :
I5 j←j*2
FTQ
I6 i←i+1
FTQ
Fin
Solution :
La table 2.2 résume le processus de calcul de complexité pour cet algorithme. Comme vu dans la table, il y a
une variable additionnelle k. Cette variable est utilisée pour exprimer le nombre de fois d’exécution du test du
bloc de la boucle I2, ainsi que le nombre de fois on rentre dans la boucle. On détaille dans la suite le processus
de calcul du coût de l’algorithme exComp2.
Complexité de l’instruction I1 :
La variable i s’incrémente régulièrement de 1 à N+1. Donc, le bloc d’instructions I2 fait N+1 tests et rentre
dans la boucle N fois. Le code délimité par cette boucle s’exécute N fois. Mais il faut faire attention que le fait
d’exécuter un code N fois n’implique pas une opération de multiplication simple sera suffisante pour calculer sa
complexité. On garde dans la tête ici que le bloc I2 consiste en N+1 tests et N entrée à la boucle.
Complexité de l’instruction I3 :
Cette instruction contient une seule opération élémentaire et s’exécute N fois. Sa complexité est donc θ(1) ∗ N .
32
Chapitre 2 : Complexité algorithmique
la variable j prend la valeur initiale 1. A chaque entrée à la boucle (quand le test est ‘vrai’), la valeur de j sera
doublée. Donc, la variable j prend les valeurs 1, 2, 4, · · · , bSup. La valeur bSup est la première puissance de 2
strictement supérieure à M.
Par exemple : si M=17 ; j prend les valeurs : 1,2, 4, 8, 16, 32.
Donc, il est claire que le nombre de fois de tests et d’entrée dans cette boucle n’est pas M+1 ou M comme la
boucle du bloc I2 .
Nombres de fois de tests et d’entrée à la boucle du bloc I4 :
A l’itération 1, → j=1
A l’itération 2, → j= 1*2
A l’itération 3, → j= 1*2*2
A l’itération k, → j= 1*2k-1
...
A la dernière itération,→ j= M ; d’où : M = 2( k − 1) k = 1 + Log2 (M ).
Pour être plus précis k = 1 + P artieEntière(Log2 (M )).
La fonction PartieEntère (symbolisée par bc) donne la valeur entière d’un nombre réel.
On a k+1 tests (car obn a un test en plus après la dernière itération) et K entrée à la boucle et le traitement
fait dans l’instruction I5 est le même. Tout ce bloc s’exécute N fois dans le bloc de la boucle externe ( I2 ).
Donc, Il y a : N ∗ (1 + bLog2 M c + 1) opérations de test (dans I4 ) et N ∗ (1 + bLog2 M c) opérations dans
le code interne (dans I5 ).
Complexité de l’instruction I6 :
Le coût de l’algorithme exComp2 est la somme des complexité des différentes instructions qui le composent. On
a donc :
CoûtexComp2 (M, N ) = θ(1) ∗ (2 + 6N + 2N bLog2 M c) ' O(N blog2 M c)
Exercice 3
On considère l’algorithme exComp3 suivant, sachant que l’ordre de grandeur du temps d’exécution de la
procédure Action est O(N ), déterminer celui de exComp3.
33
Chapitre 2 : Complexité algorithmique
Algorithme exComp3
Déclarations :
Procédure Action (Données a, b : entier, Résultat p : entier)
i, j , p, M, N : entier
Début
I1 Pour i ← 1 à N :
I2 Pour j ← 1 à M :
I3 Action(i,j,p)
I4 Ecrire (p)
FPour
FPour
Fin
Solution :
La complexité de l’algorithme exComp3 est la somme des complexités des instructions et blocs d’instructions
qui le composent. Donc, Coût_exComp3(N,M)=Coût( I1 )+Coût( I2 )+Coût( I3 )+Coût( I4 ).
La table 2.3 résume le processus de calcul de complexité pour cet algorithme. On détaille dans la suite ce
calcul.
Instruction/Bloc Coût
I1 (N+1)* θ(1)
I2 N*(M+1)*θ(1)
I3 N*M*θ(N)
I4 N*M*θ(1)
CoûtexComp3 (M, N ) ' O(N 2 ∗ M )
La boucle pour s’exécute d’une manière similaire à une boucle TQ, avec la simple différence que le test est
l’incrémentation de la variable de contrôle i de la condition d’arrêt se fait dans la clause de test. Dans cette
clause, on a une application itérative d’un test de condition, une entrée à la boucle, suivi par une incrémentation
de l’indice i, jusqu’à aboutir à la valeur N+1. Donc, comme conventionné dans le cours de ce module, le test de
cette boucle exécute N+1 opérations. On a donc N entrées à la boucle.
D’une façon pareille, ce bloc d’instructions fait (M+1) opérations de test, et vu que le même bloc s’exécute de
la même manière à chaque itération de la boucle externe, on obtient donc : N*(M+1) opérations de test.
Cette instruction contient un appel à la procédure Action qui reçoit les variable i, j et p comme paramètres. Il
est donné par l’énoncé que la complexité de cette procédure est O(N ). Une question qui se pose donc est : ‘Si
la complexité d’une portion de code s’exprime en terme de la taille des entrées, devrons-nous exprimer cette
complexité en terme de i, j ou p ?’.
34
Chapitre 2 : Complexité algorithmique
Il est à rappeler que le N est supposé représenter la taille des entrées et non pas les valeurs des entrées
elles-mêmes. Dans les algorithmes des exercices précédents, la taille de la donnée était elle-même sa valeur, mais
rien n’empêche que ça ne soit pas vrai. Par exemple, N dans notre exemple peut représenter la taille de p, qui
pourrait être un tableau, une matrice, etc.
La complexité de cette instruction isolée est O(N ). Puisque I3 se répète M fois dans le bloc I2 qui se
répète lui-même N fois dans le bloc I1 ; on obtient : Coût( I3 )=N*M*θ(N).
Cette instruction est une opération élémentaire. Elle se répète M fois dans le bloc I2 qui se répète lui-même N
fois dans le bloc I1 ; on obtient : Coût( I3 )=N*M*θ(1).
Le coût de l’algorithme exComp3 est la somme des complexité des différentes instructions qui le composent. On
a donc :
CoûtexComp3 (M, N ) = θ(1) ∗ ((N + 1) + N ∗ (M + 1) + (N ∗ M ∗ N ) + N ∗ M ) ' O(N 2 ∗ M )' O(N 3 )
Exercice 4
Evaluez la complexité maximale (au pire des cas : T[i] > Mem toujours vérifiée) du morceau de code suivant :
Algorithme exComp4
Déclarations :
···
Début
I1 Pour k ← 2 à N-1 :
I2 Mem=T[k]
I3 i←k-1
I4 TQ (i ≥1 et T[i] > Mem ) :
I5 T[i+1]←T[i]
I6 i←i-1
FTQ
I7 T[i+1]←Mem
FPour
Fin
Solution :
La complexité de l’algorithme exComp4 est la somme des complexités des instructions et blocs d’instructions
qui le composent.
La table 2.4 résume le processus de calcul de complexité pour cet algorithme. On détaille dans la suite ce
calcul.
35
Chapitre 2 : Complexité algorithmique
Instruction/Bloc Coût
I1 (N-1)* θ(1)
I2 (N-2)*θ(1)
I3 (N-2)*θ(N)
I4 (N+1)(N-2)/2*θ(1)
I5 (N-1)(N-2)/2*θ(1)
I6 (N-1)(N-2)/2*θ(1)
I7 (N-2)*θ(1)
CoûtexComp3 (M, N ) = θ(1) ∗ (3N 2 /2 − N/2 − 6) ' O(N 2 )
Cette boucle fait (N-1) opérations élémentaires. Sa complexité est donc (N-1)θ(1). Le nombre de fois d’entrée à
la boucle est donc (N-2).
Complexité de l’instruction I2 :
Cette instruction exécute une opération élémentaire à chaque entrée à la boucle, donc, sa complexité est égale à
(N-2)θ(1).
Complexité de l’instruction I3 :
Il s’agit d’une incrémentation qui est estimée par une opération élémentaire. La complexité de cette instruction
est égale à (N-2)θ(1).
Avant de discuter la complexité de cette instruction, il est utile de rappeler la différence entre les trois annotation
rencontrées dans le domaine de calcul de complexité algorithmique : O, Ω et Θ. On résume la différence/relation
entre ces notations dans ce qui suit : 1
– f (n) est O(g(n)) ssi ∃ c ∈ R ∃ n0 ∈ N, f (n) ≤ cf (n) pour tout n ≥ n0 .
– f (n) est Ω(g(n)) ssi ∃ c ∈ R ∃ n0 ∈ N, f (n) ≥ cf (n) pour tout n ≥ n0 .
– f (n) est Θ(g(n)) ssi f (n) est O(g(n)) et f (n) est Ω(g(n)).
Pour simplifier, on peut dire que :
– f (n) est O(g(n)) signifie que g(n) décrit la borne supérieure de f (n).
– f (n) est Ω(g(n)) signifie que g(n) décrit la borne inférieure de f (n).
– f (n) est Θ(g(n)) signifie que g(n) décrit la borne exacte / l’approximation de f (n).
1
https ://[Link]/data-structures-and-algorithms/content/analysis/[Link]
https ://[Link]/data-structures/aysmptotic-notations, https ://[Link]/big-o-omega-theta/
https ://[Link]/dsa/asymptotic-notations
36
Chapitre 2 : Complexité algorithmique
Projetant ces définitions sur les notions vues dans le cours et le TD du module ASD, on peut dire que : O
représente la complexité au pire des cas, Ω représente la compléxité au méilleur des cas et Θ est la complexité
approximative. Dans la plupart des cas, il serait plus utile à comparer les algorithmes à la base de la complexité
au pire.
Revenant à notre exercice, on trouve qu’il est supposé que la condition T[i] > Mem est toujours vrai, donc
le test ‘i ≥ 1 etT[i] > Mem’ sera équivalent à la condition ‘i ≥ 1’. Il s’agit donc de calculer la complexité au pire,
c’est ce qu’on a fait tout au long ce chapitre.
Complexité de I4 :
La variable i reçoit comme valeur initiale k-1 et est décrémentée jusqu’à arriver à la valeur 0 d’une façon
régulière. Donc, on a (k+1) tests et K entrées à la boucle. Il est à noter que la valeur de k change de 2 à (N-1).
On analyse selon ces valeurs le nombre de fois de test et le nombre de fois d’entrée à la boucle :
Pour k=2, i prend les valeurs 1, et 0. Donc, on obtient 2 tests et 1 exécution des instructions I5 et I6 .
Pour k=3, i prend les valeurs 2, 1, et 0. Donc, on obtient 3 tests et 2 exécutions des instructions I5 et I6 .
Pour k=4, i prend les valeurs 3, 2, 1, et 0. Donc, on obtient 4 tests et 3 exécutions des instructions I5 et I6 .
···
Pour k=N-1, i prend les valeurs (N-1), (N-2), · · · 0. Donc, on obtient ()N-1) tests et (N-2) exécutions des
instructions I5 et I6 .
On a donc [2+3+· · · (N-1)] tests et [1+2+3+· · · (N-2)] entrées à la boucle. La complexité de la clause de test,
I4 , est donc : θ(1)*((N-2)*(N+1)/2).
Les instructions I5 et I6 s’exécutent ((N-2)*(N-1)/2) fois. La comlexité de chacune est donc ((N-2)*(N-
1)/2)*θ(1).
Complexité de l’instruction I7 :
Le nombre de fois d’exécution de cette instructions est égal à (N-2). Sa complexité est égale à (N-2)*θ(1).
Le coût de l’algorithme exComp3 est la somme des complexité des différentes instructions qui le composent. On
a donc :
CoûtexComp4 (M, N ) = θ(1) ∗ ((N − 1) + (N − 2) + (N − 2) + (N + 1)(N − 2)/2 + (N − 1)(N − 2)/2 + (N −
1)(N − 2)/2 + (N − 2)) = θ(1) ∗ (3N 2 /2 − N/2 − 6)' O(N 2 )
Exercice 5
On considère, pour effectuer la recherche du plus grand élément dans un tableau de N éléments entiers,
la recherche séquentielle et la recherche dichotomique. On s’intéresse à déterminer la complexité des deux
algorithmes.
37
Chapitre 2 : Complexité algorithmique
NB : Le principe de la recherche du maximum d’un tableau par dichotomie est déjà expliqué dans l’énoncé de
l’exercice 8 du chapitre 1 (page 20).
Solution :
On commence par analyser la complexité temporelle de la recherche séquentielle du maximum dans un tableau.
La fonction rechSeq, donnée ici, représente la version la plus simple de cette recherche.
Fonction rechMaxDicho(Donnée T : Tableau de N entiers, Données indDeb : entier, Donnée indFin : entier) : réel
I1 Si (indDeb=indFin) :
I2 Retourner T(indDeb)
Sinon
I3 Si (rechMaxDicho(T, indDeb,(indDeb+indFin) div 2) > rechMaxDicho(T,((indDeb+indFin) div 2)+1, indFin))
:
I4 Retourner rechMaxDicho(T, indDeb,(indDeb+indFin) div 2)
Sinon
I5 Retourner rechMaxDicho(T, ((indDeb+indFin) div 2)+1, indFin)
FSi
FSi
Fin
Le calcul de la complexité d’une fonction récursive se fait de la même façon que celui d’une fonction
itérative. Il faut juste faire attention que lors d’exécution, on traite l’appel récursif comme étant un appel à un
sous-programme, comme déjà vu dans l’exercice 3 (l’appel à la procédure Action).
L’analyse de la fonction rechMaxDicho pour calculer sa complexité temporelle consiste en le calcule des
coûts des instruction et blocs d’instructions I1 – I5 . Il est naturel que l’analyse des instructions comprenant
38
Chapitre 2 : Complexité algorithmique
l’appel récursif se fait récursivement jusquà obtenir une valeur de coût finale.
En plus des outils vus dans les exercices précédents, on pourra se faire aider par une illustration de calcul
de la complexité pour des tailles données des entrées, ce qui permettra de généraliser. Ensuite, on prouve la
formule trouvée, par récurrence par exemple.
Pour un tableau d’un seul élément : la condition du cas trivial (’indDeb=indFin’) est vérifiée. Donc, en
plus de l’opération de test trouvée dans l’instruction I1 , l’algorithme exécute l’instruction I2 qui contient une
seule opération élémentaire. On obtient donc pour un tableau d’un seul élément une complexité égale 2*θ(1).
Pour un tableau de deux éléments : on comptabilise les opérations suivante :
– un test dans l’instruction I1 , qui donne une valeur égale à ‘faux’.
– un test dans l’instruction I3 . Ce test contient deux appels de rechMaxDicho pour deux tableaux
contenant chacun un seul élément. Ces deux appels nécessitent chacun un temps d’exécution égal àθ(1),
comme juste calculé. Ça donne donc (2+2+1) opérations, i.e. un coût égale à 5*θ(1).
– l’exécution de l’une des deux instructions I4 ou I5 . On suppose que la valeur calculée dans le test
reste stockée dans le cache, on ne comptabilise donc que l’opération retourner. Ceci donne θ(1) comme
coût pour I4 et le même coût pour I5 .
Le coût de la fonction rechMaxDicho pour un tableau à deux éléments est donc égal à (1+5+1)*θ(1)=7*θ(1).
Pour un tableau de trois éléments : on comptabilise les opérations suivante :
– un test dans l’instruction I1 , qui donne une valeur égale à ‘faux’.
– un test dans l’instruction I3 . Ce test contient deux appels de rechMaxDicho pour deux tableaux ayant
comme tailles 1 et 2. Ces deux appels nécessitent 2 et 7 opérations respectivement (2 pour un tableau de
taille 1 et 7 pour un tableau de taille 2). Ça donne donc (2+7+1) opérations, i.e. un coût égale à 10*θ(1).
– l’exécution de l’une des deux instructions I4 ou I5 . On suppose que la valeur calculée dans le test
reste stockée dans le cache, on ne comptabilise donc que l’opération retourner. Ceci donne θ(1) comme
coût pour I4 et le même coût pour I5 .
Le coût de la fonction rechMaxDicho pour un tableau à trois éléments est donc égal à (1+10+1)*θ(1)=12*θ(1).
Pour un tableau de quatre éléments : Le coût de la fonction rechMaxDicho pour un tableau à quatre
éléments est égal à (1+15+1)*θ(1)=17*θ(1).
On observe donc pour différentes tailles du tableau T, les coût de la fonction rechMaxDicho sont comme suit :
Pour Taille(T)=1 : la complexité de rechMaxDicho est 2 ∗ θ(1) = θ(1) ∗ (5 ∗ (1 − 1) + 2)
Pour Taille(T)=2 : la complexité de rechMaxDicho est 7 ∗ θ(1) = θ(1) ∗ (5 ∗ (2 − 1) + 2)
Pour Taille(T)=3 : la complexité de rechMaxDicho est 12 ∗ θ(1) = θ(1) ∗ (5 ∗ (3 − 1) + 2)
Pour Taille(T)=4 : la complexité de rechMaxDicho est 17 ∗ θ(1) = θ(1) ∗ (5 ∗ (4 − 1) + 2)
Une formule générale émerge :
Pour Taille(T)=N : la complexité de rechMaxDicho est θ(1) ∗ (5 ∗ (N − 1) + 2)
On doit ensuite prouver cette formule. On opte pour la preuve par récurrence :
1. La formule est vraie pour N=1.
2. On suppose la formule vraie pour N et on vérifie sa vérité pour N+1. Pour un tableau de taille égale à N+1,
on aura 5 opérations en plus par rapport au cas du tableau de taille N : le cas trivial sera temporisé par une
39
Chapitre 2 : Complexité algorithmique
Les deux fonctions rechSeq et rechMaxDicho, malgré qu’elles ont des complexités approximatives différentes,
ont le même ordre de grandeur de complexité, O(N ). Ceci est équivalent à dire que ‘Quand la taille du tableau
tend vers l’infini, les fonctions rechSeq et rechMaxDicho nécessitent presque le même temps pour être exécutées’.
Il est à noter que cette équivalence d’ordre de grandeur de complexité n’est pas toujours vraie.
Une illustration de l’influence de la complexité algorithmique sur le temps réel d’exécution est donnée dans
cette section. Soit les deux procédures A, B définies dans les deux pseudo-codes suivants :
40
Chapitre 2 : Complexité algorithmique
41
Chapitre 2 : Complexité algorithmique
46 xlabel(‘Taille du tableau’);
47 ylabel(‘Temps’);
48 legend(‘Reelle’,‘Theorique’);
Table 2.5: temps d’exécution pour chaque taille du tableau pour les fonctions A,B et C en Matlab
Procédure A Procédure B
N tempsExe (mS) N tempsExe (mS)
1 1.21 1 1.25
1 ∗ 105 1.38 1 ∗ 102 0.88
5 2
2 ∗ 10 1.06 2 ∗ 10 0.77
5 2
3 ∗ 10 1.45 3 ∗ 10 0.86
4 ∗ 105 2.72 4 ∗ 102 1.95
5 2
5 ∗ 10 2.31 5 ∗ 10 2.11
5 2
6 ∗ 10 3.35 6 ∗ 10 2.98
7 ∗ 105 3.62 7 ∗ 102 4.09
5 2
8 ∗ 10 3.74 8 ∗ 10 3.20
5 2
9 ∗ 10 4.44 9 ∗ 10 1.75
10 ∗ 105 4.89 10 ∗ 102 2.14
5 2
11 ∗ 10 5.43 11 ∗ 10 2.71
5 2
12 ∗ 10 5.98 12 ∗ 10 3.21
5 2
13 ∗ 10 6.59 13 ∗ 10 3.71
14 ∗ 105 7.24 14 ∗ 102 4.56
5 2
15 ∗ 10 7.56 15 ∗ 10 4.93
5 2
16 ∗ 10 7.95 16 ∗ 10 5.50
17 ∗ 105 8.58 17 ∗ 102 6.35
5 2
18 ∗ 10 9.47 18 ∗ 10 7.48
5 2
19 ∗ 10 9.85 19 ∗ 10 8.08
0.01
0.008
0.007
Temps d'exécution
0.006
0.005
0.004
0.003
0.002
0.001
0
0 2 4 6 8 10 12 14 16 18 20
Taille du tableau
Figure 2.1: Complexité théorique vs complexité réelle pour un programme de coût BigO(N )
42
Chapitre 2 : Complexité algorithmique
Figure 2.2: Complexité théorique vs complexité réelle pour un programme de coût BigO(N 2 )
2.4 Conclusion
Ce chapitre a traité la notion de complexité algorithmique. On a opté pour la complexité dans le pire des cas O.
Les autres types d’analyse de complexité, Θ et Ω , ont été expliqués brièvement. En effet, c’est le premier type
qui nous intéresse dans la pratique : on analyse les algorithmes dans le cas général et on prévoit le pire des cas.
43
Chapitre
3
Types abstraits de données, TAD
3.1 Introduction
L’objectif de ce chapitre est de faire apprendre à l’étudiant les éléments de base des types abstraits de données.
La maitrise de la notion de TAD nous permet de concevoir des algorithmes le plus indépendamment possible des
langages de programmation. On vise donc de créer des algorithmes génériques qui seront facilement extensibles
à plusieurs applications et à plusieurs langages de programmation.
La série des exercices 1 corrigée sera enrichie par un exemple de conception et d’implémentation du TAD
ARBRE dans la section 3.4.
Exercice 1
Dans un plan cartésien, la structure de donnée cercle est définie par les coordonnées de son centre et son rayon.
1. Compléter le TAD CERCLE décrivant cette structure de donnée.
2. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
1
Proposée principalement par Pr. F. Belala
44
Chapitre 3 : Types abstraits de données, TAD
Solution :
2. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
Il est utile de rappeler brièvement la définition de ces types d’opérations :
– Une opération est dite interne constructeur “si elle rend un résultat d’une sorte définie” en opérant sur
des données de sortes prédéfinies (déclarées dans la partie Ùtilise’). Ces opérations servent à construire
les valeurs d’une sorte.
– Une opération est dite interne non constructeur si elle rend un résultat d’une sorte définie mais ne
construit pas de nouvelles valeurs pour cette sorte. Ce type d’opérations ne crée pas des
données il fait juste l’association entre données déjà créées.
– Une opération est dite observateur sur une sorte définie si elle rend un résultat d’une sorte prédéfinie et
possède au moins un argument de sorte définie.
On peut donc classifier les opérations du TAD CERCLE comme suit :
→ Oprérations internes constructeur : nouveau-cercle
→ Oprérations internes non constructeur : /
→ Oprérations observateur : x-centre, y-centre, rayon
3. Enrichir le TAD CERCLE par les opérations :
a. surface : qui calcule la surface d’un cercle
b. _ _ appartient à_ : qui teste si un point de coordonnées x et y appartient à un cercle C.
Enrichir un TAD par une opération consiste en la donnée de deux éléments :
1. Définir le profile de cette opération : en plus de la syntaxe de l’opération, on doit spécifier les sortes de
ses entrées et son résultat.
2. Définir sa sémantique à travers un ensemble d’axiomes : c’est l’expression de l’opération en utilisant des
opérations basique ou plus simples de la sorte en cours ou des sortes qui figurent dans la partie ‘Utilise’
du TAD.
Profil de l’opération surface :
surface : cercle → réel
45
Chapitre 3 : Types abstraits de données, TAD
Exercice 2
Soit le TAD suivant décrivant la structure de données graphe composée d’un ensemble de nœuds et un ensemble
d’arcs. Chaque arc relit deux nœuds
TAD GRAPHE
Variables :
Utilise : Nœud, ENTIER
a,b : arc, n1, n2 : nœud
Sortes : arc, graphe
Précondotions :
Opérations :
a•b définie ssi puit(a)=source(b)
1. < _, _ > : nœud, nœud → arc
Axiomes :
2. _ • _ : arc, arc → arc
1. source(<n1, n2>) ≡ n1
3. g-nœud : nœud → graphe
2. source(a•b)≡ source(a)
4. g-arc : arc → graphe
3. puit(<n1, n2>)≡ n2
5. source : arc → nœud
4. puit(a•b)≡ puit(b)
6. puit : arc → nœud
1. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
On observe que le TAD GRAPHE définie deux types de sortes : arc et graphe. Donc, chaque opération qui
permet de construire des données de l’une de ces deux sortes, à partir de sortes prédéfinies, est une opération
interne constructeur. On obtient donc comme type d’opérations :
→ Opérations internes constructeur : < _, _ >, _ • _, g-nœud, g-arc
→ Opérations internes non constructeur : pas d’opérations de ce type
→ Opérations observateur : source, nœud
2. Indiquer parmi les données suivantes celles qui sont incorrectes syntaxiquement et corriger les. Préciser alors
la sorte de chacune de ces données.
(a) <A, C>•<D, E>•<E, F>
(b) Annaba, Médéa>•<Médéa, Ghardaïa>
(c) g-arc(<Annaba, Médéa>•<Médéa, Ghardaïa>)
46
Chapitre 3 : Types abstraits de données, TAD
12
45
32
27
Solution :
1. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
On observe que le TAD GRAPHE définie deux types de sortes : arc et graphe. Donc, chaque opération qui
permet de construire des données de l’une de ces deux sortes, à partir de sortes prédéfinies, est une opération
interne constructeur. On obtient donc comme type d’opérations :
→ Oprérations internes constructeur : < _, _ >, _ • _, g-nœud, g-arc
→ Oprérations internes non constructeur : pas d’opérations de ce type
→ Oprérations observateur : source, nœud
2. Indiquer parmi les données suivantes celles qui sont incorrectes syntaxiquement et corriger les. Préciser alors
la sorte de chacune de ces données.
Pour qu’une donnée soit correcte syntaxiquement, elle doit respecter la syntaxe des opérations du TAD et
les préconditions. On analyse ces données selon la définition du TAD GRAPHE :
(a) <A, C>•<D, E>•<E, F> : Cette donnée n’est pas correcte syntaxiquement car la précondition indique
que l’opération a•b est définie ssi puit(a)=source(b), chose qui n’est pas vérifiée dans cette expression ; <A,
C>≡ C (selon les axiomes). Pour la corriger, on propose de remplaced la source de l’arc <D, E> par C ou le
puit de <A, C> par D ; on obtient donc : <A, C>•<C, E>•<E, F> ou <A, D>•<D, E>•<E, F>. La sorte
de cette donnée est ‘arc’, il s’agit de :
1. appliquer l’opération < _, _ > sur les noeuds A et C, qui donne un arc : <A, C>
2. appliquer l’opération < _, _ > sur les noeuds C et E, qui donne un arc : <C, E>
3. appliquer l’opération _ • _ sur les arcs <A, C> et <C, E>, qui donne l’arc <A, C>•<C, E>
4. appliquer l’opération _ • _ sur les arcs <A, C>•<C, E> et <E, F>, qui donne l’arc <A, C>•<C,
E>•<E, F>
(b) <Annaba, Médéa>•<Médéa, Ghardaïa> : Cette donnée est correcte syntaxiquement et nous donne
un élément de sorte ‘arc’.
(c) g-arc(<Annaba, Médéa>•<Médéa, Ghardaïa>) : Cette donnée est correcte syntaxiquement et nous
donne un élément de sorte ‘graphe’. L’opération ‘g-arc’, selon la signature du TAD GRAPHE, reçoit un arc,
appartir duquel un graphe est construit.
(d) source(<A, D>•<D, E>•<E, F>, A) : Cette donnée est correcte syntaxiquement et nous donne un
47
Chapitre 3 : Types abstraits de données, TAD
élément de sorte ‘nœud’. L’opération ‘source’, selon la signature du TAD GRAPHE, reçoit un graphe, et nou
donne un élément de sorte ‘nœud’.
(e) puit(<A, C>•<C, E>•<E, F>) : Cette donnée est incorrecte syntaxiquement parceque l’opération
‘source’ s’applique sur un élément de sorte ‘graphe’ alors que l’expression “<A, C>•<C, E>•<E, F>, A” n’est
pas de type graphe. Sa correction se fait naturellement en supprimant la partie “, A”. On aura donc : source(<A,
C>•<C, E>•<E, F>). Cette données est de sorte nœud.
3. Donner la notation (écriture) syntaxique correcte du graphe suivant selon cette signature :
12
45
32
27
Selon la signature du TAD, pour construire (exprimer) une donnée de type graphe, il suffit d’appliquer l’une ou
un ensemble des opérations g-nœud (qui permet de construire un graphe à partir d’un nœud) et g-arc (qui crée
un graphe à partir d’un arc). Le graphe illustrée dans la figure est composé de l’union de deux sous-graphes qui
ne pourront pas être exprimés par une simple opération, ont construit ainsi notre graphe comme suit :
g-arc(<12, 27>)
g-arc(< 12, 45 > • < 45, 27 > • < 27, 32 > • < 32, 45 >)
Ces deux expressions ensembles définissent notre graphe.
Exercice 3
Soit le TAD suivant décrivant la structure de données LIVRE. La donnée livre étant définie par son auteur, son
contenu (texte) et son éditeur.
TAD LIVRE
Variables :
Utilise : AUTEUR, TEXTE, EDITEUR, · · · · · ·
L1, L2 : livre, a : auteur, t : texte, e : éditeur
Sorte : livre
Précondotions :
Opérations :
fusion(L1, L2) définie ssi mêmeA(L1, L2) et mêmeE(L1, L2)
|_, _, _| : auteur, texte, éditeur → livre
Axiomes : 1. AL(|a, t, e|) ≡ a
_ • _ : texte, texte → texte
2. TL(|a, t, e|) ≡ t
AL : livre → auteur
3. EL(|a, t, e|) ≡ · · ·
TL : · · · · · · → texte
4. fusion(L1, L2) ≡ |AL(L1), TL(L1)•TL(L2), EL(L1)|
EL : livre → éditeur
5. mêmeA(L1, L2) ≡ si AL(L1) == AL(L2) alors
fusion : livre, livre → livre
vrai sinon faux
mêmeA : livre, livre → · · ·
6. mêmeE(L1, L2)≡ si · · · · · · alors vrai sinon faux
mêmeE : livre, livre → booléen
48
Chapitre 3 : Types abstraits de données, TAD
Solution :
2. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
→ Opérations internes constructeur : |_, _, _| ; cette opération permet de construire une livre ) en
combinant les informations d’un ‘auteur’, d’un ‘text’ et d’un ‘éditeur’. On remarque que le résultat obtenu,
de type ‘livre’, ne peut pas être généré autrement. C’est une opération constructeur de base.
→ Opérations internes non constructeur : fusion. Cette opération prend deux éléments de type livre
et les fusionne ; mais elle n’est pas constructeur car son résultat pourra être généré par des opération
constructeur de base (voir l’axiome 4).
→ Opérations observateur : AL, TL, EL, mêmeA, mêmeE. Toutes ces opérations donnent des résultats
de type sortes prédéfinies.
NB : l’opération •, malgré qu’elle est définie ici, concerne le TAD TEXTE et pas le TAD LIVRE.
On peut la voir comme un enrichissement du TAD TEXTE.
3. Réécrire l’axiome 4 d’une autre manière.
Cet axiome est exprimé en termes d’opérations de haut niveau : AL, TL et ELn qui sont des opération
observateur. Une manière possible pour le réécrire autrement est d’utiliser les opérations constructeur. On
commence par nous poser la question : ’Comment peut-t-on exprimer les données L1 et LE en utilisant les
opérations constructeur ?’
Si on suppose que le livre L1 est construit à partir des information de l’auteur a1, le texte t1 et l’éditeur
e1 ; et que le livre L2 est construit à partir des information de l’auteur a2, le texte t2 et l’éditeur e2. Donc
on peut réécrire les données L1 et L2 comme |a1,t1,e1| et |a2,t2,e2|, respectivement. D’autre part, on a la
précondition ‘fusion(L1, L2) définie ssi mêmeA(L1, L2) et mêmeE(L1, L2)’ qui doit être vérifiée ; alors : on doit
avoir a1=a2 et e1=e2. Si cette condition est vérifiée, il nous reste à décider comment exprimer la partie droite
de l’axiome à proposer. L’idée est simple, selon le même axiome, on fusionne la partie texte des deux livre en
utilisant l’opération •. On obtient ainsi :
49
Chapitre 3 : Types abstraits de données, TAD
Fin
L’opération |_, _, _| par la fonction ‘constLivre’ suivante :
Exercice 4
Soit le TAD suivant décrivant la structure de données MAP formée d’une collection de paires de données.
Chaque paire de donnée est composée de clé/valeur.
TAD MAP
Utilise : KEY, VALUE, BOOLEEN Variables : k, k1 : key ; m : map
Sortes : pair, map Précondotions :
Opérations : get(k,m) est définie ssi containsKey(k,m)
< _, _ > : key, value → pair remove(k,m) est définie ssi containsKey(k,m)
_ • _ : pair, map → map Axiomes :
emptymap : → map containsKey(k, emptymap)≡ faux
containsKey : key, map → bool containsKey(k, <k1,v>•m) ≡ si k==k1 alors vrai sinon containsKey(k,m)
get : key, map → value get(k, <k1,v>•m) ≡ si k==k1 alors v sinon get(k,m)
remove : key, map → map remove(k, <k1,v>•m) ≡ si k==k1 alors m sinon <k1,v>• remove(k,m)
replace : key, value, map → map isEmpty(m) ≡ si m==emptymap alors vrai sinon faux
isEmpty : map → bool
1. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
2. Donner la notation (écriture) syntaxique correcte de map1 et map2 selon cette signature.
clé id1
map1 :
valeur 5
<id1, 5>
50
Chapitre 3 : Types abstraits de données, TAD
3. Enrichir ce TAD par l’opération "replace" qui remplace une valeur dans une map donnée, connaissant sa clé.
Solution :
1. Donner le type des opérations (internes constructeurs, internes non constructeurs, observateurs) de la signature
correspondante.
→ Opérations internes constructeur : < _, _ >, _ • _, emptymap
→ Opérations internes non constructeur : remove, replace :
→ Opérations observateur : containsKey, get, isEmpty
2. Donner la notation (écriture) syntaxique correcte de map1 et map2 selon cette signature.
clé id1
map1 :
valeur 5
Exercice 5
On se propose de définir un robot et ses déplacements dans un plan à deux dimensions à l’aide d’un TAD. Le
robot est donc défini par deux valeurs : sa position dans le plan exprimée par les valeurs x et y et sa direction
exprimée par l’angle qu’il forme avec l’axe des X. Le robot peut se déplacer d’une seule unité vers l’avant ou
vers l’arrière, vers le haut ou vers le bas comme il peut aussi changer sa direction en tournant de plus ou de
moins 90◦ .
51
Chapitre 3 : Types abstraits de données, TAD
TAD ROBOT
Utilise Direction, …
Sorte robot
Opérations
Créer-robot : entier, entier, direction → …
Robot-x : robot → entier
Robot-y : …
Robot-direction : …
_se-déplace-avant : robot → robot
_se-déplace-arrière : …
_se-déplace-haut : …
_se-déplace-bas : …
_tourne-plus : …
_tourne-moins : …
Axiomes
Robot-x (créer-robot((x, y, d)) x
Robot-y (créer-robot((x, y, d)) …
Robot-direction (créer-robot((x, y, d)) …
Créer-robot(x, y, d)se-déplace-avant créer-robot(x+1, y, d)
Créer-robot(x, y, d)se-déplace-arrière …
Créer-robot(x, y, d)se-déplace-haut …
Créer-robot(x, y, d)se-déplace-bas créer-robot(x, y-1, d)
S tourne-plus créer-robot(…, …, Robot-direction(S)+90°)
S tourne-moins …
Variables
x, y : …, d : …, S : …
52
Chapitre 3 : Types abstraits de données, TAD
TAD ROBOT
Utilise Direction, Entier
Sorte robot
Opérations
Créer-robot : entier, entier, direction → robot
Robot-x : robot → entier
Robot-y : robot → entier
Robot-direction : robot → direction
_se-déplace-avant : robot → robot
_se-déplace-arrière : robot → robot
_se-déplace-haut : robot → robot
_se-déplace-bas : robot → robot
_tourne-plus : robot → robot
_tourne-moins : robot → robot
Axiomes
Robot-x (créer-robot((x, y, d)) x
Robot-y (créer-robot((x, y, d)) y
Robot-direction (créer-robot((x, y, d)) d
Créer-robot(x, y, d)se-déplace-avant créer-robot(x+1, y, d)
Créer-robot(x, y, d)se-déplace-arrière créer-robot(x-1, y, d)
Créer-robot(x, y, d)se-déplace-haut créer-robot(x, y+1, d)
Créer-robot(x, y, d)se-déplace-bas créer-robot(x, y-1, d)
S tourne-plus créer-robot(Robot-x(S), Robot-y(S), Robot-direction(S)+90°)
S tourne-moins créer-robot(Robot-x(S), Robot-y(S), Robot-direction(S)+90°)
Variables
x, y : entier, d : direction, S : robot
3. Indiquer parmi les données suivantes celles qui sont incorrectes syntaxiquement et corriger
les.
a. Créer-robot(2, 2, 3, 50◦ )
b. Créer-robot(3, 5, 60◦ )se-déplace-avant
c. Se-déplace-arrière(Créer-robot(3, 5, 65◦ ))
d. Tourner-moins(Créer-robot(-3, 5, 60◦ ))
e. ((Créer-robot(-3, 5, 60◦ ) tourne-plus) tourne-plus) se-déplace-haut
f. ((Créer-robot(-3, 5, 60◦ ) se-déplace-haut) tourne-plus) tourne-plus
4. Indiquer parmi les données correctes précédentes celles qui sont équivalentes (ont le même
sens) selon les axiomes proposés.
On commence par fournir la sémantique de chaque donnée, c’est à dire l’exprimer en termes des opérations
les plus simples possibles. Pour qu’on puisse donner la sémantique d’une donnée, il faut appliquer successivement
53
Chapitre 3 : Types abstraits de données, TAD
les axiomes fournis dans la définition du TAD ; on peut pas juste faire l’évaluation (donner le sens) des données
par intuition. En appliquant l’ensemble des axiomes successivement, on trouve que les deux dernières données
sont équivalentes à : Créer-robot(-3, 6, 240◦ ). Elles sont équivalentes donc l’une à l’autre.
5. Enrichir ce TAD par l’opération _est-dans-champ-vision_ qui teste si un robot S1 est dans
le champ de vision de S2 (le champ de vision d’un robot est de plus ou moins 45◦ ).
Profil :
_est-dans-champs-vision_ : robot, robot → booléen
Axiome :
Une solution simplifée consiste en la proposition de l’axiome suivant :
S1 est-dans-champs-vision S2 ≡ |Robot-direction(S1)–Robot-direction (S2)|= 45◦ .
Mais, on trouve que cette solution ne considère que le cas où le robot s1 se trouve devant le robot s2
en termes de coordonnées, alors que l’axiome est supposé être correct pour tout type de robot, voir la figure
ci-après, où même si les deux robots ont la même direction, le robot s1 (x1, y1, d1) n’est pas dans le champ de
vision de s2 (x2, y2, d2) malgré qu’ils vérifie : |d1-d2|≤45◦ .
Donc, pour que s1 soit dans le champ de vision de s2, il faut que le point (x1,y1) vérifie l’équation
définissant le sous plan défini par x2, y2 et les vecteurs unitaires A et B (en vert dans la figure). On propose
ainsi l’axiome suivant :
s1 est_dans_champ_vision s2 ≡ position(s1) vérifie_equation_plan (x2,y2,d2)
s position et vérifie_equation_plan sont deux opérations pré-définies.
NB : On pourra développer ces opérations jusqu’à obtenir des instructions de base, il suffit que l’étudiant
sache faire une telle formulation qui reflète l’aspect abstrait de la chose, et c’est objectif du TAD : donner une
spécification du comportement sans s’étaler aux détails de l’implémentation.
12 52
53
13
6789
53
42 43
54
Chapitre 3 : Types abstraits de données, TAD
Exercice 6
Soit le TAD donné dans la figure 3.3 spécifiant la structure de données ensemble d’éléments.
1. Donner le type des opérations de ce TAD (constructeurs, internes non constructeurs et observateurs).
2. Les données suivantes sont-elles correctes syntaxiquement ? Si oui donner le sens de chaque donnée en
appliquant les axiomes du TAD ensemble.
3. Enrichir ce TAD par l’opération Cardinalité qui retourne le nombre d’éléments contenu dans un ensemble.
Par exemple : Cardinalité(2, 4, 5, 4)=3.
Solution :
1. Donner le type des opérations de ce TAD (constructeurs, internes non constructeurs et observateurs).
Φ, ajouter : opérations internes constructeur
supprimer : opération interne non constructeurs
Choisir, appartient : observateurs
2. Les données suivantes sont-elles correctes syntaxiquement ? Si oui donner le sens de chaque donnée en
appliquant les axiomes du TAD ensemble.
a. ajouter(a, ajouter(b, ajouter(a, Φ)))
b. appartient(x, ajouter(b, ajouter(a,Φ)))
c. supprimer(2, ajouter(4, ajouter(5, ajouter(2, ajouter(4,Φ)))))
3. Enrichir ce TAD par l’opération Cardinalité qui retourne le nombre d’éléments contenu dans un ensemble.
Par exemple : Cardinalité(2, 4, 5, 4)=3.
55
Chapitre 3 : Types abstraits de données, TAD
Exercice 7
Soit la signature suivante du TAD EXPRESSION spécifiant une expression mathématique simple.
TAD EXPRESSION
Utilise : VARIABLE, NATUREL, REEL
Sorte : expression
Opérations :
var : variable → expression
const : réel → expression
_ + _ : expression, expression → expression
_ ∗ _ : expression, expression → expression
_∧_ : variable, naturel → expression
1. Donner le type des opérations de ce TAD (constructeurs, internes non constructeurs et observateurs).
2. Les données suivantes sont elles correctes syntaxiquement ? sinon corriger ?
a. var(x)+const(2.4)*var(y)
b. x∧ 2+3.1*var(x)
c. var(x)*const(6.9)+var(y)*const(7.7)+z∧ 3
3. Enrichir ce TAD par l’opération Dérivée qui retourne la dérivée d’une expression.
Solution :
1. Donner le type des opérations de ce TAD (constructeurs, internes non constructeurs et observateurs).
Var, const, ∧ , + , * : opérations constructeurs ;
Toutes les opérations sont des opérations constructeurs, veuillez corriger l’information aux étudiants, car même
les opérations + et * ne peuvent pas être écrites en termes de constructeurs ni d’opérations plus simples. De
56
Chapitre 3 : Types abstraits de données, TAD
plus, on est en train de faire le TAD qui génère des expressions et pas des nombres réels exprimés par ces
expressions.
2. Les données suivantes sont elles correctes syntaxiquement ? sinon corriger ?
a. var(x)+const(2.4)*var(y) : Oui
b. x∧ 2+3.1*var(x) : Non, x∧ 2+const(3.1)*var(x)
c. var(x)*const(6.9)+var(y)*const(7.7)+z∧ 3 :Oui
3. Enrichir ce TAD par l’opération Dérivée qui retourne la dérivée d’une expression.
Profil :
Dérivée : expression→expression
Axiomes :
dérivée(var(x))≡const(1)
dérivée(const(n))≡const(0)
dérivée(E1+E2)≡dérivée(E1) + dérivée(E2)
dérivée(E1*E2)≡dérivée(E1)*E2+dérivée(E2)*E1
dérivée(x◦ n) ≡ const(n)*x◦ (n-1)
Variables : x : variable n : entier E1, E2 : expression
Dans cette section, on commence par définir le TAD ARBRE ; et puis on procède à son implémentation, dans
laquelle on définit ses structures et les fonctions et procédures équivalentes à ses opérations.
Un arbre est une structure de données hiérarchique formée par une collection d’informations homogènes
organisées en niveaux reliées entre eux par des branches sans boucles. Mathématiquement parlant, un arbre se
compose de deux ensembles N et A appelés respectivement l’ensemble des nœuds et l’ensemble des arcs
et d’un nœud particulier r appelé racine de l’arbre. Les éléments de A sont des paires (n1, n2) d’éléments de
N. Un arc (n1,n2) établit une relation entre n1, appelé nœud parent, et n2, appelé nœud enfant de n1. A doit
être tel que chaque nœud, sauf la racine, a exactement un parent. La racine n’a pas de parent 2 . La figure 3.4
illustre la notion d’arbre 3 .
Travail demandé
Solution :
2
Définition proposée par Pr. F. Belala
3
https ://[Link]/8-useful-tree-data-structures-worth-knowing-8532c7231e8c
57
Chapitre 3 : Types abstraits de données, TAD
Notation mathématique de l’arbre : Selon la définition mathématique de l’arbre, on doit donner trois
éléments : l’ensemble des nœuds N, l’ensemble des arcs A et la racine Racine. Si on considère l’exemple de la
figure 3.5, sa notation mathématique est comme suit :
25
10 130 24
3 14 11 120 5
58
Chapitre 3 : Types abstraits de données, TAD
59
Chapitre 3 : Types abstraits de données, TAD
4
On suppose que l’opération ∪ est définie dans le TAD ENSEMBLE.
60
Chapitre 3 : Types abstraits de données, TAD
Fin
Comme bien vu, la définition de cette structure doit passer par la définition des types : EnsembleNœuds,
EnsembleArcs et Nœud. Il n’y a pas une façon universelle pour décider la meilleure façon de représentation
de ces types ; tout dépend de la future utilisation du TAD ARBRE. Cette future application définit la nature
des nœuds et des arcs de l’arbre. Par exemple, dans un arbre qui représente la hiérarchie d’un staff dans une
entreprise, les nœuds sont des ‘personnes’, les arcs pourront représenter la relation ‘supérieur de’ et la racine est
le PDG. Dans un arbre qui représente un système de routage, les nœuds sont des machines, les arcs sont les
liens directs entre machines et la racine pourra être une machine client.
Pour illustrer l’utilité de la notion TAD et pour bien voir la généricité, on essaie dans ce qui suit
d’implémenter les opérations du TAD ARBRE, mais en gardant un certain niveau d’abstraction. Cette
abstraction, permet au maximum la réutilisation du TAD et de son implémentation.
Implémentation des opérations constructeur
arbreVide : → arbre
61
Chapitre 3 : Types abstraits de données, TAD
a
On suppose que le TAD Nœud est implémenté par un enregistrement avec Elem comme champ du contenu
Remarques :
a. Il est vrai qu’il n’existe pas de relation d’ordre entre les éléments d’un ensemble, mais quand on veut
l’implémenter dans les langages de programmation, on opte généralement pour des structures linéaires
62
Chapitre 3 : Types abstraits de données, TAD
tels que les tableaux, les liste et les arraylists. Ces structures imposent une relation d’ordre entre leurs
éléments. Donc, la notation X{i} exprime le ieme élément dans l’ensemble X.
b. ‘Cardinalité’ est une opération observateur du TAD ENSEMBLE, voir page 55.
c. L’opération source est définie dans le TAD ARC, elle retourne la source d’un arc.
a
L’opération ‘appartient’ est définie dans le TAD ENSEMBLE
D’une façon récursive, la fonction ‘ascendants’ explore l’ensemble des arcs de l’arbre b pour chercher les
parents des nœuds passés comme paramètres aux différents appels.
63
Chapitre 3 : Types abstraits de données, TAD
64
Chapitre 3 : Types abstraits de données, TAD
65
Chapitre 3 : Types abstraits de données, TAD
3.5 Conclusion
Dans ce chapitre on a présenté quelques exercices de degrés de difficulté différents, on a proposé quelques
solutions types en se basant sur les notions du TAD vues dans le cours. Le TAD arbre présenté dans la fin du
chapitre vise l’illustration d’une définition d’un TAD le plus indépendamment possible de l’implémentation.
Le TAD arbre présenté dans ce chapitre est différent de ce qui est généralement donné dans d’autres
références. Premièrement, on a essayé de fournir une définition la plus générale possible des arbre, alors que la
majorité des référence ne donne que la définition du TAD ARBRE BINAIRE, ceci limite les possibilités des
opérations et des implémentations du TAD en question. Deuxièmement, on avait l’habitude d’implémenter les
arbres par des structures linéaires, telles que les listes, piles et files. Dans notre solution, on a insisté sur le fait
qu’un TAD peut être implémenté à base de n’importe quelle structure jugée appropriée, on a opté pour les
ensembles, les nœuds et les arcs dans notre solution.
66
Chapitre
4
Structures de données linéaires : liste, pile et file
4.1 Introduction
Ce chapitre traite trois types de TAD représentant les trois structures linéaires : liste, pile et files. Les solutions
présentées sont basée principalement sur les TAD proposés dans le cours.
L’objectif du chapitre est de fournir de compléter ce qui est présenté en TD ; on présente des solutions les
plus complètes possible pour la totalité des exercices de la série de TD.
4.2 Série de TD
Exercice 1
1
Voir cours sur : https ://[Link]/elearning/course/[Link] ?id=1076
67
Chapitre 4 : Structures de données linéaires : liste, pile et file
Avant d’implémenter cette opération, on commence par implémenter la structure de la liste contiguë et par
l’implémentation d’autres opérations nécessaires pour l’accomplir.
On représente la liste contiguë par un enregistrement composé de deux champs : une table Tab contenant les
éléments de la liste et un entier N contenant le nombre des éléments de la liste.
(
Tab : tableau [N] : entier
N : entier
Une bonne façon de remplir les éléments de la liste est d’insérer les éléments dans l’ordre inverse dans le tableau
Tab. Cette représentation permet un accès rapide au premier élément de la liste et nous permet d’éviter les
opérations couteuses de décalage.
68
Chapitre 4 : Structures de données linéaires : liste, pile et file
Cette implémentation de la liste est des opération ‘reste’, ‘chercherliste’ et ‘reste’ utilise des opérations similaires
à celles proposées pour la structure pile ; en effet ‘reste’ est similaire à ‘dépiler’ et ‘premier’ est similaire à
‘sommet’.
NB : Il est également possible d’utiliser les opérations Accès et supprimer pour proposer une solution
similaire, voir la solution du prochain exercice qui exploite ces deux dernières opérations.
Exercice 2
1. Écrire un algorithme :
a. qui lit une suite de N nombres réels et construit une liste,
b. puis détermine le minimum de ces réels et l’imprime.
2. Laquelle des implémentations est favorable pour ce cas, contigüe ou chainée ?
Solution :
Cet exercice est très similaire à celui proposé dans l’exercice 7 du chapitre 1, voir page 17. La seule différence
réside dans le fait qu’on traite ici une liste au lieu d’une suite numérique et on cherche le minimum au lieu du
maximum.
Pour pouvoir écrire cet algorithme, on commence par implémenter le type élément, on le fait par
l’instruction :
Type élément : réel
On doit également définir l’opération minListe qui cherche la valeur minimale entre les éléments de la liste. On
donne le profil et l’axiome de cette opération :
Profil : minListe : liste → réel
Variables : L : liste, e : réel
Précondition : minListe(L) est définie ssi L 6= listeVide
Axiomes :
minListe(L)≡ Si (supprimer(L,1)= listeVide) Alors accès(L,1)
Sinon
Si (accès(L,1) < minListe(supprimer(L,1))) Alors accès(L,1)
Sinon minListe(supprimer(L,1))
Cet axiome nous aide à implémenter l’opération ‘minListe’ par une fonction récursive comme suit :
69
Chapitre 4 : Structures de données linéaires : liste, pile et file
IL est à noter que l’axiome et l’implémentation donnés ici pour l’opération minListe sont appropriés à
l’implémentatuion contigue de la liste. Ils peuvent être adaptés au cas de la liste chainée comme suit :
minListe(L)≡ Si (supprimer(L,L)= listeVide) Alors accès(L,L)
Sinon
Si (accès(L,L) < minListe(supprimer(L,L))) Alors accès(L,L)
Sinon minListe(supprimer(L,L))
Aussi, nous adaptons l’appel (utilisation) de la fonction insérer comme suit : insérer(L, p, NB), p est de type
pointeur de cellule et nous pouvons ici insérer les nombres réels lus au début de la liste ; alors p = L (et
p=L=Null pour insérer dans la liste vide).
L’algorithme principal devient ainsi :
70
Chapitre 4 : Structures de données linéaires : liste, pile et file
La comparaison du code A, donné dans la figure 4.2, pour les 2 implémentations donne une complexité
équivalente pour les instructions 1 jusqu’à 5 . La différence réside dans l’instruction 6 et l’appel de la fonction
‘minListe’ :
En effet les instructions 1. et 2., pour chacune des 2 implémentations, sont équivalentes en complexité car
la fonction listeVide() est du même ordre pour les 2 implémentations (elle est constante). Les instructions 3. et
4. sont aussi équivalentes en complexité pour les deux implémentations. L’instruction 5. permet d’insérer un
nombre à la fin de la liste implémentée de façon contiguë (donc pas de décalage) et elle permet d’insérer un
2
Les éléments de la liste dans ce cas sont stockés dans la table dans l’ordre de leur lecture
71
Chapitre 4 : Structures de données linéaires : liste, pile et file
nombre au début de la liste implémentée de façon chainée (pas de parcours). Ainsi elles sont presque équivalentes
en complexité.
L’instruction 6. contenant l’appel de la fonction minListe(L) nécessite plus de temps avec une
implémentation contiguë du fait qu’elle utilise la fonction supprimer(L, 1) nécessitant un décalage à gauche
de l’ordre O(N) (à chaque appel). Par contre, la fonction supprimer(L, L) (pour la liste implémentée de
façon chainée), supprimant le premier élément de la liste, est constante en complexité (avec 2 instructions
uniquement)3 .
Ainsi nous concluons que l’implémentation chainée de la liste (avec recherche du minimum) est plus
adéquate si l’implémentation contigue stocke les éléments dans le champ Tab dans le même ordre de leur lecture.
Il est évident, dans ce cas, que la deuxième implémentation (chainée) est la plus favorable ; l’accès aux
éléments de la liste, l’insertion et la suppression se font en début de liste, on n’a pas à parcourir tous les éléments.
Dans l’implémentation contigüe, les opérations de suppression et d’insertion nécessitent le décalage de tous les
éléments du tableau.
B. Si la liste contiguë est implémentée par une table dont l’ordre des éléments est inversé (comme
proposé dans l’exercice 1)
L’avantage de cette implémentation de la liste contiguë est qu’elle permet une insertion et une suppression
rapides du premier élément de la liste, donc elle va permettre de réduire la complexité de la solution de recherche
récursive du minimum de la liste, chose demandée dans cet exercice.
Ainsi, le stockage inverse des éléments dans le tableau de la liste permet une complexité équivalente à
celle offerte par l’implémentation chainée dans le contexte du code A. Cependant, si d’autres traitements sont
demandés, le même problème du décalage couteux peut émerger.
Exercice 3
1. Afin de disposer d’une boite à opérations (à utiliser selon le besoin), enrichir le type abstrait de données
LISTE (vu dans le cours 4 ) par les opérations suivantes :
(a) ajouterFin (L, e), permettant d’ajouter un élément e à la fin d’une liste L.
(b) queue (L), permettant de retourner le dernier élément d’une liste.
(c) Inverse(L), permettant d’inverser les éléments d’une liste.
(d) Som(L) qui calcule la somme des éléments d’une liste d’entiers
(e) égale(L1, L2) qui teste l’égalité de deux listes d’entiers.
2. Développez des fonctions implémentant les opérations précédentes.
3. Écrivez un algorithme, basé sur les fonctions précédentes, qui permet de :
(a) Créer une liste L1 contenant N entiers
(b) Afficher l’élément en queue de L1.
3
La fonction Supprimer peut poser une ambiguïté chez certains étudiants en pensant que les éléments de la liste sont détruits. La
réponse est que la liste initiale n’est pas touchée grâce au passage de paramètre par données, chaque fonction crée localement une
copie de la liste et travaille dessus sans toucher au paramètre réel qui a été envoyé. Exemple : Dans le code A (en rouge), à la fin de
la boucle la liste L est construite ; et à l’appel de la fonction minListe(L), cette dernière va créer une copie de L pour travailler et
idem pour la fonction supprimer(L,1) et chacune retournera son résultat. Après l’instruction 6., la liste reste intacte.
4
voir cours sur : https ://[Link]/elearning/course/[Link] ?id=1076
72
Chapitre 4 : Structures de données linéaires : liste, pile et file
Solution :
(1) Enrichir le TAD LISTE et (2) Développez des fonctions implémentant les opérations précé-
dentes.
On propose pour chacune de ces opérations le profil, les axiomes et l’implémentation dans les deux cas de liste :
chainée et contiguë.
a) ajouterFin(L,e), permettant d’ajouter un élément e à la fin d’une liste L :
Le profil de l’opération ajouterFin dans les deux types de liste est donnée par :
Profil :
ajouterFin : liste, élément → liste
Ses propriétés sous forme d’axiomes sont données par :
Axiomes :
ajouterFin(L,e) ≡ insérer(L, longueur(L)+1, e) / * Si la liste est contigüe, i.e. Position = entier
ajouterFin (L,e) ≡ insérer(L, Null,e) / * Si la liste est chainée, i.e. Position = liste
Implémentation de la fonction ajouterFin dans le cas de la liste contiguë :
Fin
Fin
Remarque : Cette fonction est meilleure dans le cas de l’implémentation contiguë. Dans la liste représentée
de manière chaînée, l’ajout à la fin se fait après avoir défilé tous les éléments de la liste, ce qui la rend plus
couteuse.
b) queue(L), permettant de retourner le dernier élément d’une liste :
Le profil de l’opération queue dans les deux types d’implémentation de la liste est donnée par :
Profil :
queue : liste→élément
73
Chapitre 4 : Structures de données linéaires : liste, pile et file
74
Chapitre 4 : Structures de données linéaires : liste, pile et file
75
Chapitre 4 : Structures de données linéaires : liste, pile et file
0
Sinon
accès(L,L)+Som(supprimer(L,L))
Liste contigue / fonction récursive :
Fin
NB : Veuillez faire aux étudiants la remarque qu’on peut remplacer “supprimer(L,L)” utilisée
dans le cas de la liste chainée par : “suivant(L)”, dans cet exercice et dans l’exercice 2 (Maximum
récursive).
Liste contiguë / fonction itérative :
76
Chapitre 4 : Structures de données linéaires : liste, pile et file
77
Chapitre 4 : Structures de données linéaires : liste, pile et file
78
Chapitre 4 : Structures de données linéaires : liste, pile et file
Exercice 4
79
Chapitre 4 : Structures de données linéaires : liste, pile et file
des nombres produits dont les éléments sont obtenus en faisant le produit (la multiplication) des éléments de L1
par ceux de L2 (deux à deux).
2. Écrire un algorithme qui :
a. Construit deux listes de 500 nombres réels chacune.
b. Multiplie les éléments (deux à deux) des deux listes pour avoir une liste LPROD en utilisant l’opération
produitListe
c. Affiche les nombres de la liste LPROD obtenue.
Solution :
1. Définir l’opération produitListe
Profil :
produitListe : liste, liste→ liste
Précondition :
produitListe(L1,L2) est définie ssi longueur(L1)=longueur(L2)
Axiomes pour le cas de la liste contiguë :
produitListe(L1,L2,L3) ≡ Si (supprimer(L1,1)= listeVide) Alors insérer(L3, accès(L1,1)*accès(L2,1),1)
Sinon
insérer(produitListe(supprimer(L1, 1), supprimer(L2,1),L3), accès(L1, 1)*accès(L2, 1),1)
Axiomes pour le cas de la liste chainée :
produitListe(L1,L2,L3) ≡ Si (supprimer(L1,1)= listeVide) Alors insérer(L3, accès(L1,1)*accès(L2,1),L3)
Sinon
insérer(produitListe(supprimer(L1, 1), supprimer(L2,1),L3), accès(L1, 1)*accès(L2, 1), L3)
80
Chapitre 4 : Structures de données linéaires : liste, pile et file
2. L’algorithme produitListe
Dans le cas de la liste contiguë :
Algorithme : algoProduitListes
Déclarations :
L1, L2, L3, Res : liste ; NB, i : entier
Fonction listeVide() : liste
Fonction supprimer(Donnée L : liste, Donnée i : liste) : liste
Fonction accès(Donnée L : liste, Donnée i : liste) : réel
Fonction insérer(Donnée L : liste, Donnée e : élément, Donnée i : entier) : réel
Fonction produitListe_Contiguë (Donnée L1, L2 : liste ; Donnée/Résultat L3 : liste) : liste
Début
L1 = listeVide()
L2 = listeVide()
L3 = listeVide()
Res = listeVide()
Pour i ← 1 à 500 :
Lire(NB1)
Lire(NB2)
L1=insérer(L1,i,NB1)
L2=insérer(L2,i,NB2)
FPour
Res=produitListe(L1,L2,L3)
TQ (Res6=listeVide) :
Écrire(accès(Res,1))
Res ← supprimer(Res,1)
FTQ
Fin
81
Chapitre 4 : Structures de données linéaires : liste, pile et file
Algorithme : algoProduitListes
Déclarations :
L1, L2, L3, Res : liste ; NB, i : entier
Fonction listeVide() : liste
Fonction supprimer(Donnée L : liste, Donnée i : liste) : liste
Fonction accès(Donnée L : liste, Donnée i : liste) : réel
Fonction insérer(Donnée L : liste, Donnée e : élément, Donnée i : entier) : réel
Fonction produitListe_Chainée (Donnée L1, L2 : liste ; Donnée/Résultat L3 : liste) : liste
Début
L1 = listeVide()
L2 = listeVide()
L3 = listeVide()
Res = listeVide()
Pour i ← 1 à 500 :
Lire(NB1)
Lire(NB2)
L1=insérer(L1,L1,NB1)
L2=insérer(L2,L2,NB2)
FPour
Res=produitListe(L1,L2,L3)
TQ (Res6=listeVide) :
Écrire(accès(Res,L3))
Res ← supprimer(Res,L3)
FTQ
Fin
Exercice 5
1. Enrichir le TAD FILE par l’opération "Identiques" qui teste l’égalité de deux files de caractères.
2. Donner les fonctions génériques récursive et itérative qui implémentent cette opération.
Solution :
Profil :
Identiques file, file → booléen
Précondition :
Identiques(f1,f2) est définie ssi longueur(f1)=longueur(f2)
Axiomes :
Identiques(fileVide,fileVide) ≡ vrai
Identiques(f1,f2) ≡ si tête(f1) = tête(f2) alors Identiques(défiler(f1), défiler(f2)) Sinon faux
2. Donner les fonctions génériques récursive et itérative qui implémentent cette opération.
82
Chapitre 4 : Structures de données linéaires : liste, pile et file
Fonction récursive :
Fonction itérative :
Exercice 6
Soit le TAD PILE d’entiers compris entre 1 et N. On suppose l’existence d’une opération estPremier qui teste si
un entier est un nombre premier,
1. Enrichir le TAD par une opération pile-premier qui construit une sous pile formée de tous les éléments, de la
pile initiale, qui sont des nombres premiers.
2. Écrire la fonction récursive correspondante.
3. Donner la fonction itérative complète (entêtes et corps des différentes fonctions) pilePremiers sachant que la
83
Chapitre 4 : Structures de données linéaires : liste, pile et file
3. Donner la fonction itérative complète (entêtes et corps des différentes fonctions) pilePremiers
sachant que la pile est implémentée de manière chaînée.
84
Chapitre 4 : Structures de données linéaires : liste, pile et file
Exercice 7
85
Chapitre 4 : Structures de données linéaires : liste, pile et file
Sinon chercherFile(défiler(f),e)
2- Proposer l’implémentation la plus adéquate (Chainée ou contigue) pour chacune de ces fonc-
tions. Justifier votre choix.
Avant de donner l’implémentation de la fonction ajouterDeb, on commence par donner le corps de la fonction
suppQueue, selon le premier axiome défini, ce dernier dépendra de l’implémentation de la file. L’implémentation
de la fonction suppQueue est premièrement donnée par exploitation de la structure adoptée de la file :
86
Chapitre 4 : Structures de données linéaires : liste, pile et file
On a également proposé un axiome plus abstrait pour l’opération suppQueue, mais il utilise l’opération
ajouterDeb :
suppQueue(f, e) ≡ Si défiler(f) = fileVide Alors fileVide
Sinon ajouterDeb(suppQueue(défiler(f)), tête(f))
Comme déjà vu dans l’exercice 13 du chapitre 1 (page 26), il s’agit de l’appel récursif mutuel (Mutual Recursion)
entre deux fonctions : ajouterDeb et suppQueue ; ça s’exécute naturellement, car on arrive à un point où l’une
des deux fonction arrive au cas trivial et retourne une valeur qui sera utilisée par l’autre fonction pour retourner
une valeur à l’appel fait par la première et ainsi de suite.
L’implémentation de la fonction suppQueue est premièrement donnée par exploitation de la structure adoptée
de la file : implémentation contiguë de la file.
On donne maintenant l’implémentation récursive de la fonction supp-queue qui fait appel à la fonction
ajouterDeb :
87
Chapitre 4 : Structures de données linéaires : liste, pile et file
Inverse :
On présente égélement à la version itérative des fonctions implémentant ces opérations (toujours
pour le cas de la file contiguë) :
88
Chapitre 4 : Structures de données linéaires : liste, pile et file
89
Chapitre 4 : Structures de données linéaires : liste, pile et file
Exercice 8
Etant donnée l’implémentation suivante de la sorte "élément" dans le TAD PILE d’éléments.
Type : élément : Etudiant
Type : Etudiant = enregistrement
nom : chaine de caractères
prénom : chaine de caractères
mat : entier
Fin
1. Donner l’implémentation chainée (types et fonctions de base) de la structure de données pile d’étudiants dans
ce cas.
2. Étant données la fonction : Mat(D E : étudiant) : entier, qui retourne le matricule d’un étudiant donné.
Écrire une fonction récursive (profile et axiome au préalable) qui retourne l’étudiant ayant le matricule 255 se
trouvant dans une pile d’étudiants.
Solution :
1. La création du TAD PILE_Etudiants consiste en :
(a) La définition ou la redéfinition des structures de données nécessaires
(b) L’adaptation des opérations du TAD PILE de base qui dépendent de l’implémentation
Redéfinition de la structure pile :
Type : pile= pointeur de cellule
90
Chapitre 4 : Structures de données linéaires : liste, pile et file
Selon l’énoncé, l’élément recherché existe dans la pile ; on n’aura pas besoin de mettre une précondition liée à
son existence.
91
Chapitre 4 : Structures de données linéaires : liste, pile et file
4.3 Conclusion
Dans ce dernier chapitre, nous avons essayé de couvrir trois notions importantes dans le domaine de l’algorith-
mique : liste, pile et file. Les solutions et explications proposée stressent l’abstraction et le réutilisation par
l’adoption des TAD comme outil de définition et manipulation des structures de données.
92
Annexe
A
Programmation en langage C des exercice du chapitre 1
93
Chapitre A : Programmation en langage C des exercice du chapitre 1
Listing A.3: Code C pour une fonction qui admet un passage de paramètres ‘par résultat’
1 #include <stdio.h>
2 int sommePositifsNegatifs(int *tab, int *somNeg,unsigned n)
3 {
4 int somPos=0;
5 *somNeg=0;
6 unsigned i;
7 for (i = 0; i <= n-1; i++)
8 {
9 printf("i= %d\n",i);
10 printf("tab[i]= %d\n",tab[i]);
11 printf("somPos= %d\n",somPos);
12 printf("somNeg= %d\n",*somNeg);
13 if (tab[i] >= 0)
14 somPos=somPos+tab[i];
15 else
16 *somNeg=*somNeg+tab[i];
17 }
18 return somPos;
19 }
20 int main(void)
21 {
22 int resNeg;
23 int tabAA[5] = {15,-3,-2,0,6};
24 printf("La somme des nombre positifs est %d\n",sommePositifsNegatifs(tabAA,&resNeg,5));
25 printf("La somme des nombre negatifs est %d\n",resNeg);
26 return 0;
27 }
94
Chapitre A : Programmation en langage C des exercice du chapitre 1
Listing A.4: Code Python définissant et utilisant une fonction qui retourne trois résultats
1 def test2():
2 return ’abc’, 100, [0, 1, 2]
3 a, b, c = test2()
4 print(a)
5 print(b)
6 print(c)
Listing A.5: Code Python définissant et utilisant une fonction qui retourne deux résultats
1 def sommePositifs(arr):
2 somPos = 0
3 somNeg = 0
4 for num in arr:
5 if num > 0:
6 somPos += num
7 else:
8 somNeg += num
9 return somPos,somNeg
10 print(sommePositifs([15,-3,-2,0,6]))
95
Chapitre A : Programmation en langage C des exercice du chapitre 1
Listing A.6: Code C utilisant la fonction Equilibre – solution basée mod et div
1 #include <stdio.h>
2 #include <stdbool.h>
3 int reverse(int n)
4 {
5 printf("**** %d *****\n",n);
6 int rev;
7 int lastDigit;
8 rev = 0;
9 while (n> 0)
10 {
11 lastDigit = n % 10;
12 rev =(rev*10) +lastDigit;
13 n = div(n,10);
14 }
15 return rev;
16 }
17 bool Equilibre(int n)
18 {
19 if (reverse(n)==n)
20 {
21 return true;
22 }
23 else
24 {
25 return false;
26 }
27 }
28 int main() {
29 int nt;
30 printf("Entrer le nombre que vous voulez tester: \n");
96
Chapitre A : Programmation en langage C des exercice du chapitre 1
31 scanf("%d", &nt);
32 printf(Equilibre(nt) ? "true\n" : "false\n");
33 return 0;
34 }
Listing A.7: Code Python pour fonction reverse – basée modulo et division naturelle
1 def reverseNumber():
2 reverse = 0
3 number = int(input("Please input a number to be reversed.\n"))
4 while (number > 0):
5 lastDigit = number % 10
6 reverse =(reverse*10) +lastDigit
7 number = number // 10
8 return reverse
9 print(reverseNumber())
1
[Link]
97
Chapitre A : Programmation en langage C des exercice du chapitre 1
27 return 0;
28 }
98
Chapitre A : Programmation en langage C des exercice du chapitre 1
1 #include <stdio.h>
2 //
3 double sommePosRec(double *T, int n)
4 {
5
6 if (n<0)
7 return 0;
8 else
9 printf("\n n=%d \t T[n]=%f",n,T[n]);
10 if (T[n] > 0)
11 return (T[n]+sommePosRec(T,n-1));
12 else
13 return (sommePosRec(T,n-1));
14 }
15
16 int main(void)
17 {
18 double tabEff[5] = {15,-3.5,-2,0,6};
19 printf("\n\n La somme des elements positifs dans ce tableau est: %Lf",sommePosRec(
,→ tabEff,5));
20 return 0;
21 }
99
Chapitre A : Programmation en langage C des exercice du chapitre 1
100
Chapitre A : Programmation en langage C des exercice du chapitre 1
19 scanf("%d", &a);
20 printf("Entrer le nombre de partitions: \n");
21 scanf("%d", &b);
22 printf("Le nombre de partiotions possibles de %d en %d partitions est: %d", a,b,nbrPart
,→ (a,b));
23 return 0;
24 }
101
Chapitre A : Programmation en langage C des exercice du chapitre 1
14 }
15 }
16 int main(void)
17 {
18 double tabEff[5] = {15,-3.5,-2,0,6};
19 printf("Le maximum du tableau est: %f",maxTabRec(tabEff,5));
20 return 0;
21 }
102
Chapitre A : Programmation en langage C des exercice du chapitre 1
37 {
38 struct suiteNum myS;
39 int nbr;
40 double tabEff[5];
41 printf("Donner le bombre d’elements de votre suite’: \n");
42 scanf("%d", &nbr);
43 myS.n=nbr;
44 printf("La taille actuelle de la suite est: %d",myS.n);
45 printf(estVide(myS) ? "\t Elle est vide\n" : "\t Elle n’est pas vide \n");
46 int i;
47 for(i=0;i<nbr;i++)
48 scanf("%Lf", &[Link][myS.n-i-1]);
49 printf("\n***********************\n");
50 printf("Le premier element de la suite est: %f \n", Elem(myS));
51 printf("\n***********************\n");
52 printf("\n***********************\n");
53 printf("Le maximum de cette suite numerique est: %f",maxSuiteNum(myS));
54 printf("\n***********************\n");
55 printf("\n***********************\n");
56 printf("Vider la suite:\n");
57 while (estVide(myS)==false)
58 {
59 printf("Taille actuelle: %d \t",myS.n);
60 printf("Tete de la suite: %f \n", Elem(myS));
61 myS=reste(myS);
62 }
63 printf("\n***********************\n");
64 printf("La taille actuelle de la suite est: %d",myS.n);
65 printf(estVide(myS) ? "\t La suite est vide \n" : "\t La suite n’est pas vide \n");
66 return 0;
67 }
103
Chapitre A : Programmation en langage C des exercice du chapitre 1
104