• Aucun résultat trouvé

Conception et Analyse d’algorithmes

N/A
N/A
Protected

Academic year: 2022

Partager "Conception et Analyse d’algorithmes"

Copied!
100
0
0

Texte intégral

(1)

CONCEPTION

ET ANALYSE D'ALGORITHMES

Bouna Mohamed

(2)

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

(3)

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

(4)

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.

(5)

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

(6)

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

(7)

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

(8)

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

(9)

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

(10)

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

(11)

• 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

(12)

É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 Etude d’exemples Estimation asymptotique

15H

Unité 2 Méthode Glouton

Méthode Diviser pour régner Programmation dynamique

45H

Unité 3 Arbres

Algorithmes des Graphes Plus court chemin, Flots

30H

Unité 4 Files de priorité

Arbres binaires de recherche Ensembles disjoints

25H

(13)

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

(14)

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

(15)

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.

(16)

É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.

(17)

É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;

}

(18)

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 ]);

} }

(19)

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 ;

(20)

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

(21)

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.

(22)

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,

(23)

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.

(24)

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).

(25)

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.

(26)

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

(27)

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

(28)

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 !}

(29)

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 :

(30)

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

(31)

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

(32)

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

(33)

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.

(34)

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;

(35)

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.

(36)

É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

(37)

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.

(38)

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

(39)

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 )

(40)

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.

(41)

É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

(42)

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.

(43)

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.

(44)

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

(45)

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)).

(46)

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.

(47)

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).

(48)

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.

(49)

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

(50)

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.

(51)

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.

(52)

É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

(53)

. 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.

(54)

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.

Références

Documents relatifs

L’opération estVide teste si la file avec priorité est vide ou pas, l’opération ajouter insère dans la file un nouvel élément avec sa priorité, l’opération premier

Écrivez maintenant la méthode de tri fusion, qui divise la liste en deux listes de taille égale, trie les sous-listes récursivement puis les fusionnent avec la méthode

Chercher sur Internet l‘algorithme DSW (c’est un algorithme qui prend en entrée un arbre de recherche binaire afin de réaliser son équilibrage) et l’algorithme de

[1] Après Amiens, les voûtes de Metz sont les plus hautes de France dans une cathédrale achevée. [2] Ce sont en effet les principaux éléments de l’architecture gothique, qui

Les pre- mières division d’un arbre sont utilisées pour construire une variable synthétique intégrée à une régression logistique afin de sélectionner les quelques

Le probl` eme de cet exercice consiste ` a d´ eterminer le nombre de points du plan contenus dans un rectangle donn´ e.. Ce genre de situation apparait tr` es souvent dans les

d- En supposant qu’il existe une fonction point/2 qui permette de dessiner un point de coordonn´ ees (x, y), d´ efinir une fonction qui dessine la repr´ esentation graphique

Dans le cas de points p-q-i alignés (orientation 0), le point le plus éloigné de P est conservé (dans le tableau donné en exemple ci-dessous, c'est la différence des distances PQ