Vous êtes sur la page 1sur 100

INFORMATIQUE APPLIQUÉE: ITI 3200

CONCEPTION
ETANALYSED'ALGORITHMES
Bouna Mohamed
Conception et Analyse d’algorithmes

Avant propos
L'Université virtuelle africaine (UVA) est fier de participer à l'amélioration de l'accès à
l'éducation dans les pays africains à travers la production de matériel didactique de qualité.
Nous sommes également fiers de contribuer aux connaissances mondiales comme nos
ressources pédagogiques sont pour la plupart accessibles de l'extérieur du continent africain.

Ce module a été développé dans le cadre d'un programme à un diplôme en informatique


appliquée, en collaboration avec 18 institutions partenaires africaines de 16 pays. Un total de
156 modules ont été élaborés pour assurer la disponibilité ou traduit en anglais, français et
portugais. Ces modules ont également été mis à disposition en tant que ressources éducatives
libres (REL) sur oer.avu.org.

Au nom de l'Université virtuelle africaine et notre patron, nos institutions partenaires, la Banque
africaine de développement, je vous invite à utiliser ce module dans votre établissement, pour
votre propre formation, de partager le plus largement possible et à participer activement à
l'avu les communautés de pratique de votre intérêt. Nous nous engageons à être en première
ligne de l'élaboration et le partage de ressources éducatives libres.

L'Université virtuelle africaine (UVA) est une organisation intergouvernementale


panafricaine créée par la location avec le mandat d'accroître sensiblement l'accès à un
enseignement supérieur de qualité et de formation à l'aide de l'information technologies
de la communication. Une Charte, l'établissement de l'avu en tant qu'organisation
intergouvernementale, a été signé ce jour par dix-neuf (19) Les gouvernements africains - le
Kenya, le Sénégal, la Mauritanie, le Mali, la Côte d'Ivoire, Tanzanie, Mozambique, République
démocratique du Congo, Bénin, Ghana, République de Guinée, Burkina Faso, Niger, Soudan
du Sud, Soudan, l'Éthiopie, la Gambie, la Guinée-Bissau et le Cap-Vert.

Les institutions suivantes ont participé au programme d'informatique appliquée : (1) Université
d'Abomey Calavi au Bénin ; (2) Université de Ougagadougou au Burkina Faso ; (3) l'Université
Lumière de Bujumbura au Burundi ; (4) l'Université de Douala au Cameroun ; (5) Université
de Nouakchott en Mauritanie ; (6) l'Université Gaston Berger au Sénégal ; (7) Université
des Sciences, des Techniques et technologies de Bamako au Mali (8) Ghana Institute of
Management and Public Administration ; (9) Université des Sciences et Technologies de
Kwame Nkrumah au Ghana ; (10) l'Université Kenyatta au Kenya ; (11) l'Université d'Egerton au
Kenya ; (12) l'Université d'Addis Abeba en Ethiopie (13) Université du Rwanda (14) ; Université
de Dar es Salaam en Tanzanie ; (15) l'Université Abdou Moumouni de Niamey au Niger ; (16)
l'Université Cheikh Anta Diop de Sénégal ; (17) Universidade Pedagógica au Mozambique ; et
(18) l'Université de la Gambie en Gambie.

Bakary Diallo,

Recteur de l'

Université virtuelle africaine

2
Crédits de production
Auteur
Bouna Mohamed El Hacen

Pair Réviseur

Djamal Abdou Nasser Seck

UVA – Coordination Académique

Dr. Marilena Cabral

Coordinateur global Sciences Informatiques Apliquées

Prof Tim Mwololo Waema

Coordinateur du module

Jules Degila

Concepteurs pédagogiques

Elizabeth Mbasu

Benta Ochola

Diana Tuel

Equipe Média
Sidney McGregor Michal Abigael Koyier

Barry Savala Mercy Tabi Ojwang

Edwin Kiprono Josiah Mutsogu

Kelvin Muriithi Kefa Murimi

Victor Oluoch Otieno Gerisson Mulongo

3
Conception et Analyse d’algorithmes

Droits d’auteur
Ce document est publié dans les conditions de la Creative Commons

Http://fr.wikipedia.org/wiki/Creative_Commons

Attribution http://creativecommons.org/licenses/by/2.5/

Le gabarit est copyright African Virtual University sous licence Creative Commons Attribution-
ShareAlike 4.0 International License. CC-BY, SA

Supporté par

Projet Multinational II de l’UVA financé par la Banque africaine de développement.

4
Table des matières
Avant propos 2

Crédits de production 3

Droits d’auteur 4

Supporté par 4

Aperçu du cours 8

Bienvenue au cours Conception et Analyse d’algorithmes . . . . . . . . . . . . . 8

Prérequis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

Matériaux. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

Objectifs du cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

Unités. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

Lectures et autres ressources. . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Unité 0. Évaluation diagnostique 13

Introduction à l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

Objectifs de l’unité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

Termes clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

Évaluation de l’unité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

Directives 14

Système de notation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

Correction 15

Lectures et autres ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

Unité 1. Complexité des algorithmes 19

Objectifs de l’unité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Termes clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Activités d’apprentissage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

Activité 1: Rappel et Notations . . . . . . . . . . . . . . . . . . . . . . . . . . 20

5
Conception et Analyse d’algorithmes

Introduction 20

Expression de la complexité 20

Exemples de complexités 21

Détermination de la complexité 22

Activité 2: Etude d’exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Complexité des instructions de répétition: 23

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

Activité 3: Estimation asymptotique . . . . . . . . . . . . . . . . . . . . . . . . 29

Introduction 29

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

Correction 33

Résumé de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

Évaluation de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Directives 34

Lectures et autres ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Unité 2. Techniques de conception d’algorithmes 35

Introduction à l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

Objectifs de l'unité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

Termes clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

Activités d’apprentissage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

Activité 1: Algorithme de glouton . . . . . . . . . . . . . . . . . . . . . . . . . 36

Introduction 36

Le principe glouton 36

Un premier exemple: Le monnayeur 36

Stratégie 36

Algorithme 37

Eléments de l’algorithme 37

Correction 39

Évaluation de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

6
Activité 2: Diviser pour régner . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

Introduction 41

Principe 41

Premier exemple : multiplication naïve de matrices 41

Analyse des algorithmes diviser-pour-régner 42

Master-Théorème 43

Exemples : 44

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

Correction 47

Activité 3: Programmation dynamique. . . . . . . . . . . . . . . . . . . . . . . 49

Introduction 49

Principe 49

Résumé de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Évaluation de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

Directives 50

Correction 50

Lectures et autres ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . .51

Unité 3. Structure de données dynamiques 52


Introduction à l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Objectifs de l’unité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Termes clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Activités d’apprentissage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

Activité 1: Arbres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

Introduction 53

Définition 53

Implémentation des arbres binaires 56

Implémentation des arbres n-aires 57

Parcours d’un arbre 60

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

Activité 2: Algorithmes des Graphes. . . . . . . . . . . . . . . . . . . . . . . . 63

5
Conception et Analyse d’algorithmes

Présentation 63

Parcours de graphes 64

Correction. 69

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

Activité 3: Plus court chemin et Flots . . . . . . . . . . . . . . . . . . . . . . . . 70

Introduction 70

Correction 75

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

Résumé de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

Évaluation de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

Directives 76

Correction 77

Lectures et autres ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

Unité 4. Structures de données spécialisées 79

Introduction à l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

Objectifs de l’unité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

Termes clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

Activités d’apprentissage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

Activité 1: Files de priorité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

Introduction  80

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

Correction 84

Activité 2: Arbres binaires de recherche. . . . . . . . . . . . . . . . . . . . . . 84

Présentation 84

Évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

Correction 86

Activité 3: Ensembles disjoints. . . . . . . . . . . . . . . . . . . . . . . . . . . 88

Introduction 88

Résumé de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

Évaluation de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

6
Directives 90

Réponses 90

Lectures et autres ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . .91

Résumé de l’unité. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

Évaluation du cours. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

Correction 94

Contrôle continu Test 2 95

Correction 96

Références du cours. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .97

7
Conception et Analyse d’algorithmes

Aperçu du cours
Bienvenue au cours Conception et Analyse d’algorithmes
Ce cours a pour but de donner aux étudiants quelques-unes des techniques avancées de
conception et d’analyse d’algorithmes. Ainsi, tous les grands thèmes de l’algorithme seront
abordés dans le cours : récursivité, Complexité temporelle et spatiale d’un algorithme,
programmation linéaire, NP-complétude, programmation dynamique, algorithmes
probabilistes, algorithmes d’approximation, etc.

Ce cours permettra les étudiants être capables d’analyser la complexité temporelle et


spatiale d’un algorithme, d’identifier les méthodes les plus appropriées pour la résolution des
problèmes algorithmiques, Connaître les principes de la conception des algorithmes et la
programmation dynamique, etc.

Prérequis

• Principes fondamentaux de l’organisation et de l’architecture de l’ordinateur


• Introduction à la programmation structurée
• Initiation au calcul des probabilités et à la statistique
• Algèbre linéaire

Matériaux
Les matériaux nécessaires pour compléter ce cours comprennent les :

• IDE pour développer en C ou C++


• Livres
• Vidéos en ligne
• Support de cours

Objectifs du cours
À la fin de ce cours, l’étudiant devrait être en mesure de:

• choisir et appliquer des algorithmes appropriés pour résoudre un problème


concret;
• Administrer la preuve de leurs connaissances en matière de structures de données
avancées : arbres de recherche binaires, tas, tables de hachage et graphes

8
Aperçu du cours

• connaître les structures de données avancées telles que les arbres de recherche
binaires, graphes;
• Appliquer les structures de données pour mettre en œuvre des algorithmes de
graphe fondamentaux de manière efficace;
• Utiliser des algorithmes et structures de données avancés pour résoudre des
problèmes de la vie réelle
• Comparer et mettre en opposition les coûts et avantages de la structure
dynamique et statique des données en termes de performances
• Sélectionner les algorithmes sur la base de leurs performances (complexité)
• évaluer la complexité des solutions retenues ;

Unités
Unité 0: Connaissances de base

Cette unité porte sur l’évaluation des connaissances de l’étudiant à l’entrée du module. Il
doit répondre à une série de questions relatives au contenu de ce module. Ce test visant à
connaître le niveau de préparation pour affronter les différents concepts du cours.

Unité 1: Complexité des algorithmes

L’objectif de cette unité est d’introduire la notion de complexité algorithmique et de présenter


les méthodes et les outils pour l’analyse de la complexité des algorithmes.

Unité 2: Techniques de conception d’algorithmes

Cette unité s’articule aux méthodes de conception d’algorithmes : glouton, diviser pour régner,
programmation dynamique, exploration, etc.

Unité 3: Structure de données dynamiques

Cette unité présente la fouille associative et les algorithme y afférant.

Cette unité porte sur les algorithmes d’arbres et des graphes : plus courts chemins, arbres
recouvrants de poids minimal, ….etc.

Unité 4: Structure de données spécialisées

Cette unité étudie les Files de priorité, les Arbres binaires de recherche et les ensembles
disjoints

9
Conception et Analyse d’algorithmes

Évaluation
Les évaluations formatives (vérification de progrès) sont incluses dans chaque unité.Les
évaluations sommatives (tests et travaux finaux) sont fournies à la fin de chaque module et
traitent des connaissances et compétences du module.

Les évaluations sommatives sont gérées à la discrétion de l’établissement qui offre le cours. Le
plan d’évaluation proposé est le suivant:

1 Evaluation des actions de l’apprenant 20%

2 Contrôle continu 30%

3 Examen final 50%

Plan

Unité Sujets et Activités Durée


estimée

Unité 0 Connaissances de base 5H

Unité 1 Rappel et Notations 15H

Etude d’exemples

Estimation asymptotique

Unité 2 Méthode Glouton 45H

Méthode Diviser pour régner

Programmation dynamique

Unité 3 Arbres 30H

Algorithmes des Graphes

Plus court chemin, Flots

Unité 4 Files de priorité 25H

Arbres binaires de recherche

Ensembles disjoints

10
Aperçu du cours

Lectures et autres ressources


Les lectures et autres ressources dans ce cours sont indiquées ci-dessous.

Unité 0

Lectures et autres ressources obligatoires:

• Jacques Courtin, Irène Kowarski. Initiation à l’algorithmique et aux structures de


données.
• Thomas H. Cormen. Algorithme - 3ème édition - Cours avec 957 exercices et 158
problèmes. 2010.
• D.S. Malik. Structures de données à l’aide de C++, Seconde Edition, 2010.
• Mark A. Weiss . Data Structures and Algorithm Analysis in C++. 2013
• Stroustrup, B. (2014). Programming Principles and Practice Using C and C++.
Addison Wesley ISBN0321992784
• Balagurusamy, E. (2008). Programming in ANSI C. (4ª ed). New Delhi: Tata
Mc-Graw-Hill

Unité 1

Lectures et autres ressources obligatoires:

• Mark A. Weiss . Data Structures and Algorithm Analysis in C++. 2013


• Pierre Tellier. ALGORITHMIQUE ET PROGRAMMATION EN C. Département
d’Informatique de l’Université Louis Pasteur
• SAMS Teach Yourself Data Structures And Algorithms In 24 hours. 1999
• V. Das, Principles of Data Structures and Algorithms using C and C++, 2008.

Unité 2

Lectures et autres ressources obligatoires:

• Acques Courtin, Irène Kowarski. Initiation à l’algorithmique et aux structures de


données.
• Mark A. Weiss . Data Structures and Algorithm Analysis in C++. 2013
• Concise Notes on Data Structures and Algorithms, Ruby Edition. Christopher Fox.
2012
• Herbert S. Wilf. Algorithms and Complexity 2nd Edition

11
Conception et Analyse d’algorithmes

Unité 3

Lectures et autres ressources obligatoires:

• Anany Levitin. Introduction to the Design and Analysis of Algorithms.


• Sara Baase, Allen Van Gelder. Computer Algorithms: Introduction to Design and
Analysis

Unité 4

Lectures et autres ressources obligatoires:

• Philippe Lacomme, Christian Prins, Marc Sevaux. Algorithmes de graphes. 2013

12
Unité 0. Évaluation diagnostique

Unité 0. Évaluation diagnostique


Introduction à l’unité
Cette unité a pour objectif de déterminer votre capacité de ce cours. Elle porte sur les boucles,
les structures conditionnelles, fonctions, les procédures, les tableaux, et la transformation de
l’algorithme à un langage de programmation.

Cette unité servira à préparer l’étudiant à une étude plus approfondie de la conception et
analyse d’algorithme.

Objectifs de l’unité
À la fin de cette unité, vous devriez être capable de:

• Écrire un algorithme correct


• Écrire un algorithme efficace
• Écrire des procédures et fonctions
• Décrire les composants d’un algorithme

Termes clés
Algorithme: Suites d’opérations élémentaires selon un
processus défini aboutissant à une solution.

Programme: Ensemble d’instructions.

Ordonnancement: Un mécanisme, qui consiste à


choisir parmi tous les processus éligibles, lequel va
devenir élu.

Structure séquentielle : Un ensemble de variables de


même nature organisées séquentiellement ; l’accès
à cette structure est fait directement par leur numéro
d’ordre ou bien en les parcourant une par une dans un
ordre.

Structure de contrôle conditionnelle : Elle permet à


un programme de modifier son traitement selon d’une
condition donnée.

Structures de contrôle itératives : Elles permettent


d’exécuter plusieurs fois de suite une ou plusieurs
instructions.

13
Conception et Analyse d’algorithmes

Évaluation de l’unité
Test

Directives
Réponse aux questions l’évaluation dure une heure.

Exercice Note (maximum 10 points)


1 2
2 4
3 4

Exercice 1 : (2points)

Ecrire le programme qui permet de calculer N factorielle

Exercice 2 : (4 points)

Ecrire un programme qui permet de saisir un tableau d’entiers de taille N et qui l’affiche de
telle sorte que tous les entiers pairs se retrouvent avant les entiers impairs.

Exercice 3 : (4 points)

Ecrire une procédure ou fonction permettant d’entrer deux valeurs M et N et d’afficher toutes
les valeurs paires entre M et N si M < N.

Système de notation
Le barème est indiqué à la fin de chaque question.

14
Unité 0. Évaluation diagnostique

Évaluation

Correction
Exercice 1

#include <stdio.h>

#include <stdlib.h>

long fact (short nombre) {

short indice;

long factorielle = 1; /∗ variable pour stocker le résultat*/

if (nombre == 0) /* le cas où il n’y a rien à calculer*/

return 1;

for(indice = nombre ; indice > 0; indice--)

factorielle = factorielle * indice;

return factorielle;

int main(void)

int nombre_lu;

printf(«De quel nombre souhaitez-vous la factorielle ? «);

if(scanf(«%d», &nombre_lu) != 1){

printf(«erreur de saisie\n»);

exit(1);

printf(«Factorielle (%d) = %ld\n», nombre_lu, fact(nombre_lu));

return 0;

15
Conception et Analyse d’algorithmes

Exercice 2

#include <stdlib.h>

#include <studio.h>

#define N 4

int main() {

int tab[N];

int i ;

/∗ lecture ∗/

for (i = 0; i < N; i = i + 1) {

printf (”lecture de l ’ entier numero %d :”, i); scanf(”%d”, &tab[i]);

/∗ affichage du tableau dans l’ordre ∗/

printf (”tab = \n{”);

for (i = 0; i< N;i = i + 1) {

printf (”%d ”, tab[i ]);

printf (”} \n”);

/∗ affichage du tableau dans l ’ordre pairs d’abord ∗/

/∗ Attention : une seule instruction dans chacun des for ∗/

/∗ donc on peut ne pas utiliser de { } ∗/

printf (”le tableau dans l’ordre = \n{”);

for (i =0; i< N; i = i + 1) { if(tab[i]%2 == 0) {

printf (”%d ”, tab[i ]);

for (i =0; i< N; i = i + 1){

if(tab[i]%2 != 0) {

printf (”%d ”, tab[i ]);

16
Unité 0. Évaluation diagnostique

printf (”} \n”);

return EXIT SUCCESS

Ecrire une fonction qui calcule la somme des éléments d’un tableau.

Ecrire une fonction qui inverse un tableau : le premier élément est permuté avec le
dernier, le deuxième avec l’avant dernier, etc.

Quel est le résultat des instructions suivantes :

int n, * ad1;

* ad1 = 3;

Exercice 3

Procedure calcul ;

Declaration Variable M, N : entier ;

Debut Lire (M, N);

Si M ≥ N Alors

Ecrire (‘Pas d»affichage’)

Sinon

Tantque M < N Faire

Debut

Si M mod 2 = 0 Alors

Ecrire (M)

FinSi ;

M + 1<- M

Fin

FinTantque

FinSi

Fin ;

17
Conception et Analyse d’algorithmes

Lectures et autres ressources

• Jacques Courtin. Initiation à l’algorithmique et aux structures de données. 1994


• D. Beauquier. J. Berstel et P. Chrétienne. Eléments d’algorithmique.1992

18
Unité 1. Complexité des algorithmes

Unité 1. Complexité des algorithmes


L’exécution d’un programme demande des ressources de l’ordinateur, par exemple un temps
de calcul pour effectuer des instructions, un espace mémoire pour stocker et manipuler le
programme et ses données. La performance d’un programme dépend de sa capacité à
minimiser le temps d’exécution et l’espace mémoire utilisé.

L’étude la complexité algorithmique sert à estimer la performance des algorithmes,


notamment en terme de temps d’exécution et d’espace mémoire. Cette notion peut sembler
abstraite, cependant elle est bien concrète, et son incidence sur le fonctionnement d’un
programme est réelle.

Quelle est l’utilité d’un programme qui donne un résultat au bout d’une semaine ? Il y a alors
une nécessité d’évaluer la performance d’un algorithme avant de le programmer.

Dans cette unité on abordera les questions suivantes :

• Peut-on calculer ou estimer le temps de calcul nécessaire à un programme pour


s’exécuter sur un ordinateur ?
• Peut-on savoir si un programme/algorithme est optimal ?
• et peut-on estimer formellement qu’un algorithme A est meilleur qu’un
algorithme B ?

Objectifs de l’unité
À la fin de cette unité, vous devriez être capable de:

• pouvoir prévoir le temps d’exécution d’un algorithme


• pouvoir comparer deux algorithmes réalisant le même traitement

Termes clés
Complexité  algorithmique : Elle est donnée par un
ordre de grandeur théorique du temps de calcul et/ou
de l’espace mémoire utilisé en fonction d’une mesure
des données.

Le temps d’exécution : c’est le nombre d’opérations


élémentaires exécutées. Il dépend de la taille des
données fournies en entrée.

19
Conception et Analyse d’algorithmes

La taille d’une donnée : elle est mesurée par le


nombre de bits indispensables à sa représentation en
machine. Cette mesure ne dépend pas du matériel
utilisé pour programmer l’algorithme.

Complexité temporelle : le temps d’exécution


nécessaire pour exécuter un algorithme.

Complexité spatiale : la quantité de mémoire.

Analyse empirique : elle sert à étudier la complexité


d›un algorithme, en temps et en espace, de manière
expérimentale.

Analyse théorique : Elle consiste à évaluer le pseudo-


code de l’algorithme

Activités d’apprentissage

Activité 1: Rappel et Notations

Introduction
Un algorithme doit résoudre un problème de manière efficace, c’est-à-dire : rapide (en termes
de temps d’exécution) ; et économe en ressources (espace de stockage, mémoire utilisée).
Dans cette unité, on va présenter des outils qui nous permettront d’évaluer la qualité théorique
des algorithmes proposées.

Expression de la complexité
Les opérations élémentaires comprennent les évaluations d’expressions, les affectations, et
plus généralement tous les traitements en temps constant ou indépendant de l’algorithme
(entrées-sorties, . . . . Le détail de l’exécution de ces opérations au niveau du processeur par
l’intermédiaire d’instructions machine donnerait des résultats identiques à un facteur constant
près.

Soit m la machine, il existe des constantes m et M telles que pour tout algorithme A et pour
toute donnée d fournie en entrée de l’algorithme, si l’on note T(d) le nombre d’opérations
élémentaires effectuées par l’algorithme avec la donnée d en entrée et T_(m ) (d) le temps de
calcul du programme implémentant A avec la donnée d en entrée,

20
Unité 1. Complexité des algorithmes

mT(d)≤T_(m ) (d)≤MT(d)

Soit, en utilisant les notations de Landau :

T=θ(T_m )

On peut donc définir la complexité en temps d’un algorithme comme le nombre d’opérations
élémentaires T(n) effectuées lors de son exécution sur des données de taille n. Il s’agit donc
d’une fonction.

On distingue

la complexité dans le pire des cas (worst case complexity) : T_(pire ) (d) est le nombre maximal
d’opérations calculé sur toutes les données de taille n ;

la complexité dans le meilleur des cas (best case complexity) : T_(min ) (d) est le nombre
minimal d’opérations calculé sur toutes les données de taille n ;

la complexité en moyenne (average case complexity) : T_(moy ) (d) le nombre moyen


d’opérations calculé sur toutes les données de taille n, en supposant qu’elles sont toutes
équiprobables.

Analyser un algorithme consiste à évaluer la ou les fonctions de complexité qui lui sont
associées. En pratique, on cherche à évaluer le comportement asymptotique de ces fonctions
relativement à la taille des entrées.

Exemples de complexités
en temps constant : T(n) = θ(1)

logarithmiques : T(n) = θ(log n),

polylogarithmiques : T(n) = θ(〖(log n)〗^c) pour c > 1,

linéaires : T(n) = θ(n),

quasi linéaires : T(n) = θ(n log n),

quadratiques : T(n) = θ(n^2),

polynomiaux : T(n) = θ(n^K) pour k ∈ N,

exponentiels : T(n) = θ(k^N) pour k > 1.

Voici un exemple d’une machine qui fait une opération élémentaire en 1µs (soit 106 opérations
par seconde), le tableau suivant montre les temps d’exécutions approximatifs de quelques
classes de problèmes en fonction de leurs tailles.

21
Conception et Analyse d’algorithmes

Tableau 1.1.1 : Temps d’exécutions approximatifs en fonction de leurs tailles.

Nous notons que pour un algorithme, on considère uniquement les opérations fondamentales,
c’est à dire celles qui ont de l’influence sur la complexité :

Pour un algorithme de recherche, il s’agit de comparer deux objets : proportionnel à la taille


de l’objet.

Pour un algorithme de tri : comparaisons + transferts.

Pour un algorithme de fichiers : accès aux disques

Attention aux grands nombres : un petit nombre est un type prédéfini de taille connue ou
d’opérations élémentaires.

Détermination de la complexité
Dans l’espace

La taille est connue en fonction du type. Pour les ordres de grandeur, seuls les tableaux sont
pris en considération. La seule difficulté apparaît dans les allocations dynamiques (mémoire
demandée en cours d’exécution) ou dans les algorithmes récursifs, il faut compter le nombre
d’appels pour avoir une idée de la taille.

Dans le temps

Pour les enchaînements séquentiels : L’ordre de grandeur de la séquence correspond à la


valeur maximale.

Séquences d’actions élémentaires :

• (1)
• Pour l’appel de fonction, il faut inclure la complexité de cette fonction dans la
complexité de la séquence.
• Alternative (Si…alors…sinon) : On prend toujours la complexité maximale. Sinon
on peut aussi faire une complexité moyenne mais il faut avoir des informations sur
le résultat moyen de la condition.
• Boucles : Complexité = (Complexité du corps de boucle) x (Nombre de tours).
• Algorithme récursif : Complexité = (Complexité de la fonction) x (nombre
d’appels).

22
Unité 1. Complexité des algorithmes

Conclusion

La complexité temporelle d’un algorithme est le nombre d’opérations élémentaires


(affectations, comparaisons, opérations arithmétiques) effectuées par un algorithme. Ce
nombre est exprimé par la taille n des données.

Évaluation
On considère deux manières de représenter ce que l’on appelle des « matrices creuses»,
c’est-à-dire des matrices d’entiers contenant environ 90% d’éléments nuls :

1. La matrice est représentée par un tableau à deux dimensions dont les


cases contiennent les éléments.

2. La matrice est représentée par un tableau à une dimension. On ne


s’intéresse qu’aux éléments de la matrice qui ne sont pas nuls. Chaque
case du tableau contient un triplet (i, j, a) correspondant à l’indice de
ligne, l’indice de colonne, et la valeur d’un élément non nul.

3. Le problème considéré consiste à calculer la somme des éléments d’une


matrice

• Écrire un algorithme permettant de calculer cette somme, pour chacune des


deux représentations, puis de comparer leur complexité spatiale (espace
mémoire occupé) et leur complexité temporelle (nombre d’opérations à
effectuer).
• Que peut-on conclure de cette comparaison ?
• Montrer qu’il existe une valeur critique du nombre d’éléments non nuls à
partir de laquelle une méthode l’emporte sur l’autre.

Activité 2: Etude d’exemples

Complexité des instructions de répétition:


Complexité de la Boucle :

La complexité de la boucle est égale à la complexité du corps multipliée par le nombre de fois
qu’elle est répétée.

Démarche :

1. On détermine le nombre de répétitions

2. On multiplie ce nombre par la complexité du corps de cette boucle.

23
Conception et Analyse d’algorithmes

Structure conditionnelle

Pour la structure conditionnelle (si …. Sinon …) : on prend le maximum entre le alors et le sinon.

Appels de fonctions: Temps d’exécution de la fonction

Procédures et fonctions: leur complexité est déterminée par celle de leur corps.

1.Algorithmes de tri

Il y a deux types fondamentaux d’opérations pour les algorithmes de tri : les comparaisons et
les transferts.

Tris simples

Ils sont faciles à mettre en œuvre mais leur complexité est mauvaise (O(N²)) et ne sont
utilisables que s’il y a peu de valeurs à trier (104 valeurs).

Sélection ordinaire

Idée : on cherche le minimum et on le met à la case 0, puis on cherche le minimum suivant que
l’on met à la case suivante et ainsi de suite. Cela correspond à l’algorithme suivant :

Action TriSelection (ES : T :Ttableau, E : N : entier)

Var, I, pos, posmin, : entiers

Début

Pour I de 0 à N-2 faire

{recherche de la position du minimum entre I et N-1}

posminçT[I]

Pour pos de I+1 à N-1 faire

Si T[pos]<T[posmin] alors posminçpos {comparaison}

Echanger (T, I, posmin) {transfert}

Fin

Le fait que tout le tableau soit en entrée implique qu’il y a autant de transferts que de
comparaisons. On peut la trouver ainsi :

Nombre de transferts : 3 par boucle I, faite N-1 fois donc : 3N-3 soit une complexité
(N). C’est donc optimal.

Nombre de comparaisons : 1 par boucle pos, et il y a (N-1)+(N-2)+⋯+1=N(N-1)/2


boucles. La complexité est donc de O(N²), ce qui est très mauvais.

Tri Bulle

On compare la première case avec la seconde. Si la case 1 est plus petite que la case 2, on

24
Unité 1. Complexité des algorithmes

change de place. Puis on compare la case 2 et la case 3 et ainsi de suite.

Action Tri Bulle (ES : T : Ttableau ; E : N : entier)

Var : I, J, entiers fini : booléen

Début

I<-0

Répéter :

Fini<-vrai

Pour J de n-1 à I+1 par pas de –1 faire

Si T[j]<T[j-1] alors Echanger (T, J, J-1) et fini faux.

I<-I+1

Jusqu’à (fini=vrai ou I=N-1)

Fin

2.Complexité :

Dans le meilleur des cas (tableau déjà trié) : on a N-1 comparaisons O(N) et 0 transferts O(1)

Dans le pire des cas (tableau trié à l’envers) on obtient N(N-1)/2 nombres de transferts et N(N-
1)/2 nombres de comparaisons. On a alors une complexité de O(N²) ; ce qui est très mauvais.

En moyenne : Le calcul d’une moyenne est trop compliqué. On admettra que la complexité
moyenne est de O(N²).

Insertion séquentielle

Il s’agit de l’algorithme suivant :

Action tri_insertion (….)

Var : pos, I : entiers

Element : Tinformations

Trouvé : booléen

Début :

Pour I de 1 à N-1 faire :

{on cherche la position de T[i] entre 0 et i-1 de droite à gauche en décalant les plus grands}

Element <-T[i]

Pos<-i-1,

trouvé<-false

25
Conception et Analyse d’algorithmes

tant que (non trouvé et pos>=0) faire

si T[pos]>Element alors T[pos+1]<-T[pos] et pos<-pos-1

sinon trouvé<-vrai

{on range l’élément}

T[pos+1]<-Element {transfert}

Fin

Fin

3.Complexité de cet algorithme :

Dans le meilleur des cas (tableau trié) la complexité du nombre de comparaisons et de


transferts est de O(N)

Dans le pire des cas : O(N²)

En moyenne : O(N²)

Remarque :

Il existe une amélioration à cet algorithme : en faisant une recherche de la position d’insertion
par dichotomie. La complexité du nombre de comparaisons est alors O(nlog n), ce qui est
optimal.

Lorsqu’on trie de gros objets, on ne peut pas éviter de les comparer mais on peut éviter de
les déplacer en créant un deuxième tableau contenant les indices de l’ordre du tri du premier
tableau.

Tris rapides

Le principe de cet algorithme se trouve dans l’unité 2.

Généralement,

• On choisit une valeur


• On réorganise le tableau en fonction de ce pivot
• Récursivement, on trie les 2 côtés.

On constate qu’on trie des parties de tableau, il faut donc indiquer le début et la fin de la
portion à traiter : les paramètres seront donc T, début, fin.

Action Tri Rapide (ES T : Ttableau, E : début, fin :entiers)

Var : pospivot : entier

Début

Si fin > début {il faut au moins deux cases !}

26
Unité 1. Complexité des algorithmes

Réarranger (T, début, fin, pospivot)

Tri Rapide (T, début, pospivot-1)

Tri Rapide (T, pospivot+1, fin

Fin

On peut trier la table entière en appelant cette fonction.

4.Complexité de cet algorithme

En moyenne, cet algorithme est optimal : (nlogn) et au maximum la complexité pourrait être
O(N²)

Tri par Tas

Un tas est caractérisé par :

• Un arbre parfait
• Tout nœud a une valeur et permet d’accéder à celle de tous ses descendants.
Les méthodes associés sont : estVide, minimum, insérer, supprimerMin

Principe :

• Transformer le tableau à trier en tas,


• Extraire un à un les éléments min (racines) en conservant la structure de tas, et les
stocker dans cet ordre dans le tableau trié.

Voici un pseudo code qui décrit l’algorithme de tri par tas :

void triParTas (tableau d’entiers tabATrier)

entier n=tabATrier.length;

Tas unTas = new Tas(n);

début pour i = 0 à n-1 faire //construction du tas associé à tabATrier

unTas.inserer(tabATrier[i]); //O(log n) pour chaque i

fait; pour i=0 à n-1 faire

//on sélectionne un par un les minimums successifs du tas, qui sont mis à leur place dans le
tableau tabATrier[i]=unTas.minimum(); //O(1) pour chaque i

//suppression du minimum en gardant la structure de tas

unTas.supprimerMin(); //O(log n) pour chaque i

fait;fin

complexité de tri par tas :

27
Conception et Analyse d’algorithmes

On a n itérations pour chaque boucle.

insérer et supprimer en O(log n)

Ce qui donne O(n log n).

Conclusion

On a vu les différents algorithmes de tri et leurs complexités, et on peut en conclure.

Tri par tas : meilleure complexité

Tri rapide : très efficace en pratique

Tri par insertion excellent si liste initiale presque triée, et peut débuter sans liste initiale
complète

Évaluation
On considère, pour effectuer la recherche d’un élément dans un tableau, la recherche
séquentielle et la recherche dichotomique. On s’intéresse à leur complexité temporelle.
Pour cela, considérer un tableau ayant mille éléments (version trié, et version non trié).

1. Pour chaque algorithme, et pour chaque version du tableau, combien de


comparaisons sont à effectuer pour :

a. Trouver un élément qui y figure ?

b. Trouver un élément qui n’y figure pas ?

2. Quels sont les cas où le tableau est parcouru complètement et les cas
où un parcours partiel est suffisant ?

3. Conclure en donnant la complexité temporelle pour chaque algorithme

28
Unité 1. Complexité des algorithmes

Activité 3: Estimation asymptotique

Introduction
En complexité, on ne prend en considération que des fonctions positives de N dans IR. On
ne veut pas évaluer de manière exacte les temps d’exécution (d’autant que ça dépend des
machines…). En réalité, le calcul de la complexité de manière exacte n’est pas raisonnable vu
la quantité d’instructions de la plupart des programmes ; de plus il n’est pas utile pour pouvoir
comparer deux algorithmes. On se contente en général de trouver des approximations.

Définitions

Soit la fonction g définie par g∶ N→ R^+

une fonction positive. On définit les notations suivantes :

Notation grand-O

O(g)

désigne l’ensemble des fonctions positives

pour lesquelles il existe une constante strictement positive

et un rang n_0

tels que : f (n) ≤ αg(n)

pour tout n ≥ n_0

Ces fonctions croissent au plus aussi vite que g.

Notation grand- Ω

Ω(g) constitue des fonctions positives f pour lesquelles il existe une constante strictement
positive α

et un rang n_0

tels que f (n) ≥ αg(n)

pour tout n ≥ n0

Ces fonctions croissent au moins aussi vite que g

Notation grand-θ

θ(g) = O(g) ∩ Ω(g).

Ces fonctions croissent aussi vite que g (qui ont même ordre de grandeur).

Exemple

Le tri fusion a un coût f(n) = αnlnn+βn

29
Conception et Analyse d’algorithmes

avec α > 0

Cette fonction appartient à O(nlnn),

même Θ(nlnn)

plus précisément on a l’équivalence f ∼ αnlnn

Interprétations

Interprétation de la notation Ο :

Si f et g sont des fonctions positives réelles : f(n) =O(g(n)) si et seulement si le rapport f/g est
borné à l’infini (on dit f est bornée par g). Figure 1.3.1 donne l’interprétation géométrique de
la notation O.

Figure 1.3.1 : Interprétation géométrique de la notation O

Interprétation de grand-Ω

Si f et g sont des fonctions positives réelles : f(n)= Ω(g(n) )

alors g est une borne inférieure asymptotique pour f. La figure 1.3.2 donne une interprétation
géométrique de la notation Ω

Figure 1.3.2 : Interprétation géométrique de la notation

30
Unité 1. Complexité des algorithmes

Interprétation de la notation θ:

Si f et g sont des fonctions positives réelles : f(n)=θ(g(n))

implique que f et g ont le même ordre de grandeur. La figure 1.3.3 donne une interprétation
géométrique de la notation θ

Figure 1.3.3 : Interprétation géométrique de la notation

Classes de complexité

Les algorithmes usuels peuvent être classés en un certain nombre de grandes classes de
complexité, On présente dans cette partie les classes de complexité courantes :

La classe constante O(1) : le temps d’exécution n’augmente pas quand le paramètre croit : par
exemple l’affectation, la comparaison.

La classe logarithmique O(log n) : le temps d’exécution augmente faiblement quand le


paramètre croit. Par exemple conversion du décimal au binaire.

La classe linaire O(n) : le temps d’exécution augmente linéairement quand le paramètre croit.
Par exemple somme des n premiers entiers ou parcours un ensemble de données.

La classe quasi linéaire O(n*log(n)) : augmentation un peu supérieure à O(n). Par exemple
algorithmes qui décomposent un problème en d’autres plus simples, traités indépendamment
et qui combinent les solutions partielles pour calculer la solution générale.

La classe quasi quadratique 〖O(n〗2) : le temps d’exécution est multiplié par quatre lorsque le
paramètre double. Par exemple parcourir un exemple de données en utilisant deux boucles
imbriquées.

La classe polynomiale : quand le paramètre double, le temps d’exécution est multiplié par 2i.
Par exemple parcourir un ensemble de données en utilisant deux boucles imbriquées.

31
Conception et Analyse d’algorithmes

La classe exponentielle : 〖O(i〗n) : quand le paramètre double, le temps d’exécution est élevé
à la puissance 2. Par exemple générer tous les sous-ensembles possibles d’un ensemble de
données.

La figure 1.3 illustre les différentes classes de complexité précédentes.

Figure 1.3 : Classes de complexité

Conclusion

On peut conclure que les algorithmes usuels peuvent être classés en un certain nombre de
grandes classes de complexité.

Évaluation
Soit les algorithmes suivants avec un temps d’exécution pour une longueur de données
n.

Donner leurs complexités asymptotiques respectives, et indiquer quel(s) règle(s) vous


avez appliqués.

1. Algorithme A1 T(n) = 3n + 2

2. Algorithme A2 T(n) = 6

3. Algorithme A3 T(n) = 4n 2 + n + 2

4. Algorithme A4

Exécuter A1;

Exécuter A2;

Exécuter A3;

32
Unité 1. Complexité des algorithmes

5. Algorithme A5

pour i de 1 à n faire

Exécuter A3;

fin pour

Exécuter A1;

6. Algorithme A6

pour i de 1 à 5 faire

Exécuter A1;

fin pour

Correction

1. Complexité asymptotique O(n)

2. Complexité asymptotique O(1)

3. Complexité asymptotique O()

4. Règle de somme : Complexité asymptotique O()

5. Règle de produit pour la boucle et ensuite règle de somme : Complexité


asymptotique O()

6. Fois la règle de somme : Complexité asymptotique O(n)

Résumé de l’unité
Cette unité a permis de comprendre l’utilité de l’évaluation de la performance d’un algorithme
avant de le programmer. La complexité d’un algorithme a été abordé afin de pouvoir
déterminer qu’un algorithme est optimal et de le comparer à un autre afin de déterminer
lequel est meilleur.

33
Conception et Analyse d’algorithmes

Évaluation de l’unité
Test

Directives
Les barèmes sont donnés à la suite de chaque question

1. Considérer les algorithmes suivants avec un temps d’exécution pour


une longueur de données n.

2. Déterminer leurs complexités asymptotiques respectives, et indiquer


quel(s) règle(s) vous aviez appliquées.

Lectures et autres ressources

• Jacques Courtin. Initiation à l’algorithmique et aux structures de données. 1994

34
Unité 2. Techniques de conception d’algorithmes

Unité 2. Techniques de conception


d’algorithmes
Introduction à l’unité
Dans ce chapitre, on va présenter les structures de données de base telles que les listes, les
piles et les files, on va présenter également l’importance de ces structures à travers des
exemples d’application.

Objectifs de l'unité
À la fin de cette unité, vous devriez être capable de:

• Comprendre les structures de données ;


• Comprendre ce que c’est qu’une liste, une pile et une file ;
• Maîtriser l’importance et l’utilisation de ces structures de données.

Termes clés
Paradigme : Un paradigme de programmation
Informatique désigne la manière d’analyser le monde
dans le but de concevoir un programme Informatique.

Fonction objectif : est utilisé en optimisation


mathématique pour désigner une fonction qui sert
de critère pour déterminer la meilleure solution à un
problème d'optimisation.

Récursivité: On appelle récursive toute fonction ou


procédure qui s’appelle elle-même.

35
Conception et Analyse d’algorithmes

Activités d’apprentissage

Activité 1: Algorithme de glouton

Introduction
Les algorithmes pour problèmes d’optimisation exécutent en général une série d’étapes,
chaque étape proposant un ensemble de choix. Pour de nombreux problèmes d’optimisation,
la programmation dynamique est une approche bien trop lourde pour déterminer les meilleurs
choix ; d’autres algorithmes, plus simples et plus efficaces, peuvent faire l’affaire. Un algorithme
glouton fait toujours le choix qui lui semble le meilleur sur le moment.

Autrement dit, il fait un choix localement optimal dans l’espoir que ce choix mènera à une
solution globalement optimale. Cette activité étudie les problèmes d’optimisation qui peuvent
se résoudre par des algorithmes gloutons.

Le principe glouton
Un algorithme de glouton est un algorithme qui résout des problèmes d’optimisation (qui
optimise une fonction objectif), il cherche à construire une solution pas à pas

• en espérant obtenir une solution optimale en effectuant une suite de choix : à


chaque étape on fait le meilleur choix local
• Pas de retour en arrière : à chaque étape de décision dans l’algorithme, le choix
qui semble le meilleur à ce moment est effectué.
• Progression descendante : choix puis résolution d’un problème plus petit

Un premier exemple: Le monnayeur


On dispose des pièces des monnaies correspondant aux valeurs P = {25,25,25,10,10,5,1,1,1,1}

, montant à payer (n = 67).

Sortie: ensemble des pièces utilisées (S = {25,25,10,5,1,1}), le moins de pièces possibles.

Etant donnée une quantité c

entière, on veut trouver une façon de "rendre" la somme c

avec un nombre de pièces minimum.

Stratégie
ajouter la plus grande pièce possible

tant que la somme n’excède pas le montant à payer

36
Unité 2. Techniques de conception d’algorithmes

Algorithme
RENDRE-MONNAIE(P,n)

S←Ø

i←1

tant que n≠0

et i ≤ P.longueur faire

si n−P[i] ≥ 0 alors

n ← n−P[i]

S ← S∪{P[i]}

i ← i+1

si n = 0

retourner S

sinon

erreur “pas de solution”

P = {25,25,25,10,10,5,1,1,1,1}: toujours une solution optimale (n < 105)

P = {8,4,2}: toujours une solution optimale si elle existe (si n est pair, n ≤ 14)

P = {5,2}* : solution existe toujours (si n ≥ 4), mais on ne la trouve pas toujours (n = 6)

P = {5,4,1}* : solution existe toujours, on la trouve toujours, mais elle n’est pas toujours
optimale (n = 8)

Eléments de l’algorithme
Tableau des candidats C (tableau des pièces disponibles)

poids des candidats C[i].poids (valeurs des pièces)

ensemble de solution S (pièces utilisées)

fonction SOLUTION(S) (∑_(s∈S) s.poids=n

fonction REALISABLE(S) ∑_(s∈S) s.poids≤n

37
Conception et Analyse d’algorithmes

Stratégie

trier le tableau des candidats par ordre de poids décroissant

tester tous les candidats C[i] exactement une fois

si C[i] peut être ajouter à S, garder le

si C[i] ne peut pas être ajouté à S, jeter le

ne retester jamais un candidat déjà testé

Algorithme général

GLOUTON(C)

trier C par ordre de poids décroissant

S←Ø

i←1

tant que non SOLUTION(S) et i ≤ C.longueur faire

si REALISABLE(S∪{C[i]}

) alors

S ← S∪{C[i]}

i ← i+1

si SOLUTION(S)

retourner S

sinon

erreur “pas de solution”

Complexité :

Temps d’exécution: O(nlgn+n f(n)+ng(n))

Temps d’exécution de SOLUTION(S) = O(f(n))

Temps d’exécution de REALISABLE(S) = O(g(n))

Conclusion

Cette activité vous a présenté les problèmes d’optimisation qui peuvent se résoudre en
utilisant les algorithmes gloutons.

38
Unité 2. Techniques de conception d’algorithmes

Évaluation de l’unité
Le problème du rendu de monnaie.

Comment rendre une somme donnée avec le minimum de pièces. Nous regarderons tout
d'abord notre système monétaire : des pièces de 1, 2, 5, 10, 50, 100, 200.

1. Comment rendre 263 centimes d'euros (2,63 €)

2. Trouvez un algorithme glouton pour ce problème

3. Montrez que cet algorithme est optimal

4. Qu'est ce qui se passe si les pièces ont comme valeur 1, 3, 4 ? Comment


rendre la somme de 6 ?

5. Faire le plein sans panne sèche

Un automobiliste cherche à optimiser le nombre d'arrêts qu'il doit


effectuer pour faire le plein d'essence sur un parcours donné. Son
véhicule lui permet de faire k

kilomètres avec un seul plein. Il y a n stations-service sur son trajet,


de positions p1,...,pn

(distances au point de départ).

Proposez une méthode glouton pour ce problème et montrez qu'elle


fournit une solution optimale.

Correction

1. Pour rendre la monnaie sur 263 centimes d’euros, on rend une pièce de
200, de 50, de 10, de 2 et de 1.

2. L’algorithme glouton pour ce problème est le suivant : on rend toujours la


pièce de la plus grande valeur que l’on peut. On suppose que les valeurs
des pièces sont stockées par ordre croissant dans un tableau p : rendre_
pieces(entier S):

rendu := 0

i := 0

r := tableau rempli de 0 de même taille que p

tant que (S-rendu != 0) faire

si p[i] > S-rendu alors

i := i+1

sinon alors

39
Conception et Analyse d’algorithmes

r[i] := r[i] + 1

rendu := rendu + p[i]

retourner r

Attention! Cet algorithme ne termine pas toujours, essayez de trouver


quels sont les cas dans lesquels il ne termine pas.

3. Montrons que l’algorithme est optimal : Montrons d’abord la propriété


de choix glouton : Supposons qu’il existe s > 200 pour lequel le rendu
de monnaie optimal ne contient pas de pièce de 200. On peut avoir au
maximum une pièce de 100, car sinon ou pourrait remplacer les deux
pièces de 100 par une de 200, au maximum une pièce de 50, 4 pièces de
10, 1 pièce de 5 et 2 pièces de 2 (sinon on peut remplacer par 1 pièce de
5 et une pièce de 1) et 1 pièce de 1. La somme maximale que l’on peut
donc obtenir de manière optimale sans pièce de 200 est 1 + 2 × 2 + 5 +
4 × 10 + 50 + 100 = 200 qui est inférieure ou égale à 200 (et qui pourrait
être remplacée par 200).

Montrons la propriété de sous-structure optimale. Soit P une solution


optimale pour une somme s et c le choix glouton, P^'= P \ {c

} est une solution optimale pour s - c

, car si il y a une meilleure solution P^"

pour s - c

, alors P^" ∪ {c}

est une meilleure solution que P pour s.

4. Si les pièces ont comme valeurs 1,3,4, alors l’algorithme glouton va rendre
la monnaie sous la forme d’une pièce de 4 et de deux pièces de 1, alors
que rendre deux pièces de 3 aurait été optimal, il ne fournit donc pas la
solution optimale.

5. La méthode glouton pour ce problème consiste à utiliser à chaque fois


la dernière station avant la panne sèche. Montrons maintenant que cet
algorithme est optimal.

Notons la solution optimale y1,...,yp

et la solution gloutonne g1,...,gq

, on a donc p ≤ q

. Comme l’on prend la dernière station avant d’être en panne, on a g1

qui est au moins aussi loin que y1, intuitivement, on déduit que gk est
au moins aussi loin que yk

, et donc que l’algorithme glouton donne la solution optimale.

40
Unité 2. Techniques de conception d’algorithmes

Activité 2: Diviser pour régner

Introduction
La méthode de diviser pour régner est une méthode qui permet, parfois de trouver des
solutions efficaces à des problèmes algorithmiques. L’idée est de découper le problème initial,
de taille n, en sous-problèmes de taille plus petite, résoudre les sous-problèmes récursivement
(ou les résoudre directement si de taille suffisamment petite), puis recombiner les solutions des
sous-problèmes pour obtenir la solution du problème d’origine.

Principe
Le paradigme diviser-pour-régner implique trois étapes à chaque niveau de la récursivité :

• Diviser : le problème en sous-problèmes plus petits


• Régner : sur les sous-problèmes en les résolvant récursivement ou, si la taille d’un
sous-problème est assez réduite, le résoudre directement ;
• Combiner : les réponses des sous-problèmes afin d'obtenir la réponse au
problème de départ

Premier exemple : multiplication naïve de matrices


On s’intéresse dans cet exemple à la multiplication de matrices carrées de taille n

Algorithme naïf

MULTIPLIER-MATRICES(A, B)

Soit n

la taille des matrices carrées A et B

Soit C une matrice carrée de taille n

Pour i ← 1 à n faire

Pour j ← 1 à n faire

cij ← 0

Pour k ← 1 à n faire

cij ← cij +aik * bkj

retourne C

Cet algorithme effectue θ(n^3)

multiplications et autant d’additions.

41
Conception et Analyse d’algorithmes

Algorithme « diviser pour régner » naïf

Dans la suite nous supposerons que n

est une puissance exacte de 2. Décomposons les matrices A, B et C en sous-matrices de taille


n/2 *n/2

. L’équation C = A *B peut alors se récrire :

(r s t u )=(a b c d )(e f g h )

En développant cette équation, nous obtenons :

r = ae+b f ;

s = ag+bh;

t = ce+df

u = cg+dh:

Chacune de ces quatre opérations correspond à deux multiplications de matrices carrées de


taille n/2 et une addition de telles matrices.

À partir de ces équations on peut aisément dériver un algorithme « diviser pour régner » dont
la complexité est donnée par la récurrence :

T(n) = 8T(n/2)+ θ(n^2);

L’addition de matrices carrées de taille n/2

étant en θ(n^2).

Analyse des algorithmes diviser-pour-régner


Lorsqu’un algorithme contient un appel récursif à lui-même, son temps d’exécution peut
souvent être décrit par une équation de récurrence, qui décrit le temps d’exécution global
pour un problème de taille n

À partir du temps d’exécution pour des entrées de taille moindre. On peut alors se
servir d’outils mathématiques pour résoudre la récurrence et trouver des bornes pour les
performances de l’algorithme.

Une récurrence pour le temps d’exécution d’un algorithme diviser-pour-régner s’appuie sur les
trois étapes du paradigme de base. Comme précédemment, soit T(n) le temps d’exécution
d’un problème de taille n

Si la taille du problème est suffisamment petite, disons n ≤c

pour une certaine constante c, la solution directe prend un temps constant que l’on écrit θ(1).

Supposons que l’on divise le problème en a

sous-problèmes, la taille de chacun étant 1/b

42
Unité 2. Techniques de conception d’algorithmes

de la taille du problème initial. (Pour le tri par fusion, tant a que b

valent 2, mais nous verrons beaucoup d’algorithmes

diviser-pour-régner dans lesquels a ≠ b

.) Si l’on prend un temps D(n)

pour diviser le problème en sous-problèmes et un temps C(n)

pour construire la solution finale à partir des solutions aux sous-problèmes, la relation de
récurrence prend alors la forme :

T(n)= {θ(1), n≤c aT(n/b) + D(n) + C(n) sinon .

Master-Théorème
On considère un problème de taille n, qu’on découpe en a sous-problèmes de taille n/b
permettant de résoudre le problème.

Soient a ≥ 1

et b >

1 deux constantes, soit f(n)

une fonction et soit T(n)

une fonction définie pour les entiers positifs par la récurrence :

T(n) = aT (n/b) + f(n)

Alors T(n)

peut être bornée asymptotiquement comme suit :

Si f(n) = O(n^(logb a-ε)

pour une certaine constante ε> 0

, alors T(n)= θ(n^(logb a)-

Si f(n)= θ(n^(logb a)

alors T(n)=θ(n^(logb alogn)

Si f(n) = Ω(n^(logba-ε)

pour une certaine constante ε> 0

, et si af(n/b)≤cf(n)

pour une certaine constante c < 1

et n suffisamment grand, alors T(n) = θ(f(n)).

43
Conception et Analyse d’algorithmes

Exemples :
Algorithme tri par fusion

Cet algorithme suit fidèlement la méthodologie diviser-pour-régner. Intuitivement, il agit de la


manière suivante :

Diviser : Diviser la suite de n éléments à trier en deux sous-suites de n/2 éléments chacune.

Régner : Trier les deux sous-suites de manière récursive en utilisant le tri par fusion.

Combiner : Fusionner les deux sous-suites triées pour produire la réponse triée.

Algorithm Tri fusion

procedure TriFusion(T)

if n ≤ 1 then

return T

else

n = |T|

T1 = TriFusion(T[0 . . . n/2])

T2 = TriFusion(T[ n/2 + 1 . . . n − 1])

return Fusion(T1,T2)

end if

end procedure

La complexité T(n)

de cet algorithme est la suivante :

T(0)= 0,

T(1)= 0,

T(n) ≈ 2 T(n/2) + n - 1

le ≈ est là car il y a des parties entières à considérer pour être rigoureux.

Dichotomie

Cet algorithme fait partie des méthodes diviser pour régner. Elle consiste pour un tableau de
taille n à exécuter un algorithme de façon à réduire le problème à un tableau de taille n/2. On
répète alors l'algorithme de réduction sur ce dernier tableau. Ainsi, il suffit de connaitre la
résolution pour un problème de taille faible (typiquement n=1 ou n=2) pour obtenir la totalité
de la résolution. Ce type d'algorithme est souvent implémenté de manière récursive. Lorsque
cette technique est utilisable, elle conduit à un algorithme très efficace et très lisible.

44
Unité 2. Techniques de conception d’algorithmes

Soit T est un tableau trié de taille n, on s’intéresse à l’algorithme qui recherche si x est un
élément de T au moyen d’une dichotomie. Pour l’algorithme récursif, on spécifie un indice de
début d et de fin f, et on recherche si x est dans T entre les positions d et f. L’appel initial se
fait avec d = 0 et f = n − 1.

Algorithme Dichotomie

procedure Recherche(T,x,d,f)

if f < d then

return Faux

else

m = b b+a 2 c

if T[m] = x then

return Vrai

else if T[m] < x then

return Recherche(T,x,m + 1,f)

else

return Recherche(T,x,d,m − 1)

end if

end if

end procedure

On peut distinguer les cas suivants:

a=1

car on appelle soit à gauche, soit à droite (ou on a fini, mais on se place dans le pire des cas),

b=2

car les sous-problèmes sont de taille n/2 ;

d=0

car on se contente de renvoyer la solution, donc en temps constant. La complexité de la


dichotomie est donc O(log n).

45
Conception et Analyse d’algorithmes

Conclusion

On a vu dans cette activité une technique de conception d’algorithmes : diviser pour régner.
On a donné le Master-Théorème qui permet d’obtenir des bornes asymptotiques sur la plupart
des fonctions rencontrées lors de l’analyse de coût des algorithmes Diviser pour Régner.

Évaluation
Exercice 1 – Valeur encadrée

Étant donné un tableau trié d’entiers A[s..f] et deux entiers ("bornes") a ≤ b

, on cherche s’il existe un élément A[i] du tableau tel que a ≤ A[i] ≤ b

(s’il y en a plusieurs trouvez un)

Exemple Soit le tableau A[1..5] = [3, 7, 8, 43, 556] et les bornes a = 40, b = 50. Dans ce
cas-là la valeur encadrée existe : c’est A[4] = 43.

1. Donnez (en pseudocode) un algorithme « diviser-pour-régner » qui résout


ce problème. Expliquez brièvement.

2. Analyser sa complexité

Exercice 2

Le but de cet exercice est de choisir l’algorithme de type diviser pour régner le plus rapide
pour un même problème.

1. Pour résoudre un problème manipulant un nombre important de


données, on propose deux algorithmes :

A. Un algorithme qui résout un problème de taille n en le divisant en


deux sous-problèmes de taille n/2 et qui combine les solutions en
temps quadratique,

B. Un algorithme qui résout un problème de taille n en le divisant en


quatre sous-problèmes de taille n/2 et qui combine les solutions
en temps O(√n). Lequel faut-il choisir ?

2. Même question avec les algorithmes suivants :

A. Un algorithme qui résout un problème de taille n en le réduisant

à un sous problème de taille n/2 et qui combine les solutions en


temps O(√n).

B. Un algorithme qui résout un problème de taille n en le divisant


en deux sous-problèmes de taille n/2 et qui combine les solutions

en temps constant.

46
Unité 2. Techniques de conception d’algorithmes

Correction
Exercice 1

1. Coupons le tableau en deux moitiés : A[s..m] et A[m + 1..f]. Les trois cas
ci-dessous correspondent à trois positions de l’élément de milieu A[m] par
rapport à l’intervalle [a, b].

À gauche : A[m] < a. Dans ce cas-là tous les éléments de la moitié


gauche du tableau sont aussi < a, et ne peuvent pas appartenir à
l’intervalle demandé [a, b]. On va chercher la valeur encadrée dans la
moitié droite seulement.

Dedans :a ≤ A[m] ≤ b

.On a déjà trouvé une valeur encadrée A[m]. On retourne son indice.

À droite :b < A[m]. Ce cas est symétrique au premier, la recherche


peut être limitée à la moitié gauche du tableau.

Cette analyse mène à la fonction récursive suivante chercher(s,f) qui


renvoie l’indice de la valeur encadrée

dans le tableau A[s..f] (ou ⊥ s’il n’y en a pas. On suppose que le


tableau A, et les bornes a,b sont des variables globales.

fonction chercher(s,f)

// cas de base

si (s=f)

si (A[s] ∈ [a; b])

retourner s

sinon retourner ⊥

// on divise pour régner

m=(s+f)/2

si A[m] < a

ind= chercher(m+1,f)

sinon si a ≤A[m] ≤b

ind=m

sinon

ind=chercher(s,m)

retourner ind

47
Conception et Analyse d’algorithmes

2. On a réduit un problème de taille n à un seul problème de la taille n/2 et


des petits calculs en O(1), donc la complexité satisfait la récurrence :

T(n)= T(n/2)+ O(1).

En la résolvant par Master Théorème on obtient que T(n) = O(log n).

Exercice 2

Question 1

T(n)= 2 T (n/2)+n^(2 )

ce qui implique T(n)=θ(n^2)

Question 1a

a = 4, b = 2, f(n) = O(√n),

cas 1. ε=3/2

, ce qui implique T(n)=θ(n^2)

Question 1b

les algorithmes sont équivalents

Question 2

a = 1, b = 2, f(n)=Ω(n^(1/2)),

cas n=3. ε=1/2,c=1/√2

par exemple. T(n)=θ(√n)

Question 2a

a=b=2, T(n)=θ(1),

cas 1. ε=1

, ce implique T(n)=θ(n).

Question 2b

Le premier algorithme est meilleur.

48
Unité 2. Techniques de conception d’algorithmes

Activité 3: Programmation dynamique

Introduction
La programmation dynamique, comme la méthode diviser-pour-régner, résout des problèmes
en combinant des solutions de sous-problèmes. (le mot « Programmation », dans ce contexte,
fait référence à une méthode tabulaire et non à l’écriture de code informatique.). Comme nous
l’avons vu à l’activité 2.2, les algorithmes diviser-pour-régner partitionnent le problème en
sous-problèmes indépendants qu’ils résolvent récursivement, puis combinent leurs solutions
pour résoudre le problème initial. La programmation, quant à elle, peut s’appliquer même
lorsque les sous-problèmes ne sont pas indépendants, c’est-à-dire lorsque des sous-problèmes
ont des sous-sous-problèmes communs. Dans ce cas, un algorithme diviser-pour-régner fait
plus de travail que nécessaire, en résolvant plusieurs fois le sous-sous-problème commun. Un
algorithme de programmation dynamique résout chaque sous-sous-problème une seule fois et
mémorise sa réponse dans un tableau, évitant ainsi le recalcul de la solution chaque fois que le
sous-sous-problème est rencontré.

Lorsque les sous-problèmes ne sont pas indépendants (c’est-à-dire lorsque les sous-problèmes
ont des sous-sous-problèmes communs) la programmation récursive se révèle le plus souvent
très inefficace car elle conduit à résoudre de nombreuses fois les sous-sous-problèmes
communs.

Principe
Afin de résoudre un problème, on commence tout d’abord par résoudre les plus petits sous-
problèmes et on stocke les valeurs de ces sous-problèmes dans une table de programmation
dynamique. Ensuite, on utilise ces valeurs pour calculer la valeur des sous-problèmes de plus
en plus grands, jusqu’à obtenir la solution de notre problème global.

Résumé de l’unité
Dans cette unité, vous avez étudié les structures de données de base telles que les listes,
les piles et les files, et découvert l’importance de ces structures à travers des exemples
d’application à travers les algorithmes glouton, diviser-pour-règner et la programmation
dynamique.

49
Conception et Analyse d’algorithmes

Évaluation de l’unité
Vérifiez votre compréhension!

Directives
Répondre aux questions de l’exercice suivant.

Exercice 1

Le coût de la non panne sèche

Le professeur Bell conduit une voiture entre Amsterdam et Lisbonne sur l’autoroute E10.
Son réservoir, quand il est plein, contient assez d’essence pour faire n kilomètres, et sa
carte lui donne les distances entre les stations-service sur la route.

1. Donnez une méthode efficace grâce à laquelle Joseph Bell pourra


déterminer les stations-service où il peut s’arrêter, sachant qu’il souhaite
faire le moins d’arrêts possible.

2. Démontrez que votre stratégie est optimale.

Correction

1. La solution consiste à s’arrêter le plus tard possible, c’est-à-dire à la


station la plus éloignée du dernier point d’arrêt parmi celles à moins de n
kilomètres de ce même point.

2. Démontrez que votre stratégie est optimale.

On procède de la même manière que pour prouver l’optimalité de


l’algorithme glouton pour la location de voiture : on considère une
solution F = (x_1,x_2,...,x_p)

fournie par notre algorithme et une solution optimale G =


(y_1,y_2,...,y_q),

on a donc p ≥ q

et on cherche à montrer que p = q

Ici les deux suites sont bien sûr des suites de stations-service, triées
de celle la plus proche du point de départ à celle la plus éloignée. Soit
k le plus petit entier tel que : ∀i < k,xi = yi

, et xk ≠yk.

Par définition de notre algorithme, on a xk > yk

50
Unité 2. Techniques de conception d’algorithmes

. On construit à partir de la solution optimale G =


(y1,y2,...,yk-1,yk,yk+1,...,yq)

la suite de stations-service G^' = (y_1,y_2,...,y_(k-1),x_k,y_(k+1),...,y_q)

. G^'

est une liste de même taille. Montrons que c’est aussi une solution —et
donc une solution optimale. Il nous faut vérifier qu’il n’y a pas de risque
de panne sèche c’est-`a-dire que :

– x_k -y_(k-1) ≤ n.

F est une solution, donc x_k -x_(k-1) ≤ n.

Par conséquent, x_k -y_(k-1)= x_k -x_(k-1) ≤ n.

– y_(k+1) - 〖x 〗_k≤ n.

G est une solution, donc y_(k+1) - y_k ≤ n

. D’où y_(k+1) - x_k < y_(k+1) - y_k ≤ n .

Si y_(k+1) < 〖x 〗_k

, on peut en fait supprimer y_(k+1)

de G^'

est obtenir une solution strictement meilleure ce qui contredirait


l’optimalité de G.

G^'

est donc bien une solution optimale de notre problème.

Nous avons donc construit à partir de G

une solution G^'

qui contient un élément en commun de plus avec F. On itère jusqu’à


ce que l’on ait une solution optimale contenant F

. Alors p ≤ q

, ce qui prouve le résultat attendu.

Lectures et autres ressources


Les lectures et autres ressources de cette unité se trouvent au niveau des lectures et autres
ressources du cours.

51
Conception et Analyse d’algorithmes

Unité 3. Structure de données


dynamiques
Introduction à l’unité
La structure d’arbre est très utilisée dans plusieurs domaines en Informatique, par exemple
pour représenter les algorithmes de recherche opérationnelle, dans le traitement d’image
et dans les bases de données. Sans arbre, pas de base de données relationnelles, pas de
système de fichiers, pas de compression MP3, etc. On cherche à construire une structure de
données qui permet d’ajouter de nouveaux éléments, de retirer des éléments existants et
éventuellement de mettre à jour un élément.

Après une description générale des arbres, nous attaquerons les graphes et leurs algorithmes.

Objectifs de l’unité
À la fin de cette unité, vous devriez être capable de:

• Comprendre les arbres (la définition, les propriétés, les opérations)


• Implémenter un arbre en langage C.
• Mettre en œuvre des schémas génériques d’algorithmes (arbre, graphe)
• Comprendre les algorithmes des graphes (parcours, Recherche de plus courts
chemins)

Termes clés
Arbre binaire de recherche : une structure de données
qui peut supporter des opérations courantes sur des
ensembles dynamiques.

Clés : Chaque nœud d’un arbre binaire de recherche


est associé à une clé.

Arbre équilibré : un arbre qui maintient


une profondeur équilibrée entre ses branches.

52
Unité 3. Structure de données dynamiques

Activités d’apprentissage

Activité 1: Arbres

Introduction
Un arbre est une structure de données récursive générale, représentant un arbre au sens
mathématique. C’est un cas particulier de graphe qui n’a qu’une seule source et aucun cycle.

On distingue deux catégories d’éléments dans un arbre :

• les feuilles; éléments ne possédant pas de fils dans l’arbre,


• les nœuds internes; éléments possédant des fils (sous-branches).

Les répertoires sur la plupart des systèmes d’exploitation actuels forment un arbre, on peut
distinguer :

• Les arbres binaires


• Les arbres binaires de recherche
• Les arbres n-aires
• Les arbres AVL
• Les arbres rouge-noir

On commence par décrire les opérations de base sur les arbres binaires, et on exprime ensuite
d’autres opérations à l’aide de ces opérations de base.

Définition
Un arbre est une structure de donnée avancée qui généralise la liste. Dans un arbre, une cellule
peut avoir plusieurs successeurs, au contraire de la liste qui possède un seul successeur pour
la cellule. On parle de nœud (au lieu de cellule). Un nœud père peut avoir plusieurs nœuds
fils. Un fils n’a qu’un seul père, et tous les nœuds ont un ancêtre commun appelé la racine de
l’arbre. La figure 3.1.1 illustre un exemple d’un arbre.

Un arbre est alors un ensemble fini T de un ou plusieurs éléments tel que :

il y a un élément particulier appelé racine de l’arbre.

les éléments restants sont partitionnés en m ≥ 0 ensembles disjoints T1, T2,…,Tm qui sont
eux-mêmes des arbres (ce sont les sous-arbres issus de la racine).

53
Conception et Analyse d’algorithmes

Figure 3.1.1 : Exemple d’un arbre

Le nombre de nœuds est défini de manière récursive en arbre de moins de m nœuds, ce qui
permet d’éviter le problème de circularité. Chaque nœud est défini par son étiquette et ses
sous-arbres.

Terminologie

On utilise pour les arbres une terminologie inspirée des liens de parenté, comme montré dans
la figure 3.1.2  :

Figure 3.1.2 : Exemple d’un arbre

• Nœud est l’élément d’un arbre


• Feuille est un nœud qui n’a pas de fils. B, D, E, G
• Fils (C) : D, E, F
• Successeur (C) : D, E, F, G
• Un frère d’un nœud p est un fils du père de p, et qui n’est pas p. par exemple :
Frère (C) : {D, E, F} :
• Le degré d’un nœud est le nombre de ses enfants, par exemple : Degré (C) =
3
• La profondeur d’un nœud est la longueur du chemin entre la racine et ce nœud.

54
Unité 3. Structure de données dynamiques

• Par convention, la racine est de profondeur 0 ;


• Un arbre ordonné est un arbre enraciné dans lequel tous les fils de chaque nœud
sont ordonnés.
• Un arbre non ordonné est un arbre dans lequel l’ordre des sous-arbres est
indifférent.

Autres représentations

Figure 3.1.3 : Autres représentations d’arbres

Définition de Arbre binaire

Un arbre binaire est un arbre avec une racine, et où chacun des nœuds possède :

• soit aucun successeur,


• soit un successeur, à gauche ou à droite,
• soit deux successeurs.

La Figure 3.1.4 montre un exemple qui illustre un arbre binaire, dessiné avec sa racine en haut
et ses branches en descente, à l’inverse d’un arbre dans la nature :

Lorsqu’il y a un seul sous-arbre présent, il faut distinguer le droit du gauche.

55
Conception et Analyse d’algorithmes

Figure 3.1.4 : Un arbre binaire

Un arbre binaire peut aussi être réduit à l’arbre vide. Pour comprendre l’importance de l’arbre
vide, voici comment on peut donner une définition récursive tout à fait cohérente d’un arbre
binaire : il s’agit soit de l’arbre vide, soit d’un nœud racine auquel sont accrochés un arbre à
gauche et un arbre à droite aussi. Si l’on oubliait le cas de l’arbre vide, la définition perdrait
toute signification : notamment l’arbre ne pourrait avoir aucune feuille, c’est-à-dire un nœud
auxquels sont accrochés deux arbres vides.

Implémentation des arbres binaires

• On peut effectuer les opérations suivantes sur les arbres binaires :


• est−vide (arbre a) : booléen : teste si a est vide ou non ;
• G(arbre a) : arbre : renvoie le sous-arbre gauche de a (si a est non vide) ;
• D(arbre a) : arbre : renvoie le sous-arbre droit de a (si a est non vide) ;
• val(arbre a) : clé : donne la clé associée à la racine de a.
• Arbre(clé x; arbre a1, a2) : arbre – crée une racine contenant x et avec a1 et a2
comme sous-arbre (g et d).

On peut aussi utiliser les fonctions suivantes pour modifier les champs d’un nœud interne :

• FixerVal(clé x; arbre a) : arbre - place x à la racine de a (ok si a est l’arbre vide).


• FixerAG(arbre a, g) : arbre - remplace le sous-arbre gauche de a par g.
• FixerAD(arbre a, d) : arbre - remplace le sous-arbre droit de a par d.

Mais on préférera écrire directement : val(a) := x, G(a) := g et D(a) := d.

Voici un exemple d’implémentation d’un arbre binaire en langage C :

typedef struct node * tree;

struct node

56
Unité 3. Structure de données dynamiques

TElement value;

tree left;

tree right;

};

On remplacera le type TElement par type ou la structure de données que l’on veut utiliser
comme entité significative des nœuds de l’arbre.

D’autre part, on peut remarquer que l’arbre vide sera représenté par la constante NULL (voir la
figure 3.1.5 : un arbre binaire).

Figure 3.1.5 : Représentation d’arbre binaire

Implémentation des arbres n-aires


Si on veut représenter un autre type d’arbre, on a deux solutions : Soit nous connaissons à
l’avance le nombre maximal de fils des nœuds (ce qui veut dire que l’on connaît l’arité de
l’arbre), soit nous ne la connaissons pas.

Dans le premier cas, nous pouvons utiliser un tableau pour stocker les fils. Ainsi pour un arbre
d’arité 4 nous aurons l’implémentation suivante :

typedef struct node * tree;

struct node

TElement value;

tree child[4];

};

57
Conception et Analyse d’algorithmes

Figure 3.1.6 : Représentation d’arbre binaire n-aires

La deuxième solution consiste à utiliser une liste chainée pour la liste des fils.

typedef struct node * tree;

typedef struct cell * list;

struct cell{

tree son;

list next;

};

struct node{

TElement value;

list child;

};

Quelques Opérations de base

Déclaration d’un arbre

typedef struct t_arbre {

Info *info;

struct t_arbre *filsG, *filsD;

} *Arbre, Noeud;

Arbre a = NULL;

- Accès au fils gauche

58
Unité 3. Structure de données dynamiques

Arbre filsGauche(Arbre a)

Arbre res = NULL;

If ( !vide(a) ) res = a->filsG;

return res;

Ajout d’un fils à gauche

fonction insertionG(ArbreBinaire Info a, ArbreBinaire Info n) : ArbreBinaire

début

si NON vide(a) filsG(a) := n;

insertionG := a;

fin

Feuille

fonction estFeuille(ArbreBinaire Info a):Booléen

variables

Booléen res init FAUX;

début

si vide(filsG(a)) ET vide(filsD(a)) alors res := VRAI; finsi

estFeuille := res;

fin

Hauteur d’un arbre

fonction hauteur(ArbreBinaire a) : entier

variables

entier res init 0 ;

début

si NON vide(a) alors res := max(hauteur(filsG(a), hauteur(filsD(a))) + 1 ; finsi

hauteur := res ;

fin

Suppression d’un élément dans un arbre

fonction supparbre (info v, ArbreBinaire a) : ArbreBinaire

59
Conception et Analyse d’algorithmes

variables

ArbreBinaire res = ARBREVIDE ;

début

si NON vide(a) alors

si v = info(a) alors

si vide(filsG(a)) alors res := supparbre (v, filsD (a)) ;

sinon res:=enraciner(info(filsG(a)), suppdarbre(info(filsG(a)),filsG(a)), suppdarbre(v, filsD(a)));

finsi

sinon res := enraciner(info(a), suppdarbre(v,supparbre(info(filsG(a)),filsG(a)))), suppdarbre(v, filsD


(a))) ;

finsi

finsi

supparbre := res ;

fin

Parcours d’un arbre


On va attaquer des algorithmes de parcours d’un arbre. Cela permet de visiter tous les
nœuds de l’arbre et éventuellement d’appliquer une fonction sur ces nœuds. On distinguera
deux types de parcours : le parcours en profondeur et le parcours en largeur. Le parcours en
profondeur permet d’explorer l’arbre en explorant jusqu’au bout une branche pour passer à
la suivante. Le parcours en largeur permet d’explorer l’arbre niveau par niveau. C’est à dire
que l’on va parcourir tous les nœuds du niveau un puis ceux du niveau deux et ainsi de suite
jusqu’à l’exploration de tous les nœuds.

Parcours en profondeur

Le parcours en profondeur de l’arbre, montré dans la figure 3.1.7, donne 1,2,3,4,5,6,7,8,9,10

Figure 3.1.7 : Parcours en profondeur

Parcours préfixe de l’arbre : 1-2-3-4-5-6-7-8-9-10

60
Unité 3. Structure de données dynamiques

Celui-ci représente un seul parcours en profondeur de l’arbre. Il s’agit d’un parcours d’arbre en
profondeur à gauche d’abord et préfixe.

Profondeur parcours préfixe

Les types de parcours infixe, suffixe et post-fixe sont les plus importants, en effet chacun à
son application particulière. Nous Commençons par le parcours préfixe, celui-ci traite la racine
d’abord.

procedure parcours_prof_prefixe(T : arbre)

si non EstVide(T) alors

traiter_racine(T);

parcours_prof_prefixe(FilsGauche(T));

parcours_prof_prefixe(FilsDroit(T));

fin si

La fonction (ou procédure) traiter_racine, est une fonction que vous définissez vous même, il
s’agit par exemple d’une fonction d’affichage de l’élément qui est à la racine.

Profondeur parcours infixe

procedure parcours_prof_infixe(T : arbre)

si non EstVide(T) alors

parcours_prof_infixe(FilsGauche(T));

traiter_racine(T);

parcours_prof_infixe(FilsDroit(T));

fin si

Profondeur parcours suffixe

procedure parcours_prof_suffixe(T : arbre)

si non EstVide(T) alors

parcours_prof_suffixe(FilsGauche(T));

parcours_prof_suffixe(FilsDroit(T));

traiter_racine(T);

fin si

Conclusion

Dans cette activité, on a étudié les arbres en général et les arbres binaires en
particulier. D’après l’étude qu’on a faite sur les algorithmes itératifs du parcours en profondeur
dans un arbre binaire on peut déceler les problèmes qui y sont liés. Pour aboutir à l’algorithme

61
Conception et Analyse d’algorithmes

proprement dit pour chacun des différents parcours en profondeur, nous retenons qu’il faut
procéder par deux phases qui sont l’empilement et le dépilement. Par ailleurs cette étude
nous a aussi permis de nous familiariser à la résolution de problèmes formalisés de façon
récursive avec des procédés itératifs

Évaluation
Voici une liste aléatoire de 15 éléments. Notez que vous pouvez faire cet exercice en
prenant une autre liste aléatoire ; évidemment, il y a peu de chances que vous obteniez le
même résultat.

25 60 35 10 5 20 65 45 70 40 50 55 30 15

On s’intéresse aux arbres binaires de recherche.

1. Rappelez les propriétés des arbres binaires de recherche.

2. Rappelez ce qu’est l’opération d’adjonction aux feuilles.

3. Construire l’arbre binaire de recherche par adjonction des valeurs


aux feuilles, dans l’ordre de la liste. On s’attachera particulièrement à
expliquer le raisonnement.

4. Donner la liste infixée de l’arbre obtenu, en justifiant votre raisonnement.


Quelle propriété a-t-elle ? Est-ce toujours le cas ?

Correction

1. Un arbre binaire de recherche est tel que tout nœud a une clé supérieure
à celles des nœuds de son sous arbre gauche et inférieure à celles des
nœuds de son sous arbre droit. On peut encore dire que la clé d’un nœud
est comprise entre la plus grande clé de son sous arbre gauche et la plus
petite clé de son sous arbre droit.

2. L’adjonction aux feuilles consiste à rechercher où devrait être la feuille si


elle était déjà dans l’arbre et à la mettre à cet endroit.

3. 25 est introduit dans un arbre vide. 60 étant plus grand que 25 est mis
à droite de 25. 35 étant plus grand que 25 et plus petit que 60 est mis à
gauche de 60. 10 est plus petit que 25 est mis à gauche de 25. 5 est plus
petit que 25 et plus petit que 10, il est mis à gauche de 10. 20 est plus
petit que 25 et plus grand que 10, il est mis à droite de 10. 65 est plus
grand que 25 et plus grand que 60, il est mis à droite de 60. 45 est plus
grand que 25, plus petit que 60 et plus grand que 35, il est mis à droite

62
Unité 3. Structure de données dynamiques

de 35. 70 est plus grand que 25, plus grand que 60 et plus grand que 65.
Il est mis à droite de 65. 40 est plus grand que 25, plus petit que 60, plus
grand que 35 et plus petit que 45, il est mis à gauche de 45. 50 est plus
grand que 25, plus petit que 60, plus grand que 35 et plus grand que 45,
il est mis à droite de 45. 55 est plus grand que 25, plus petit que 60, plus
grand que 35, plus grand que 45, plus grand que 50, il est mis à droite de
50. 30 est plus grand que 25, plus petit que 60 et plus petit que 35, il est
mis à gauche de 35. Enfin, 15 est plus petit que 25, plus grand que 10 et
plus petit que 20, il est mis à gauche de 20. On obtient l’arbre suivant.

4. La liste infixée de l’arbre est obtenue en faisant une exploration à main


gauche, et en sortant le nœud après l’exploration de son sous arbre
gauche et avant l’exploration de son sous arbre droit. C’est donc la
suivante: 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70 Cette liste
est une liste ordonnée. C’est une des propriétés d’un arbre binaire de
recherche que sa liste infixée soit une liste triée, qui découle directement
de la propriété énoncée dans la question A.

Activité 2: Algorithmes des Graphes

Présentation
Le graphe est une structure de données très utilisées dans divers domaines par exemple :

• réseaux de transport routier, transport d’eau, d’électricité


• réseaux de transport de données (réseau de téléphonie fixe, GSM, wifi )
• réseaux d’informations (bases de données, web, réseaux sociaux . . .).

Cette structure permet de représenter des relations entre éléments. Un graphe est constitué
d’un ensemble S de sommets mis en relation par un ensemble A d’arêtes ou arcs, couples
d’éléments de S.

Il y a des types particuliers de graphes. Parmi eux :

63
Conception et Analyse d’algorithmes

Les graphes non-orientés où la directionnalité de l’arête n’intervient pas : ainsi si (i,j) ∈ A alors
(j,i) ∈ A.

• Les arbres qui constituent des graphes particuliers avec définition d’un sommet
racine : tout sommet de l’arbre ne peut alors être atteint que par un chemin
unique depuis la racine.
• Les graphes acycliques qui ne comportent aucun cycle (un graphe comporte un
cycle lorsqu’il est possible de trouver un chemin arbitrairement long entre deux
sommets de celui-ci).
• Les automates qui représentent des graphes dont les arêtes sont étiquetées avec
définition d’une liste d’états initiaux et d’états terminaux.
• Les graphes d’arêtes évaluées où l’on associe à chaque arête une valeur.
Nous nous intéressons ici à la structure générale de graphe (non-orienté et
non-acyclique).

La figure 3.2.1 montre un graphe G orienté d’ordre 5. Il y a une boucle sur les sommets B et E.

Figure 3.2.1 : Graphe G orienté

Parcours de graphes
Parcours en largeur

On présente ici l’algorithme de parcours en largeur PL(G,s). Cet algorithme s’implémente à


l’aide d’une file.

couleur(s) ← GRIS

d(s) ← 0

pere(s) ← NIL

F ← {s}

pour tout v ∈ V (G)\s faire

couleur(v) ← BLANC

d(v) ← ∞

pere(v) ← NIL

fin pour

tant que F non vide faire

64
Unité 3. Structure de données dynamiques

v ← tete(F)

pour tout w ∈ Adj(v) faire

si couleur(w) = BLANC alors

couleur(w) ← GRIS

d(w) ← d(v) + 1

pere(w) ← v

Enfiler(F, w)

fin si

fin pour

Defiler(F)

couleur(v) ← NOIR

fin tant que

Parcours en profondeur

On présente ici l’algorithme de parcours en profondeur PP(G, s). Cet algorithme se décrit
naturellement de manière récursive. 

pour tout v ∈ V (G) faire

couleur(v) ← BLANC

pere(v) ← NIL

fin pour

temps ← 0

pour tout v ∈ V (G) faire

si couleur(v) = BLANC alors

VisiterPP(v)

fin si

fin pour

Algorithme VisiterPP(v) :

d(v) ← temps ← temps + 1

couleur(v) ← GRIS

pour tout w ∈ Adj(v) faire

65
Conception et Analyse d’algorithmes

si couleur(w) = BLANC alors

pere(w) ← v

VisiterPP(w)

fin si

fin pour

couleur(v) ← NOIR

f(v) ← temps ← temps + 1

Applications du parcours en profondeur

On va donner deux applications du parcours en profondeur : le tri topologique et le calcul des


composantes fortement connexes.

Le tri topologique

Le tri topologique d’un graphe orienté acyclique G = (V,E) sert à ordonner tous les sommets de
sorte que si

est un arc de

alors

apparaît avant

dans le tri. Cette notion est fréquente dans de nombreux problèmes d’ordonnancement :
chaque sommet représente une tâche à effectuer, et les arcs indiquent celles de ces tâches qui
doivent être réalisées avant une autre tâche.

fonction Tri-topologique (G graphe acyclique) : liste ;

début

pour chaque sommet s de G faire

visité [s] ← faux ;

L ← liste-vide ;

pour chaque sommet s de G faire

si non visité [s] alors Topo (s) ;

retour (L) ;

fin

procédure Topo (s sommet de G) ;

début

visité [s] ← vrai ;

66
Unité 3. Structure de données dynamiques

pour chaque t adjacent à s faire

si non visité [t] alors Topo (t) ;

Ajouter s en tête de L ;

fin

Composantes fortement connexes

Cette recherche s’effectue selon les étapes suivantes :

• La réalisation d’un parcours en profondeur pour calculer les instants de fin de


traitement de chaque sommet du graphe.
• La classification des sommets par ordre décroissant des instants de fin de
traitement.
• La construction du graphe transposé du graphe G.
• La réalisation d’un parcours en profondeur sur le graphe transposé en s’appuyant
sur les sommets classés par instant de fin.

Voici l’algorithme correspondant:

Exécuter PP(G) et trier les sommets selon un ordre décroissant de f

Calculer G^(-1)

Exécuter PP(G^(-1))

Retourner les arborescences obtenues comme composantes fortement connexes de G

Arbre de poids minimum

Soit le graphe non orienté connexe G = (X,E)

et la fonction de poids ω∶ E → R_+

.On cherche donc un sous-graphe T = (X, F) connexe dont la somme des poids des arêtes est
minimale.

Algorithme de Kruskal

Cet algorithme est basé sur la caractérisation des arbres comme des graphes acycliques
maximaux au sens de l’inclusion.

Algorithme (G, w):

pour tout v de V (G) faire

composante(v) ← {v}

fin pour

Trier E(G) dans un ordre croissant (e1, . . . , em) en fonction de w(e)

i←1

67
Conception et Analyse d’algorithmes

E(T) ← {}

tant que |E(T)| < n − 1 faire

si ei = (u, v) et composante(u) 6= composante(v) alors

E(T) ← E(T) ∪ {ei}

UNIFIER(composante(u), composante(v))

fin si

i←i+1

fin tant que

retourner E(T)

Algorithme de Prim

IL est un algorithme glouton qui calcule un arbre couvrant minimal dans un graphe


connexe valué et non orienté. 

Algorithme (G, w)

F ← FILE PRIORITE(V (G), cle)

pour tout v de V (G) faire

cle(v) ← ∞

fin pour

cle(r) ← 0

pere(r) ← NIL

tant que F

∅ faire

u ← EXTRAIRE MIN(F)

pour tout v ∈ Adj(u) faire

si v ∈ F et w(u, v) < cle(v) alors

pere(v) ← u

cle(v) ← w(u, v)

fin si

fin pour

fin tant que

68
Unité 3. Structure de données dynamiques

Conclusion

Dans cette activité on a présenté les principaux algorithmes de graphes (les algorithmes
courants de la théorie des graphes). L’algorithme de Prim maintient une composante connexe.
L’algorithme de Kruskal maintient l’absence de cycle.

Évaluation
Pour le graphe pondéré donné dans la figure 3.2.2, on cherche à trouver l’arbre couvrant
minimum en appliquant un algorithme du cours.

Figure 3.2.2

1. Choisissez un algorithme (écrivez juste son nom s’il s’agit d’un algorithme
connu).

2. Appliquez l’algorithme (dessinez toutes ses itérations).

Correction.

1. On choisit l’algorithme de Prim

2. On construit un arbre en partant d’un sommet initial et en ajoutant chaque


fois une arête du poids min qui le touche par une extrémité.

On part du sommet 0.

On ajoute l’arête 0-1. Les sommets de l’arbre sont 0,1.

On ajoute l’arête 1-4. Les sommets de l’arbre sont 0,1,4.

On ajoute l’arête 4-3. Les sommets de l’arbre sont 0,1,3,4.

On ajoute l’arête 4-7. Les sommets de l’arbre sont 0,1,3,4,7.

On ajoute l’arête 7-8. Les sommets de l’arbre sont 0,1,3,4,7,8.

On ajoute l’arête 8-2. Les sommets de l’arbre sont 0,1,2,3,4,7,8.

69
Conception et Analyse d’algorithmes

On ajoute l’arête 4-5. Les sommets de l’arbre sont 0,1,2,3,4,5,7,8.

On ajoute l’arête 5-6.

L’arbre couvre tout le graphe. On choisit l’algorithme de Prim.

Activité 3: Plus court chemin et Flots

Introduction
Le problème du plus court chemin consiste à déterminer le chemin de coût minimum reliant un
nœud

à un nœud

. Nous allons présenter, dans cette activité, les algorithmes les plus utilisées pour résoudre ce
problème notamment l’algorithme de DIJKSTRA et l’algorithme de BELLMAN – FORD.

L’algorithme de DIJKSTRA est le plus utilisé car il est simple à mettre en œuvre, efficace en
temps d’exécution et bien adapté aux situations courantes.

Plus court chemin à partir d’un sommet

Dijkstra : Cet algorithme sert à résoudre le problème du plus court chemin. Il permet,
par exemple, de déterminer un plus court chemin pour se rendre d’une ville à une autre
connaissant le réseau routier d’une région.

Soit G : graphe orienté w : E(G) → R+ et s : source de G

Algorithme Dijkstra(G, w, s)

pour tout v de V (G) faire

d(v) ← ∞

pere(v) ← NIL

couleur(v) ← BLANC

fin pour

d(s) ← 0

F ← FILE PRIORITE({s}, d)

tant que F 6= ∅ faire

pivot ← EXTRAIRE MIN(F)

pour tout e = (pivot, v) arc sortant de pivot faire

si couleur(v) = BLANC alors

70
Unité 3. Structure de données dynamiques

si d(v) = ∞ alors

INSERER(F, v)

fin si

si d[v] > d[pivot] + w(e) alors

d[v] ← d[pivot] + w(e)

pere[v] ← pivot

fin si

fin si

fin pour

couleur[pivot] ← NOIR

fin tant que

Bellman : Cet algorithme calcule les plus courts chemins depuis un sommet source donné dans
un graphe orienté pondéré.

Notons :

G : graphe orienté sans circuit

w : E(G) → R

s : source de G

Algorithme : Bellman-court(G;w; s)

TriTopologique(G)

Soit v1, . . . , vn l’ordre topologique calculé à l’étape 1

pour i de 1 à n − 1 faire

d[vi ] ← ∞

pere[vi ] ← NIL

fin pour

d[v1] ← 0

pour i de 1 à n − 1 faire

pour u ∈ Adj(vi) faire

si d[u] > d[vi ] + w(vi , u) alors

d[u] ← d[vi ] + w[vi , u]

pere[u] ← vi

71
Conception et Analyse d’algorithmes

fin si

fin pour

fin pour

Algorithme de Ford est un algorithme qui calcule des plus courts chemins depuis un sommet
source donné dans un graphe orienté pondéré.

G : graphe orienté

w : E(G) → R

s : source de G

L’algorithme renvoie vrai si le graphe G est sans circuit négatif.

Algorithme Ford(G, w, s)

pour tout v de V (G) faire

d[v] ← ∞

pere[v] ← NIL

fin pour

d[s] ← 0

pour i de 1 à n − 1 faire

pour tout arc e = (u, v) ∈ E(G) faire

si d[v] > d[u] + w(u, v) alors

d[v] ← d[u] + w[u, v]

pere[v] ← u]

fin si

fin pour

fin pour

pour tout arc e = (u, v) ∈ E(G) faire

si d[v] > d[u] + w(u, v) alors

retourner FAUX

fin si

fin pour

retourner VRAI

On souhaite par exemple trouver le trafic maximal entre deux villes d’un réseau routier dont on
connait la capacité (le nombre de voiture par heure sur chaque tronçon).

72
Unité 3. Structure de données dynamiques

Comment trouver un flot dont la valeur est maximale pour un graphe valué qui a une seule
source et un seul puits ?

Flots

Algorithme de Ford et Fulkerson : On souhaite trouver le flot maximum du réseau représenté

Figure 3.3.1 : Réseau

Soit G : graphe orienté

c : E(G) → R+

s : source de G

t : puits de G

On considère un arc (t, s) de capacité c(t, s) infinie.

La valeur du flot sur cet arc sera la valeur du flot de s à t. On note respectivement par I(e) et
T(e) l’extrémité initiale et l’extrémité terminale d’un arc e.

Algorithme FlotMax(G, c, s, t)

pour tout e de E(G) faire

f[e] ← 0

fin pour

répéter

Marquage(G, c, f, s, t)

si t ∈ Y alors

v←t

C+ ← {(t, s)}

C− ← ∅ 10: tant que v différent de s faire

e ← A[v]

73
Conception et Analyse d’algorithmes

si v = T[e] alors

C + ← C + ∪ {e}

v ← I[e]

sinon

C− ← C− ∪ {e}

v ← T[e]

fin si

fin tant que

fin si

pour tout e ∈ C+ faire

f(e) ← f(e) + δ[t]

fin pour

pour tout e ∈ C− faire

f(e) ← f(e) − δ[t]

fin pour

jusqu’ à t ∈

Conclusion

On peut conclure que la difficulté principale de l’algorithme de Kruskal est une implémentation
efficace des sous-ensembles de l’ensemble des sommets. Plus précisément, il faut pouvoir
trouver le sous-ensemble auquel appartient un sommet donné ;

décider si deux sous-ensembles sont identiques ou différents ;

faire une union de deux sous-ensembles.

Le temps d’exécution est très dépendant de la façon dont cette implémentation est réalisée.

74
Unité 3. Structure de données dynamiques

Évaluation
On considère le graphe orienté ci-dessous valué par des longueurs d’arcs. On cherche à
déterminer les plus courts chemins de à tout autre sommet. Dire pourquoi on peut utiliser
pour cela l’algorithme de Bellman.

Correction
On peut appliquer l’algorithme de Bellman parce que le graphe est sans circuit, ce qu’on peut
vérifier en effectuant la numérotation topologique qui a été faite ci-dessous. Les numéros
topologiques sont encadrés.

Après avoir posé

, on calcule les distances par numéros topologiques croissants en appliquant la formule :

distance(y)= 〖min〗_(prédecesseurs de y ) distance(x)+ distance(y))

Les distances sont indiquées en gras à côté des sommets et les plus courts chemins sont
indiqués par les arcs en gras.

75
Conception et Analyse d’algorithmes

Résumé de l’unité
Les principales structures de données avancées ont été présentées (arbres, graphes) en
précisant à chaque fois leurs mémorisations, les algorithmes de parcours, de création et de
désallocation, plus des algorithmes spécifiques à chaque structure de données.

Évaluation de l’unité
Test

Directives
Les barèmes sont fixés devant chaque question

Considérons l’arbre suivant :

76
Unité 3. Structure de données dynamiques

1. (2points) Dessiner cet arbre.

2. (2.5 points) Quelle est la hauteur de l’arbre ?

3. (3 points) Est-ce que cet arbre est un arbre entier, un arbre parfait
(=complet), et/ou un arbre dégénéré ?

4. (2.5 points) Afficher cet arbre binaire de la manière préfix, puis infix, et
ensuite postfix. Premièrement, on va afficher cet arbre sans utiliser des
parenthèses.

Correction

1.

2. La hauteur de l’arbre est 3.

3. Cet arbre est entier car chaque nœud a zéro ou deux fils. Par contre, cet
arbre est ni parfait ni dégénéré.

4. Premièrement, on va afficher cet arbre sans utiliser des parenthèses.

Préfix * + 3 / 4 2 - 8 * 2 3

Infix 3 + 4 / 2 * 8 - 2 * 3

Postfix 3 4 2 / + 8 2 3 * - *

Deuxièmement, on va afficher cet arbre en utilisant des parenthèses.


Notez que les parenthèses sont importantes uniquement pour la
notation infix.

Préfix *( +( 3, /( 4, 2) ) , -( 8, *( 2, 3) ) )

Infix ( ( 3+ ( 4/2 ) ) * ( 8- ( 2*3 ) ) )

Postfix ( ( 3, ( 4, 2)/)+, ( 8, ( 2, 3)*)-)*

77
Conception et Analyse d’algorithmes

Lectures et autres ressources

• Jacques Guyot, Christian Vial. Arbres, tables et algorithmes. 1998.


• Philippe Lacomme, Christian Prins, Marc Sevaux. Algorithmes de graphes. 2003.

78
Unité 4. Structures de données spécialisées

Unité 4. Structures de données


spécialisées
Introduction à l’unité
Dans cette unité, vous allez découvrir les structures de données avancées telles que les arbres
binaires, les ensembles disjoints et étudier l’implantation des ensembles disjoints et des 
composantes connexes.

Objectifs de l’unité
À la fin de cette unité, vous devriez être capable de:

• Comprendre les structures de données avancées telles que les arbres binaires,
les ensembles disjoints
• Implantation des ensembles disjoints,  composantes connexes.

Termes clés
File de priorité : Une file de priorité est un objet
contenant des éléments ayant chacun une priorité
représentée par un nombre entier positif.

Ensembles disjoints : Une structure de données


d’ensembles disjoints gère une collection d’ensembles
dynamiques disjoints. Chaque ensemble est identifié
par un représentant, qui est un certain membre de
l’ensemble

Arbres binaires de recherche : sont des structures


de données qui peuvent supporter des opérations
courantes sur des ensembles dynamiques.

79
Conception et Analyse d’algorithmes

Activités d’apprentissage

Activité 1: Files de priorité

Introduction 
Le tri par tas est un très bon algorithme, mais une bonne implémentation du tri rapide est
généralement plus performante en pratique. Néanmoins, la structure de données tas offre
intrinsèquement de gros avantages. Dans cette activité, On va présenter l’une des applications
les plus répandues du tas, à savoir la gestion d’une file de priorité efficace. Comme c’est le cas
avec les tas, il existe deux sortes de files de priorité : les files max et les files min.

Parmi les Exemples d’utilisation, on peut citer :

• Ordonnancement des tâches d’un système d’exploitation.


• Application boursière (d’après Rolf Ingold)
• Contrôle aérien
• Techniques de simulation basées sur les événements
• Gestion de ressources dans un système d’exploitation

Définition

Une file de priorité est un type abstrait de données qui permet de gérer un ensemble S
d’éléments, dont chacun a une valeur associée baptisée clé.

Une file de priorité max est munie des opérations suivantes.

INSÉRER(S, x) insère l’élément x dans l’ensemble S. Cette opération pourrait s’écrire sous la
forme

MAXIMUM(S) retourne l’élément de S ayant la clé maximale.

EXTRAIRE-MAX(S) supprime et retourne l’élément de S qui a la clé maximale.

AUGMENTER-CLÉ(S, x, k) accroît la valeur de la clé de l’élément x pour lui donner la nouvelle


valeur k, qui est censée être supérieure ou égale à la valeur courante de la clé de x.

Une file de priorité min est munie des opérations INSÉRER, MINIMUM, EXTRAIREMIN et
DIMINUER-CLÉ.

Implémentation

Implémentons maintenant les opérations associées à une file de priorité max :

La procédure MAXIMUM-TAS implémente l’opération MAXIMUM en un temps

MAXIMUM-TAS(A)

retourner A[1]

80
Unité 4. Structures de données spécialisées

La procédure EXTRAIRE-MAX-TAS :

Le temps d’exécution de EXTRAIRE-MAX-TAS est O(lg n)

EXTRAIRE-MAX-TAS(A)

si taille[A] < 1

alors erreur « limite inférieure dépassée »

max ← A[1]

A[1] ← A[taille[A]]

taille[A] ← taille[A] − 1

ENTASSER-MAX(A, 1)

retourner max

La procédure AUGMENTER-CLÉ-TAS implémente l’opération AUGMENTERCLÉ. L’élément de


la file de priorité dont il faut accroître la clé est identifié par un indice i pointant vers le tableau.

La procédure commence par modifier la clé de l’élément A[i] pour lui donner sa nouvelle valeur.
Accroître la clé de A[i] risque d’enfreindre la propriété de tas max ; la procédure, d’une manière
qui rappelle la boucle d’insertion, parcourt donc un chemin reliant ce nœud à la racine, afin de
trouver une place idoine pour la clé qui vient d’être augmentée. Tout au long du parcours, elle
compare un élément à son parent ; elle permute les clés puis continue si la clé de l’élément est
plus grande, et elle s’arrête si la clé de l’élément est plus petite, vu que la propriété de tas max
est alors satisfaite.

AUGMENTER-CLE-TAS(A, i, cle)

si cle < A[i]

alors erreur nouvelle cle plus petite que cle actuelle â

A[i] <- cle

tant que i > 1 et A[PARENT(i)] < A[i]

faire permuter A[i] <-> A[PARENT(i)]

i <- PARENT(i)

La procédure INSÉRER-TAS-MAX implémente l’opération INSÉRER. Elle prend en entrée la clé


du nouvel élément à insérer dans le tas max A. La procédure commence par étendre le tas max
en ajoutant à l’arbre une nouvelle feuille dont la clé est −∞. Elle appelle ensuite AUGMENTER-
CLÉ-TAS pour affecter à la clé du nouveau nœud la bonne valeur et conserver la propriété de
tas max.

INSÉRER-TAS-MAX(A, clé)

taille[A] ← taille[A] + 1A[taille[A]]←−∞AUGMENTER-CLÉ-TAS(A, taille[A], clé)

Le temps d’exécution de INSÉRER-TAS-MAX sur un tas à n éléments est O(lg n).

81
Conception et Analyse d’algorithmes

Conclusion

On peut conclure qu’un tas permet de faire toutes les opérations de file de priorité sur un
ensemble de taille n en un temps O(lg n).

Évaluation
Files de priorités et tas

Nous allons étudier ici une nouvelle structure de données : les files de priorité, et nous verrons
qu’un certain type d’arbres permet de les représenter de manière extrêmement efficace.

Définition. Intuitivement, une file de priorité est un ensemble d’éléments à qui sont attribués
un rang de priorité avant qu’ils ne rentrent dans la file, et qui en sortiront précisément selon
leur rang : l’élément de rang le plus élevé sera ≪ servi ≫ en premier. En d’autres termes, il
s’agit d’une structure de données sur laquelle opèrent les trois opérations suivantes :

• une fonction d’insertion d’un élément dans la file (avec son rang de priorité),
• une fonction qui renvoie l’élément de rang le plus élevé,
• une fonction qui supprime d’élément de rang le plus élevé de la file.

On disposera de plus d’un objet vide correspondant : la file vide.

Parmi les différentes possibilités de codage des files de priorité, nous utiliserons la structure
dite de tas ou maximier (heap en Anglais). Les autres possibilités (file d’attente d’éléments
non triés, tableau trié) impliquent ou bien un calcul du maximum en temps linéaire, ou alors le
maintien d’un ordre entre tous les éléments qui n’est pas nécessaire. Une structure de tas est
un arbre satisfaisant les propriétés suivantes :

– c’est un arbre binaire complet, c’est-à-dire un arbre dont tous les niveaux de profondeurs
sont remplis, à l’exception du dernier, celui qui ne comporte que des feuilles, lesquelles sont
rangées ≪ le plus à gauche possible ≫

– la clef de tout nœud est supérieure ou égale à celles de ses descendants

Figure 4.1 – Un exemple de tas où l’on a numéroté les nœuds à partir de 0.

82
Unité 4. Structures de données spécialisées

Opérations sur les tas

Montrons maintenant comment employer un tas pour coder une file de priorité. Tout d’abord,
le codage de la fonction qui renvoie le maximum est très simple : il suffit de renvoyer la clef
contenue dans la racine (si le tas n’est pas vide, bien sûr). Cette opération peut donc s’exécuter
en temps constant.

En ce qui concerne la fonction d’insertion d’un élément, son principe est le suivant :

1. On commence par créer un nouveau nœud contenant la nouvelle clef et


on insère ce nœud à la première place disponible de ce tas, c’est-à-dire
le plus à gauche possible dans le niveau le plus profond (quitte à créer
un nouveau niveau si nécessaire). On obtient alors un arbre qui est certes
complet, mais qui peut ne plus être un tas. L’étape suivante vise donc à
rétablir l’ordre des nœuds.

2. On compare la nouvelle clef insérée à celle de son père, et on permute


ces deux clefs si nécessaire, on réitère ce processus avec la clef du père
et celle du grand-père, etc. remontant ainsi dans l’arbre jusqu’`a ce qu’une
permutation ne soit pas nécessaire ou que l’on soit arrivé à la racine. C’est
ce qui est illustré sur la figure 4.2.

Figure 4.2 – Insertion de l’élément 43 dans un tas.

3. Enfin, pour la suppression du maximum d’un tas non vide, on commence


par extraire la clef de la racine (le maximum), puis on la remplace par la
clef du nœud le plus à droite du niveau de profondeur maximale, et on
supprime ce nœud. Comme dans le cas de l’insertion, on obtient un arbre
binaire complet, mais qui peut ne plus être un tas. Il faut donc rétablir la
propriété de tas de l’arbre, en partant de la racine : on compare sa clef à
celles de ses deux fils, et la permute avec le maximum de ces deux clefs.
On itère ce processus sur le nœud où on a placé la clef, la faisant ainsi
descendre dans l’arbre jusqu’à ne plus avoir à permuter, ou jusqu’`a arriver
à une feuille. C’est ce qui est illustré sur la figure 4.3.

Figure 4.3 – Suppression de la racine d’un tas.

83
Conception et Analyse d’algorithmes

a. En notant h la hauteur du tas, exprimer la complexité des


opérations de recherche du maximum, d’insertion et de suppression
en fonction de h.

b. Exprimer h en fonction du nombre d’éléments n contenus dans le


tas. En déduire la complexité des trois opérations en fonction de n.

Correction

1. L’insertion et la suppression ont une complexité en Θ(h), la recherche du


maximum en Θ(1).

2. Nous avons h ≥ log2. Cependant, un tas est un arbre complet donc


nous avons toujours h = log2 (n), donc les opérations d’insertion et de
suppression ont une complexité Θ(log n).

Activité 2: Arbres binaires de recherche

Présentation
Comme son nom l’indique un Arbre Binaire de Recherche (ABR) est organisé comme un arbre
binaire. Il est étiqueté par des éléments d’un ensemble

totalement ordonné et tel que si

est l’étiquette d’un nœud ,

tous les nœuds du sous-arbre gauche de n sont étiquetés par des éléments b tels que b ≤ a

, ET

tous les nœuds du sous-arbre droit de n sont étiquetés par des éléments b tels que b ≤ a

Figure 4.2.1 : arbre binaire

84
Unité 4. Structures de données spécialisées

Figure 4.2.2 : arbre binaire de recherche

Évaluation
Exercice 1 :

1. Considérer l’ensemble des clés 1, 4, 5, 10, 16, 17, 21. 1. Dessiner des
arbres binaires de recherche de cet ensemble de clés avec une hauteur
de 2, puis 4, et ensuite 6.

Exercice 2 :

1. Etablir la structure de données p_t_noeud pour cet arbre binaire de


recherche contenant une clé (integer) et deux pointeurs, un pour le sous-
arbre gauche, et un pour le sous-arbre droite.

2. Ecrire une fonction function max(noeud : p_t _noeud ):integer qui calcule
le maximum de l’arbre de recherche.

3. Ecrire une fonction function min(noeud : p_t_noeud ):integer qui calcule


le minimum de l’arbre de recherche.

85
Conception et Analyse d’algorithmes

Correction
Exercice 1 :

Exercice 2 :

1. Etablir la structure de données p_t_noeud pour cet arbre binaire de


recherche contenant une clé (integer) et deux pointeurs, un pour le sous-
arbre gauche, et un pour le sous-arbre droite.

type p_t_noeud = ^t_noeud;

t_noeud = RECORD cle :

integer;

gauche : p_t_noeud;

droite : p_t_noeud;

END;

2. Ecrire une fonction function max(noeud : p_t_noeud ):integer qui calcule


le maximum de l’arbre de recherche.

function max(noeud : p_t_noeud) : integer;

86
Unité 4. Structures de données spécialisées

var temp : p_t_noeud;

var max : integer;

begin max := Low(Integer); { Le plus faible possible }

temp := noeud;

while NOT(noeud = NIL) do

begin if noeud^.cle > max then

max := noeud^.cle ;

noeud := noeud^.droite;

end;

result := max; end;

3. Ecrire une fonction function min(noeud : p_t_noeud ):integer qui calcule le


minimum de l’arbre de recherche.

function min(noeud : p_t_noeud) : integer;

var temp : p_t_noeud;

var min : integer;

begin min := High(Integer); { Le plus grand possible }

temp := noeud;

while NOT(noeud = NIL) do

begin

if noeud^.cle < min then

min := noeud^.cle ;

noeud := noeud^.droite;

end;

result := min;

end;

87
Conception et Analyse d’algorithmes

Activité 3: Ensembles disjoints

Introduction
Certaines applications imposent de regrouper

éléments distincts dans une collection d’ensembles disjoints. Pour cela il est nécessaire de
savoir à quel ensemble appartient un élément donné et de pouvoir réunir deux ensembles.

Dans cette unité, on va étudier des méthodes permettant de gérer une structure de données
qui supporte ces opérations.

Opérations sur les ensembles disjoints

Une structure de données d’ensembles disjoints (UNION-FIND data structure) est une structure
de données qui maintient à jour une collection

d’ensemble dynamique disjoint. Chaque ensemble est identifié par un représentant, qui est un
certain membre de l’ensemble.

Une structure de données d’ensembles disjoints supporte les opérations suivantes.

CREER _ENSEMBLE(x) : une procédure qui permet de créer un nouvel ensemble dont le
seul membre (et donc le représentant) est x. Comme les ensembles sont disjoints, il faut que x
ne soit pas déjà membre d’un autre ensemble.

UNION(x; y) : réunit les ensembles dynamiques qui contiennent et y. x et y doivent


appartenir à des ensembles différents avant l’union.

Find(D, x) : cette procédure retourne un pointeur sur le représentant de l’ensemble (unique)


contenant x. Chaque appel doit renvoyer le même représentant tant que la structure n’est pas
modifiée.

Application

L’une des nombreuses applications des structures de données d’ensembles disjoints apparaît
lorsqu’il s’agit de déterminer les composantes connexes d’un graphe non orienté.

Algorithme : Calcul des composantes connexes d’un graphe :

Connected-components(G)

D = ”Empty disjoint set”

for each vertex

Make-Set(D, v)

for each

if Find-Set(D, u)

Find-Set(D, v)

Union(D, u, v)

88
Unité 4. Structures de données spécialisées

Same-component(u, v)

if Find-Set(D, u) == Find-Set(D, v)

return true

else return False

La procédure Connected-components utilise les opérations d’ensembles disjoints pour calculer


les composantes connexes d’un graphe.

Figure 4.3.1 : graphe à quatre composantes connexes

Représentation des ensembles disjoints par listes chaînées

Une manière simple d’implémenter une structure de données d’ensembles disjoints utilise des
listes chaînées:

• Chaque ensemble est représenté par une liste chaînée.


• Le premier objet de chaque liste chaînée sert de représentant.
• Chaque objet de la liste chaînée contient un élément de l’ensemble, un pointeur
sur l’objet contenant l’élément suivant, et un pointeur sur le représentant de
l’ensemble.

Analyse du temps

Avec cette représentation en liste chaînée, CREER _ENSEMBLE(x) , et Find(D, x) sont facile à
implémenter, et consomment au pire des cas. L’implémentation la plus simple de l’opération
UNION(x; y), exécutée par concaténation de la liste de x à la fin de la liste de y, consomme
beaucoup plus de temps, même θ(n) au pire des cas.

n de la liste de y, consomme beaucoup plus de temps, même (n) au pire des cas.

Conclusion

Nous avons vu dans cette unité :

• Un exemple d’application de l’ensemble disjoint pour calculer les composantes


connexes d’un graphe ;
• La représentation des ensembles disjoints par listes chaînées

89
Conception et Analyse d’algorithmes

Résumé de l’unité
Cette unité vous a permis d’étudier les structures de données spécialisées, de voir un exemple
d’application de l’ensemble disjoint pour calculer les composantes connexes d’un graphe et la
représentation des ensembles disjoints en utilisant les listes chaînées.

Évaluation de l’unité
Test

Directives
Les barèmes sont fixés devant chaque question

1. Etablir la structure de données p_t_noeud pour cet arbre binaire de


recherche contenant une clé (integer) et deux pointeurs, un pour le sous-
arbre gauche, et un pour le sous-arbre droite.

2. Ecrire une fonction function max(noeud : p_t _noeud ):integer qui calcule
le maximum de l’arbre de recherche.

3. Ecrire une fonction function min(noeud : p_t_noeud ):integer qui calcule


le minimum de l’arbre de recherche.

Réponses

1. Etablir la structure de données p_t_noeud pour cet arbre binaire de


recherche contenant une clé (integer) et deux pointeurs, un pour le sous-
arbre gauche, et un pour le sous-arbre droite.

type p_t_noeud = ^t_noeud;

t_noeud = RECORD cle :

integer;

gauche : p_t_noeud;

droite : p_t_noeud;

END;

2. Ecrire une fonction function max(noeud : p_t_noeud ):integer qui calcule


le maximum de l’arbre de recherche.

function max(noeud : p_t_noeud) : integer;

var temp : p_t_noeud;

var max : integer;

begin max := Low(Integer); { Le plus faible possible }

90
Unité 4. Structures de données spécialisées

temp := noeud;

while NOT(noeud = NIL) do

begin if noeud^.cle > max then

max := noeud^.cle ;

noeud := noeud^.droite;

end;

result := max; end;

3. Ecrire une fonction function min(noeud : p_t_noeud ):integer qui calcule le


minimum de l’arbre de recherche.

function min(noeud : p_t_noeud) : integer;

var temp : p_t_noeud;

var min : integer;

begin min := High(Integer); { Le plus grand possible }

temp := noeud;

while NOT(noeud = NIL) do

begin

if noeud^.cle < min then

min := noeud^.cle ;

noeud := noeud^.droite;

end;

result := min;

end;

Lectures et autres ressources

• Jacques Guyot, Christian Vial. Arbres, tables et algorithmes. 1998.


• Philippe Lacomme, Christian Prins, Marc Sevaux. Algorithmes de graphes. 2003.

91
Conception et Analyse d’algorithmes

Résumé de l’unité
Ce module vous a permis d’avoir quelques-unes des techniques avancées de conception et
d’analyse d’algorithmes. Ainsi, tous les grands thèmes de l’algorithme ont été abordés dans
le cours : récursivité, Complexité temporelle et spatiale d’un algorithme, programmation
linéaire, NP-complétude, programmation dynamique, algorithmes probabilistes, algorithmes
d’approximation, etc.

Ce cours vous a permis d’analyser la complexité temporelle et spatiale d’un algorithme,


d’identifier les méthodes les plus appropriées pour la résolution des problèmes algorithmiques,

de connaître les principes de la conception des algorithmes et la programmation dynamique.

92
Unité 4. Structures de données spécialisées

Évaluation du cours
Contrôle continu Test1

Directives

Test

Système de notation

Les barèmes sont fixés devant chaque question

Exercice 1 : Vrai ou Faux ? (4.5 points)

1. (1.5 point) Un algorithme de complexité O(nlogn) dans le cas moyen est


toujours plus rapide qu’un algorithme dont la complexité est O(n2 ) dans
le cas moyen.

2. (1.5 point) Pour tout graphe connexe, il existe un et un seul arbre de


recouvrement minimum.

3. (1.5 point) Les algorithmes de parcours de graphes branch-and-bound


trouvent toujours la solution optimale.

Exercice 2 : (4.5 points)

1. (1.5 point) Donnez la différence entre les notations O() et Ω() sans donner
leur définition formelle.

2. (1.5 point) Les algorithmes diviser-pour-régner et de programmation


dynamique sont tous deux basés sur une relation de récurrence.
Expliquez cependant la différence fondamentale entre les deux
techniques de conception.

3. (1.5 point) Expliquez l‘algorithme vorace ?

Exercice 3 : (11 points)

Considérer le graphe G2 suivant :

93
Conception et Analyse d’algorithmes

1. (6points) Faites tourner l’Algorithme de Dijkstra sur le Graphe G2 avec s


= s1 dans le tableau suivant (Copier le tableau sur vos feuilles d’examen).

2. Dessiner l’arbre recouvrant à partir de vos résultats.

Correction
Exercice 1 :

1. Faux

2. Faux

3. Vrai

Exercice 2 :

exprime une borne supérieure sur la consommation de ressources, alors que Ω()

1. Exprime une borne inférieure sur la consommation de ressources.

2. Diviser-pour-régner procède de haut en bas, alors que la programmation


dynamique procède de bas en haut.

3. Un algorithme vorace (glouton, en anglais greedy) est une procédure


algorithmique qui construit d’une manière incrémentale une solution.
Cet algorithme fait toujours le choix qui lui semble le meilleur sur le
moment. Autrement dit, il fait le choix localement optimal, dans l’espoir
que ce choix conduira à une solution globalement optimale.

Exercice 3 :

1.

2.

94
Unité 4. Structures de données spécialisées

Contrôle continu Test 2


Directives

Test

Système de notation

Les barèmes sont fixés devant chaque question

Exercice 1 (4 points)

1. Donnez une implémentation détaillée de l’opération delete(T : tas, i :


entier) dans un tas-min binaire implémenté dans le type tas présenté, qui
retourne un tas-min dans lequel l’élément situé à l’indice i dans le vecteur
a été supprimé. Attention aux effets de bord au niveau des feuilles et au
problème des nœuds avec un seul fils !

Exercice 2 (4 points)

1. Écrire l›algorithme qui recherche un élément dans un vecteur de


taille n. Calculer la complexité temporelle en fonction du nombre de
comparaisons dans le pire et dans le meilleur des cas. Refaire les calculs
en fonction du nombre d›accès au vecteur.

Exercice 3 (12 points)

Considérer le graphe G2 suivant :

1. (6 points) Dessiner l’arbre minimal couvrant du Graphe G2 avec


l’Algorithme de Kruskal. Utiliser pour ceci le tableau suivant.

2. (5 points) Quelle est la somme des poids des arêtes de l’arbre minimal
couvrant T ?

95
Conception et Analyse d’algorithmes

Correction
Exercice 1

tas delete(T : tas, i : entier)

j, k : entier;

T.element[i] := T.element[T.taille]; \\

T.taille-- ;

j:=i; \\

tantque (2j+1 <= T.taille-1) faire

si (2j+2 <= T.taille-1) alors

T.element[2j+1] < T.element[2j+2]

k:=2*j+1

else k:=2*j+2 else k:=2*j+1;

Echanger(T.element, j, k); j:=k;

return(T);

fin delete

Exercice 2

On compte le nombre de comparaison avec les éléments de vecteurs. Dans le


meilleur de cas où on trouve l’élément à la position i, on effectue une comparaison.
Dans le pire des cas où on trouve l’élément à la fin du vecteur ou on le trouve pas,
on effectue n comparaisons. Une comparaison coûte un accès au vecteur.

96
Unité 4. Structures de données spécialisées

Références du cours

• Jacques Courtin, Irène Kowarski. Initiation à l’algorithmique et aux structures de


données.
• Thomas H. Cormen. Algorithme - 3ème édition - Cours avec 957 exercices et 158
problèmes. 2010.
• D.S. Malik. Structures de données à l’aide de C++, Seconde Edition, 2010.
• Mark A. Weiss . Data Structures and Algorithm Analysis in C++. 2013
• Stroustrup, B. (2014). Programming Principles and Practice Using C and C++.
Addison Wesley ISBN0321992784
• Balagurusamy, E. (2008). Programming in ANSI C. (4ª ed). New Delhi: Tata
Mc-Graw-Hill
• Pierre Tellier. ALGORITHMIQUE ET PROGRAMMATION EN C. Département
d’Informatique de l’Université Louis Pasteur
• SAMS Teach Yourself Data Structures And Algorithms In 24 hours. 1999
• V. Das, Principles of Data Structures and Algorithms using C and C++, 2008.
• Concise Notes on Data Structures and Algorithms, Ruby Edition. Christopher Fox.
2012
• Herbert S. Wilf. Algorithms and Complexity 2nd Edition
• Anany Levitin. Introduction to the Design and Analysis of Algorithms.
• Sara Baase, Allen Van Gelder. Computer Algorithms: Introduction to Design and
Analysis
• Philippe Lacomme, Christian Prins, Marc Sevaux. Algorithmes de graphes. 2013

97
Siège de l’Université Virtuelle Africaine

The African Virtual University


Headquarters

Cape Office Park

Ring Road Kilimani

PO Box 25405-00603

Nairobi, Kenya

Tel: +254 20 25283333

contact@avu.org

oer@avu.org

Bureau Régional de l’Université


Virtuelle Africaine à Dakar

Université Virtuelle Africaine

Bureau Régional de l’Afrique de l’Ouest

Sicap Liberté VI Extension

Villa No.8 VDN

B.P. 50609 Dakar, Sénégal

Tel: +221 338670324

bureauregional@avu.org

2017 UVA

Vous aimerez peut-être aussi