104
Algorithmique et complexité Mr. Slim Mesfar Mail: [email protected] A.U. 2011-2012

Cours algorithmique et complexite complet

Embed Size (px)

Citation preview

Page 1: Cours algorithmique et complexite complet

Algorithmique et complexité

Mr. Slim Mesfar

Mail: [email protected]

A.U. 2011-2012

Page 2: Cours algorithmique et complexite complet

Plan du cours Chap-1: Introduction & motivations Chap-2: Complexité & optimalité Chap-3: Algorithmes de tri:

analyse et estimation de la complexité Chap-4: Récursivité

Différents types de récursivité Dérécursivation d’algorithmes Récursivité terminale et non terminale Paradigme « diviser pour régner »

Chap-5: Graphes et arbres Chap-6: Arbres binaires de recherche

Page 3: Cours algorithmique et complexite complet

Objectifs du cours Elaborer des algorithmes performants et efficaces Comprendre la notion de complexité d’un algorithme Maîtriser la récursivité (simple, multiple, mutuelle,

imbriquée) Savoir dérécursiver des algorithmes simples et multiples Maîtriser la démarche « diviser pour régner » Savoir estimer la complexité d’un algorithme itératif ou

récursif pouvant conduire à des récurrences linéaires d’ordre 1, d’ordre 2 et des récurrences de type « diviser régner »

Connaître les différents algorithmes de tri et estimer leur complexité

Elaborer des algorithmes à base de graphes et d’arbres Réaliser des algorithmes de parcours de graphes et d’arbres

Page 4: Cours algorithmique et complexite complet

Chapitre 1 – Introduction et motivations

Page 5: Cours algorithmique et complexite complet

Introduction Un algorithme = une suite ordonnée d'opérations

ou d'instruction écrites pour la résolution d'un problème donné.

Algorithme = une suite d’actions que devra effectuer un automate pour arriver à partir d’un état initial, en un temps fini, à un résultat

L’algorithmique désigne le processus de

recherche d’algorithme

Page 6: Cours algorithmique et complexite complet

Structures de données Une structure de données indique la manière

d'organisation des données dans la mémoire. Le choix d'une structure de données adéquate

dépend généralement du problème à résoudre. Deux types de structures de données :

Statiques : Les données peuvent être manipulées dans la mémoire dans un espace statique alloué dès le début de résolution du problème. Ex : les tableaux

Dynamiques : On peut allouer de la mémoire pour y stocker des données au fur et à mesure des besoins de la résolution du problème. Ex: liste chainée, pile, file, …

notion des pointeurs nécessité de la gestion des liens entre les données d'un problème en mémoire.

Page 7: Cours algorithmique et complexite complet

Qualités d’un bon algorithme Correct: Il faut que le programme exécute

correctement les tâches pour lesquelles il a été conçu

Complet: Il faut que le programme considère tous les cas possibles et donne un résultat dans chaque cas.

Efficace: Il faut que le programme exécute sa tâche avec efficacité c’est à dire avec un coût minimal. Le coût pour un ordinateur se mesure en termes de temps de calcul et d’espace mémoire nécessaire.

Page 8: Cours algorithmique et complexite complet

Exemple : Calcul de la valeur d’un polynôme Soit P(X) un polynôme de degré n P(X) = anXn + an-1Xn-1 + ... + a1X + a0

Où, n : entier naturel an, an-1, ..., a1, a0 : les coefficients du polynôme

1ère variante : début P=0 Pour i de 0 à n faire P = P+ ai*Xi

finpour fin

Coût de l’algorithme : - (n+1) additions - (n+1) multiplications - (n+1) puissances

Page 9: Cours algorithmique et complexite complet

Exemple : Calcul de la valeur d’un polynôme 2ème variante : debut

Inter=1 P =0 Pour i de 0 à N faire P = P+ Inter *ai

Inter = Inter * X finpour

Fin

Coût de l’algorithme : - (n+1) additions - 2(n+1) multiplications

Page 10: Cours algorithmique et complexite complet

Exemple : Calcul de la valeur d’un polynôme 3ème variante : Schéma de Horner P(x) = (….(((anx+an-1)x+an-2)x+an-3)…..)x+a0

début P = an

Pour i de n-1 à 0 (pas = –1) faire P = P*X + ai

finpour Fin Nécessité d’estimer le coût d’un algorithme avant de l’écrire

et l’implémenter

Coût de l’algorithme : - n additions - n multiplications

Page 11: Cours algorithmique et complexite complet

Chapitre 2 – Complexité et optimalité

Page 12: Cours algorithmique et complexite complet

Définitions la complexité d'un algorithme est la mesure du

nombre d'opérations fondamentales qu'il effectue sur un jeu de données.

La complexité est exprimée comme une fonction de la taille du jeu de données.

La complexité d'un algorithme est souvent déterminée à travers une description mathématique du comportement de cet algorithme.

Page 13: Cours algorithmique et complexite complet

Définitions On note Dn l’ensemble des données de taille n et T(d) le

coût de l’algorithme sur la donnée d. On définit 3 types de complexité :

Complexité au meilleur : C'est le plus petit nombre d'opérations qu'aura à exécuter l'algorithme sur un jeu de données de taille fixée, ici à n.

Complexité au pire : C'est le plus grand nombre d'opérations qu'aura à exécuter l'algorithme sur un jeu de données de taille fixée, ici à n.

Complexité en moyenne :

C'est la moyenne des complexités de l'algorithme sur des jeux de données de taille n.

Page 14: Cours algorithmique et complexite complet

Définitions C'est l'analyse pessimiste ou au pire qui est généralement

adoptée. En effet, de nombreux algorithmes fonctionnent la plupart

du temps dans la situation la plus mauvaise pour eux. l'analyse au pire des cas donne une limite supérieure de

la performance et elle garantit qu'un algorithme ne fera jamais moins bien que ce qu'on a établi.

Un algorithme est dit optimal si sa complexité est la complexité minimale parmi les algorithmes de sa classe.

Même si on s’intéresse quasi-exclusivement à la complexité en temps des algorithmes. Il est parfois intéressant de s’intéresser à d’autres ressources, comme la complexité en espace (taille de l’espace mémoire utilisé), la largeur de bande passante requise, etc.

Page 15: Cours algorithmique et complexite complet

Notations mathématiques La notation O est celle qui est le plus communément

utilisée pour expliquer formellement les performances d'un algorithme.

Cette notation exprime la limite supérieure d'une fonction dans un facteur constant.

La notation O reflète la courbe ou l'ordre croissance d'un

algorithme. Les règles de la notation O sont les suivantes :

Les termes constants : O(c) = O(1) Les constantes multiplicatives sont omises : O(cT ) = cO(T) = O(T) L'addition est réalisée en prenant le maximum : O(T1) + O(T2) =

O(T1 + T2) = max(O(T1);O(T2)) La multiplication reste inchangée mais est parfois réécrite d'une façon

plus compacte :O(T1)O(T2) = O(T1T2)

Page 16: Cours algorithmique et complexite complet

Exemple On suppose qu’on dispose d'un algorithme dont le temps

d'exécution est décrit par la fonction T(n) = 3n2+10n+10. L'utilisation des règles de la notation O nous permet de simplifier en :

O(T(n)) = O(3n2 + 10n + 10) = O(3n2) = O(n2) Pour n = 10 nous avons :

Temps d'exécution de 3n2 : 3(10)2 / 3(10)2+10(10)+10 = 73,2% Temps d'exécution de 10n : 10(10) / 3(10)2+10(10)+10 = 24,4% Temps d'exécution de 10 : 10 / 3(10)2+10(10)+10 = 2,4%

Le poids de 3n2 devient encore plus grand quand n = 100, soit 96,7% on peut négliger les quantités 10n et 10. Ceci explique les règles de la notation O.

Page 17: Cours algorithmique et complexite complet

Classes de complexité Les algorithmes usuels peuvent être classés en un certain

nombre de grandes classes de complexité. Les complexités les plus utilisées sont :

Constante : O(1) Accéder au premier élément d'un ensemble de données

Logarithmique : O(logn) Couper un ensemble de données en deux parties égales, puis couper ces moitiés en deux parties égales, etc.

Linéaire : O(n) Parcourir un ensemble de données Quasi-linéaire : O(nlogn) Couper répétitivement un ensemble de

données en deux et combiner les solutions partielles pour calculer la solution générale

Quadratique : O(n2) Parcourir un ensemble de données en utilisant deux boucles imbriquées

Polynomiale : O(nP) Parcourir un ensemble de données en utilisant P boucles imbriquées

Exponentielle : O(2n) Générer tous les sous-ensembles possibles d'un ensemble de données

Page 18: Cours algorithmique et complexite complet

Classes de complexité

Page 19: Cours algorithmique et complexite complet

Calcul de la complexité 1. Cas d'une instruction simple : écriture, lecture,

affectation Dans le cas d'uns suite d'instructions simples, on considère la

complexité maximale. La complexité de cette séquence vaut max(O(1),O(1))=O(1).

Page 20: Cours algorithmique et complexite complet

Calcul de la complexité 2. Cas d'un traitement conditionnel

3. Cas d'un traitement itératif : Boucle Tant Que

Page 21: Cours algorithmique et complexite complet

Calcul de la complexité 4. Cas d'un traitement itératif : Boucle Pour

Pour i de indDeb à indFin faire Traitement Fin Pour

∑=

indFin

indDebiTraitementTO )(

Page 22: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 1 : Tri par insertion

Principe : Cette méthode de tri s'apparente à celle utilisée pour trier ses cartes dans un jeu : on prend une carte, tab[1], puis la deuxième, tab[2], que l'on place en fonction de la première, ensuite la troisième tab[3] que l'on insère à sa place en fonction des deux premières et ainsi de suite. Le principe général est donc de considérer que les (i-1) premières cartes, tab[1],..., tab[i-1] sont triées et de placer la ie carte, tab[i], à sa place parmi les (i-1) déjà triées, et ce jusqu'à ce que i = N.

Page 23: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 1 : Tri par insertion

Procédure tri_Insertion (var tab : tableau entier [N]) i, k :entier ; tmp : entier ; Pour i de 2 à N faire tmp ← tab[i]; k ← i; Tant que k > 1 ET tab[k - 1] > tmp faire tab[k] ← tab[k - 1]; k ← k - 1; Fin Tant que tab[k] ← tmp; Fin pour

Fin

Page 24: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 1 : Tri par insertion Calcul de la complexité:

la taille du tableau à trier est n. On a deux boucles imbriquées :

La première indique l'élément suivant à insérer dans la partie triée du tableau.

Elle effectuera n - 1 itérations puisque le premier élément est déjà trié.

Pour chaque élément donné par la première boucle, on fait un parcourt dans la partie triée pour déterminer son emplacement.

Page 25: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 1 : Tri par insertion Calcul de la complexité:

Au meilleur des cas : le cas le plus favorable pour cet algorithme est quand le tableau est déjà trié (de taille n) O(n)

Au pire des cas : Le cas le plus défavorable pour cet algorithme est quand le tableau est inversement trié on fera une itération pour le 1er élément, deux itérations pour le 2ème et ainsi de suite pour les autres éléments. Soit 1+2+3+4+…+(n-1) =

sa complexité O(n2) nnn

−+

2)1(

Page 26: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 1 : Tri par insertion Calcul de la complexité:

En moyenne des cas : En moyenne, la moitié des éléments du tableau sont triés, et sur l’autre moitié ils sont inversement triés.

O(n2)

Page 27: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 2 : Recherche dichotomique Fonction RechDicho(Tab :Tableau, borneinf :entier, bornesup :entier, elemcherche :entier) : entier

Trouve = false ; Tant que ((non trouve) ET (borneinf<=bornesup)) faire mil = (borneinf+bornesup) DIV 2 ; Si (Tab[mil]=elemcherche) Alors trouve=true ; Sinon Si (elemcherche < Tab[mil]) Alors bornesup = mil-1 ; Sinon borneinf = mil+1 ; Fin Si Fin Si Fin Tant que Si (trouve) Alors Retourner (mil) ; Sinon Retourner (-1) ; Fin Si

Fin

Page 28: Cours algorithmique et complexite complet

Exemples de calcul de la complexité Exemple 2 : Recherche dichotomique Cette fonction effectue une recherche dichotomique d'un

élément dans un tableau trié. Supposons que le tableau est de taille n une puissance de 2 (n = 2q).

Le pire des cas pour la recherche d'un élément est de continuer les divisions jusqu'à obtenir un tableau de taille 1.

q le nombre d'itérations nécessaires pour aboutir à un tableau de taille 1

dernière itération taille tableau = 1

La complexité = log2(n)

Page 29: Cours algorithmique et complexite complet

Chapitre 3 – Les algorithmes de Tri

Page 30: Cours algorithmique et complexite complet

Tri par sélection Principe : Le principe est que pour classer n valeurs,

il faut rechercher la plus petite valeur (resp. la plus grande) et la placer au début du tableau (resp. à la fin du tableau), puis la plus petite (resp. plus grande) valeur dans les valeurs restantes et la placer à la deuxième position (resp. en avant dernière position) et ainsi de suite...

Page 31: Cours algorithmique et complexite complet

Tri par sélection Algorithme : i, j: entier ; tmp, small : entier ; t : tableau entier [n] ; Début Pour i de 1 à n-1 faire small←i; Pour j de i+1 à n faire Si t[j] < t[small] alors small ←j ; Fin si Fin pour tmp←t[small]; t[small] ←t[i]; t[i] ← tmp; Fin pour Fin

T(n) = O(n²)

Page 32: Cours algorithmique et complexite complet

Tri par propagation / à bulles Principe : Il consiste à parcourir le tableau tab en permutant

toute paire d'éléments consécutifs (tab[k],tab[k+1]) non ordonnés - ce qui est un échange et nécessite donc encore une variable intermédiaire de type entier. Après le premier parcours, le plus grand élément se retrouve dans la dernière case du tableau, en tab[N], et il reste donc à appliquer la même procédure sur le tableau composé des éléments tab[1], ..., tab[N-1].

Page 33: Cours algorithmique et complexite complet

Tri par propagation / à bulles Algorithme : Procédure tri_Bulle (tab : tableau entier [N] ) i,

k :entier ;tmp : entier ; Pour i de N à 2 faire Pour k de 1 à i-1 faire Si (tab[k] > tab[k+1]) alors

tmp ← tab[k]; tab[k] ← tab[k+1]; tab[k+1] ← tmp;

Fin si Fin pour Fin pour Fin

T(n) = O(n²)

Page 34: Cours algorithmique et complexite complet

Chapitre 4 – La récursivité

Page 35: Cours algorithmique et complexite complet

Définitions Un algorithme est dit récursif s'il est défini en

fonction de lui-même. La récursion est un principe puissant permettant de

définir une entité à l'aide d'une partie de celle-ci. Chaque appel successif travaille sur un ensemble

d'entrées toujours plus affinée, en se rapprochant de plus en plus de la solution d'un problème.

Page 36: Cours algorithmique et complexite complet

Evolution d’un appel récursif L'exécution d'un appel récursif passe par deux

phases, la phase de descente et la phase de la remontée : Dans la phase de descente, chaque appel récursif fait à

son tour un appel récursif. Cette phase se termine lorsque l'un des appels atteint une condition terminale. condition pour laquelle la fonction doit retourner une valeur au lieu de faire un autre appel récursif.

Ensuite, on commence la phase de la remontée. Cette phase se poursuit jusqu'à ce que l'appel initial soit terminé, ce qui termine le processus récursif.

Page 37: Cours algorithmique et complexite complet

Les types de récursivité 1/ La récursivité simple :

récursivité simple la fonction contient un seul appel récursif dans son corps.

Exemple : la fonction factorielle

Page 38: Cours algorithmique et complexite complet

Les types de récursivité Trace d’exécution de la fonction factorielle (calcul de la

valeur de 4!)

Page 39: Cours algorithmique et complexite complet

Les types de récursivité 2/ La récursivité multiple:

récursivité multiple la fonction contient plus d'un appel récursif dans son corps

Exemple : le calcul du nombre de combinaisons en se servant de la relation de Pascal :

Page 40: Cours algorithmique et complexite complet

Les types de récursivité 3/ La récursivité mutuelle:

Des fonctions sont dites mutuellement récursives si elles dépendent les unes des autres

Par exemple la définition de la parité d'un entier peut être écrite de la manière suivante :

Page 41: Cours algorithmique et complexite complet

Les types de récursivité 4/ La récursivité imbriquée:

Exemple : La fonction d'Ackermann

Page 42: Cours algorithmique et complexite complet

Récursivité terminale vs. non terminale Une fonction récursive est dite récursive

terminale si tous ses appels sont récursifs terminaux.

Un appel récursif est terminal s'il s'agit de la dernière instruction exécutée dans le corps d'une fonction et que sa valeur de retour ne fait pas partie d'une expression.

Les fonctions récursives terminales sont caractérisées par le fait qu'elles n'ont rien à faire pendant la phase de remontée

Page 43: Cours algorithmique et complexite complet

Importance de l’ordre des appels récursifs

Proc. terminale Procédure AfficherGaucheDroite (

Tab : Tableau entier, N : entier, i : entier)

Si (i<=N) Alors Ecrire(Tab[i]) ;

AfficherGaucheDroite (Tab,N,i+1) ; Fin Si Fin

Proc. non terminale Procédure AfficherDroiteGauche (

Tab : Tableau entier, N : entier, i : entier)

Si (i<=N) Alors AfficherDroiteGauche (Tab,N,i+1) ; Ecrire(Tab[i]) ; Fin Si Fin

l’ordre des appels récursifs affecte la terminaison d’une fonction récursive

Page 44: Cours algorithmique et complexite complet

Importance de l’ordre des appels récursifs

Page 45: Cours algorithmique et complexite complet

Elimination de la récursivité Dérécursiver, c’est transformer un

algorithme récursif en un algorithme équivalent ne contenant pas des appels récursifs.

Elimination de la récursivité terminale simple Rappel : Un algorithme est dit récursif terminal

s’il ne contient aucun traitement après un appel récursif.

La récursivité terminale simple peut être remplacée par une solution itérative.

Page 46: Cours algorithmique et complexite complet

Elimination de la récursivité terminale simple

Algo. récursif Procédure ALGOR(X) Si (COND) Alors TRAIT1 ALGOR(β (X)) Sinon TRAIT2 Fin Si Fin

Algo. itératif Procédure ALGOI(X) Tant que (COND) faire TRAIT1 X β (X) Fin tant que TRAIT2 Fin

•X est la liste des paramètres ; • COND est une condition portant sur X ; • TRAIT1 est le traitement de base de l'algorithme (dépendant de X) ; • β(X) représente la transformation des paramètres ; • TRAIT2 est le traitement de terminaison (dépendant de X).

Page 47: Cours algorithmique et complexite complet

Application : a est diviseur de b ? Algo. récursif

Fonction Diviseur (a,b) : Bool Si (a <=0) Alors

Retourner(Faux) Sinon Si (a>=b) Retourner (a=b) Sinon Retourner (Diviseur (a,b-a)) Fin Si Fin Si Fin

Algo. itératif Fonction Diviseur (a,b) : Bool Si (a <=0) Alors

Retourner(Faux) Sinon Tant que (a<b) Faire b b-a Fin tant que Retourner (a=b) Fin Si Fin

Page 48: Cours algorithmique et complexite complet

Elimination de la récursivité non terminale simple Dans un algorithme récursif non terminal, l’appel

récursif est suivi d’un traitement il reste un traitement à reprendre après l’appel récursif

Il va falloir donc sauvegarder, sur une pile, le contexte de l’appel récursif, typiquement les paramètres de l’appel engendrant l’appel récursif.

La récursivité non terminale simple peut être remplacée par une solution itérative utilisant une pile.

Page 49: Cours algorithmique et complexite complet

Elimination de la récursivité non terminale simple

Algo. récursif Procédure ALGOR(X) Si (COND) Alors TRAIT1 ALGOR(β (X)) TRAIT2 Sinon TRAIT3 Fin Si Fin

Algo. itératif Procédure ALGOI(X) Pile.init() Tant que (COND) Faire TRAIT1 Pile.empiler(X) X β (X) Fin Tant que TRAIT3 Tant que (Non_vide_pile())

Faire Pile.dépiler(U) TRAIT2 Fin Tant que Fin

Page 50: Cours algorithmique et complexite complet

Elimination de la récursivité non terminale simple

Algo. récursif Procédure

AfficherDroiteGauche ( Tab : Tableau entier, N : entier, i : entier)

Si (i<=N) Alors AfficherDroiteGauche

(Tab,N,i+1) ; Ecrire(Tab[i]) ; Fin Si Fin TRAIT1 = Ø TRAIT2 = Ecrire(Tab[i]) TRAIT3 = Ø

Algo. itératif Procédure ALGOI(X) Pile.init() Tant que (i<=N) Faire /* TRAIT1 */ Pile.empiler(Tab[i]) i i+1 Fin Tant que /* TRAIT3 */ Tant que (Non_vide_pile())

Faire Ecrire(Pile.dépiler() ) /* TRAIT2 */ Fin Tant que Fin

Page 51: Cours algorithmique et complexite complet

Exemple : Tours de Hanoi Problème : Le jeu est constitué d’une plaquette de bois où

sont plantées trois tiges numérotées 1, 2 et 3. Sur ces tiges sont empilés des disques de diamètres tous différents. Les seules règles du jeu sont que l’on ne peut déplacer qu’un seul disque à la fois, et qu’il est interdit de poser un disque sur un disque plus petit. Au début, tous les disques sont sur la tige 1 (celle de gauche), et à la fin ils doivent être sur celle de droite.

Page 52: Cours algorithmique et complexite complet

Exemple : Tours de Hanoi Résolution: On suppose que l’on sait

résoudre le problème pour (n-1) disques. Pour déplacer n disques de la tige 1 vers la tige 3, on déplace les (n-1) plus petits disques de la tige 1 vers la tige 2, puis on déplace le plus gros disque de la tige 1 vers la tige 3, puis on déplace les (n-1) plus petits disques de la tige 2 vers la tige 3.

Page 53: Cours algorithmique et complexite complet

Exemple : Tours de Hanoi Algorithme Procédure Hanoi (n, départ, intermédiaire, destination) Si n > 0 Alors Hanoi (n-1, départ, destination, intermédiaire) déplacer un disque de départ vers destination Hanoi (n-1, intermédiaire, départ, destination) Fin Si Fin

Page 54: Cours algorithmique et complexite complet

Exemple : Tours de Hanoi Trace d’exécution pour n=3 L’appel à Hanoi(3,1,2,3) entraîne l’affichage de : 1. Déplace un disque de la tige 1 vers la tige 3 2. Déplace un disque de la tige 1 vers la tige 2 3. Déplace un disque de la tige 3 vers la tige 2 4. Déplace un disque de la tige 1 vers la tige 3 5. Déplace un disque de la tige 2 vers la tige 1 6. Déplace un disque de la tige 2 vers la tige 3 7. Déplace un disque de la tige 1 vers la tige 3

Page 55: Cours algorithmique et complexite complet

Exemple : Tours de Hanoi Calcul de la complexité : On compte le nombre de déplacements de

disques effectués par l’algorithme Hanoi invoqué sur n disques.

On trouve : T(n) = T(n-1) + 1 + T(n-1) T(n) = 2T(n-1) + 1 T(n) = 2n – 1 Complexité exponentielle

Page 56: Cours algorithmique et complexite complet

Paradigme « diviser pour régner » De nombreux algorithmes ont une structure récursive: pour

résoudre un problème donné, ils s’appellent eux-mêmes récursivement une ou plusieurs fois sur des problèmes très similaires, mais de tailles moindres, résolvent les sous problèmes de manière récursive puis combinent les résultats pour trouver une solution au problème initial.

Le paradigme « diviser pour régner » parcourt trois étapes à chaque appel récursif à savoir : Diviser : le problème en un certain nombre de sous-

problèmes de taille moindre ; Régner : sur les sous-problèmes en les résolvant d'une façon

récursive ou le résoudre directement si la taille d'un sous-problème est assez réduite ;

Combiner : les solutions des sous-problèmes en une solution globale pour le problème initial.

Page 57: Cours algorithmique et complexite complet

Exemple 1 Recherche de l’indice du maximum dans un tableau d’entiers Fonction maximum ( Tab , indDeb, indFin) Si ( indDeb = indFin) alors retourner (indDeb) Sinon m=(indDeb+indFin)/2 // division du problème en 2 sous-problèmes

k1 = maximum (Tab, indDeb, m ) // régner sur le 1er sous-problème

k2 = maximum (Tab, m+1, indFin)// régner sur le 2ème sous-problème

Si(Tab[k1] > Tab[k2]) Alors // combiner les solutions

retourner (k1) Sinon retourner (k2) FinSi FinSi Fin

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

Page 58: Cours algorithmique et complexite complet

Exemple2 : multiplication de matrices carrées Dans cet exemple, on se propose de multiplier 2

matrices carrées A et B de taille n * n chacune, où n est une puissance exacte de 2. C est la matrice résultante.

Si on décompose les matrices A, B et C en sous-matrices de taille n/2 * n/2, l'équation C = AB peut alors se réécrire :

Le développement de cette équation donne : r = ae+bf ; s = ag+bh; t = ce+df et u = cg+dh

Page 59: Cours algorithmique et complexite complet

Exemple2 : multiplication de matrices carrées Chacune de ces quatre opérations correspond à :

deux multiplications de matrices carrées de taille n/2 2T(n/2)

et une addition de telles matrices n2/4

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

T(n) = 8T(n/2)+O(n2)

Page 60: Cours algorithmique et complexite complet

Analyse des algorithmes « Diviser pour Régner » On peut souvent donner une relation de récurrence qui décrit

le temps d'exécution d’un algorithme « diviser pour régner » en fonction des temps d'exécution des appels récursifs résolvant les sous-problèmes associés de taille moindre.

Cette récurrence se décompose suivant les trois étapes du paradigme de base : Si la taille du problème est suffisamment réduite, n ≤ c pour

une certaine constante c, la résolution est directe et consomme un temps constant O(1).

Sinon, on divise le problème en a sous-problèmes chacun de taille 1/b de la taille du problème initial. Le temps d'exécution total se décompose alors en trois parties : D(n) : le temps nécessaire à la division du problème en sous-

problèmes. aT (n/b) : le temps de résolution des a sous-problèmes. C(n) : le temps nécessaire pour construire la solution finale à partir

des solutions aux sous-problèmes.

Page 61: Cours algorithmique et complexite complet

Analyse des algorithmes « Diviser pour Régner »

La relation de récurrence prend alors la forme :

Soit la fonction f (n) la fonction qui regroupe D(n) et C(n). La fonction T(n) est alors définie :

T(n) = a.T(n / b) + f (n) T (n) = a.T (n / b) + c.nk

Page 62: Cours algorithmique et complexite complet

Résolution des récurrence des algorithmes « Diviser pour Régner » Théorème de résolution de la récurrence :

si a > bk T(n) = O(nlogb a )

si a = bk T(n) = O(nk logbn) si a < bk T(n) = O( f (n)) = O(nk )

Résolution de la relation de récurrence pour l’exemple de la multiplication de matrices : T(n) = 8 T(n/2) + O(n2) a = 8 , b = 2, k = 2 a > bk Logba = 3 T(n) = O(n3)

Page 63: Cours algorithmique et complexite complet

Analyse des algorithmes « Diviser pour Régner »

Complexité de l’algorithme de recherche du maximum: T(n) = 2 T(n/2) + 3 a = 2 , b = 2, k = 0 a > bk Logba = 1 T(n) = O(n)

Page 64: Cours algorithmique et complexite complet

Application : algorithme de recherche dichotomique Fonction RechDicho(Tab :Tableau, borneinf :entier, bornesup :entier, elem :entier) : bool

Si (borneinf<=bornesup) alors

mil = (borneinf+bornesup) DIV 2 ; Si (Tab[mil]=elem) Alors retourner (vrai) Sinon Si (Tab[mil]>elem) Alors Retourner (RechDicho(Tab, borneinf, mil-1, elem)) Sinon Retourner(RechDicho(Tab, mil+1, bornesup, elem)) Fin Si Fin Si

Sinon Retourner (Faux) FinSi

Page 65: Cours algorithmique et complexite complet

Application : algorithme de recherche dichotomique

Analyse de la complexité : T(n) = T(n/2) + cte a = 1 , b = 2, k = 0 a = bk a = bk T(n) = O(nk logbn) T(n) = O(log2n)

Page 66: Cours algorithmique et complexite complet

Exemples d’application Exemple 1 :

a = 9 , b = 3 , k = 1 a > bk Logba = 2 T(n) = O(n²)

Exemple 2 : a = 1 , b = 3/2 , k = 0 a = bk T(n) = O(nklogn) = O(logn)

nnTnT +

=

39)(

13

2)( +

=

nTnT

Page 67: Cours algorithmique et complexite complet

Exemples d’application Exemple 3 :

a = 3 , b = 4 , k = ?? or n < nlogn < n² 1 < k < 2 4 < bk < 16 a < bk T(n) = O(f(n)) = O(n logn)

nnnTnT log4

3)( +

=

Page 68: Cours algorithmique et complexite complet

Autres résolutions de récurrence Equations de récurrence linéaires:

Exemple : Les Tours de Hanoi T(n) = 2 T(n-1) + 1

( ) ( )nfnaTnT +−= 1)(

+= ∑

=

n

ii

n

aifTanT

1

)()0()(

122102)(

1−=

+= ∑

=

nn

ii

nnT

Page 69: Cours algorithmique et complexite complet

Autres résolutions de récurrence Equations de récurrence linéaires sans second

membre (f(n) = cte)

A une telle équation, on peut associer un polynôme:

La résolution de ce polynôme nous donne m racines ri ( avec m<=k).

La solution de l’équation de récurrence est ainsi donnée par :

Cette solution est en général exponentielle

( ) ( ) ( ) cteknTanTanTanT k =−−−−−−− ...21)( 21

kkkk axaxaxxP −−−−= −− ...)( 2

21

1

nmm

nnn rcrcrcrcnT ++++= ...)( 332211

Page 70: Cours algorithmique et complexite complet

Autres résolutions de récurrence Exemple : La suite de Fibonacci

T(n) = T(n-1) + T(n-2) On pose P(x) = X² - X – 1

251

251

21−

=+

= retrnn

nn barbranT

−+

+=+=

251

251)( 21

+Ο=

n

nT2

51)(

Page 71: Cours algorithmique et complexite complet

Suite du chapitre 3 – Les algorithmes de Tri

Page 72: Cours algorithmique et complexite complet

Tri par fusion Principe : Le principe du tri par fusion est plutôt

simple. On dit que trier un tableau de taille N, c'est trier deux tableaux de taille N/2; une fois les deux tableaux triés on n’a plus qu'à les réunir (les fusionner) de sorte à ce que le tableau final soit trié. Ce tri bien sûr utilise une notion de récursivité (un tableau étant la somme de deux tableaux).

Page 73: Cours algorithmique et complexite complet

Tri par fusion Algorithme : Fonction tri_fusion (T, deb, fin) : tableau entier T1, T2 : tableau entier [N/2] ; Si (deb >= fin) alors Retourner T ; Sinon mil = (deb + fin) DIV 2; T1 = tri_fusion (T, deb, mil) ; T2 = tri_fusion (T, mil+1, fin)) ; Retourner fusion (T1, T2) ; Fin si FIN

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

Page 74: Cours algorithmique et complexite complet

Tri par fusion Fonction fusion (T1, T2) : tableau entier T1 : tableau entier [1...N] ; T2 : tableau entier [1...M] ; T: tableau entier [1...M+N] ; i, j: entier; i←1 ;j←1 ; Tantque (i+j-1 <> N+M) faire Si (i ≤ N) alors si (j ≤ M) alors si (T1[i] < T2[j]) alors

T[i+j-1]←T1[i] ; i←i+1 ;

sinon T[i+j-1]←T2[j] ; j←j+1 ;

Fin si sinon T[i+j-1]←T1[i]; i←i+1; Fin si sinon T[i+j-1]←T2[j] ;

j←j+1 ; Fin si FinTanque Retourner T ;FIN Tfusion(n) = O(n)

Page 75: Cours algorithmique et complexite complet

Tri par fusion L’algorithme Tri_Fusion est de type « diviser pour

régner ». Il faut donc étudier ses trois phases: Diviser : cette étape se réduit au calcul du milieu

de l’intervalle [deb,fin], sa complexité est donc en O(1).

Régner : l’algorithme résout récursivement deux sous-problèmes de tailles respectives (n/2) , d’où une complexité en 2T(n/2).

Combiner : la complexité de cette étape est celle de l’algorithme de fusion qui est de O(n) pour la construction d’un tableau solution de taille n.

Page 76: Cours algorithmique et complexite complet

Tri par fusion T(n) = 2 T(n/2) + O(n) Rappel : Théorème de résolution de la récurrence

T(n) = a T(n/b) + O(nk): si a > bk T(n) = O(nlog

b a )

si a = bk T(n) = O(nk logbn) si a < bk T(n) = O( f (n)) = O(nk )

a = 2, b = 2, k = 1 (2ème cas) T(n) = O(n log2n)

Page 77: Cours algorithmique et complexite complet

Tri rapide (Quicksort) Principe : Le tri rapide est fondé sur le paradigme « diviser pour

régner», tout comme le tri par fusion, il se décompose donc en trois étapes :

Diviser : Le tableau T[deb..fin] est partitionné (et réarrangé) en deux sous-tableaux non vides, T[deb..inter] et T[inter+1..fin] tels que chaque élément de T[deb..fin] soit inférieur ou égal à chaque élément de T[inter+1..fin].

L’indice inter est calculé pendant la procédure de partitionnement.

Régner : Les deux sous-tableaux T[deb..inter] et T[inter+1..fin] sont triés par des appels récursifs.

Combiner : Comme les sous-tableaux sont triés sur place, aucun travail n’est nécessaire pour les recombiner, le tableau T[deb..fin] est déjà trié !

Page 78: Cours algorithmique et complexite complet

Tri rapide Tri_Rapide (T, deb, fin) Si (deb < fin ) alors inter =Partionner (T, deb, fin) Tri_Rapide (T, deb, inter) Tri_Rapide (T, inter+1, fin) Fin si Partionner (T, deb, fin) x = T(deb) i = deb-1 ; j= fin+1 Tant que (1) Répéter { j=j-1 } Jusqu’à T(j) <= x Répéter { i =i+1 } Jusqu’à T(i) >= x si ( i < j ) permuter (T(i), T(j)) sinon retourner j Fin Si Fin

Page 79: Cours algorithmique et complexite complet

Tri rapide : calcul de la complexité Pire cas Le cas pire intervient quand le partitionnement produit une région

à n-1 éléments et une à 1 élément. Comme le partitionnement coûte O(n) et que T(1) = O(1), la

récurrence pour le temps d’exécution est : T(n) = T(n-1) +O(n) et par sommation on obtient : T(n) = O(n²) Meilleur cas : Le meilleur cas intervient quand le partitionnement produit deux

régions de longueur n/2. La récurrence est alors définie par : T(n) = 2T(n / 2) + O(n) ce qui donne d’après le théorème de résolution des récurrences :

T(n) = O(nlog n)

Page 80: Cours algorithmique et complexite complet

Chapitre 5 – Graphes et arbres

Page 81: Cours algorithmique et complexite complet

Les graphes Un graphe orienté G est représenté par un couple

(S, A) où S est un ensemble fini et A une relation binaire sur S. L’ensemble S est l’ensemble des sommets de G et A est l’ensemble des arcs de G.

Il existe deux types de graphes : graphe orienté : les relations sont orientées et on parle d’arc. Un

arc est représenté par un couple de sommets ordonnés. Graphe non orienté : les relations ne sont pas orientées et on

parle alors d’arêtes. Une arête est représentée par une paire de sommets non ordonnés.

Page 82: Cours algorithmique et complexite complet

Les graphes Une boucle est un arc qui relie un sommet à lui-même. Dans un

graphe non orienté les boucles sont interdites et chaque arête est donc constituée de deux sommets distincts.

Degré d’un sommet : Dans un graphe non orienté, le degré d’un sommet est le nombre d’arêtes qui lui sont incidentes. Si un sommet est de degré 0, il est dit isolé.

Degré sortant d’un sommet : Dans un graphe orienté, le degré sortant d’un sommet est le nombre d’arcs qui en partent,

Degré rentrant d’un sommet : le degré entrant est le nombre d’arcs qui y arrivent et le degré est la somme du degré entrant et du degré sortant.

Chemin : Dans un graphe orienté G = (S,A), un chemin de longueur k d’un sommet u à un sommet v est une séquence (u0,u1,…, uk) de sommets telle que u = u0, v = uk et (ui-1, ui) appartient à A pour tout i. Un chemin est élémentaire si ses sommets sont tous distincts

Page 83: Cours algorithmique et complexite complet

Les graphes Un sous-chemin p0 d’un chemin p = (u0,u1, …. ,uk) est une sous-

séquence contiguë de ses sommets. Autrement dit, il existe i et j, 0<=i<= j <=k, tels que p0 = (ui,ui+1, …. ,uj).

Circuit : Dans un graphe orienté G=(S,A), un chemin (u0,u1, …. ,uk) forme un circuit si u0 =uk et si le chemin contient au moins un arc. Ce circuit est élémentaire si les sommets u0, ..., uk sont distincts. Une boucle est un circuit de longueur 1.

Cycle : Dans un graphe non orienté G = (S,A), une chaîne (u0,u1,…., uk) forme un cycle si k >= 2 et si u0 = uk. Ce cycle est élémentaire si les sommets u0, ..., uk sont distincts. Un graphe sans cycle est dit acyclique.

Sous-graphe : On dit qu’un graphe G0 = (S0,A0) est un sous-graphe de G = (S,A) si S0 est inclus dans S et si A0 est inclus dans A.

Page 84: Cours algorithmique et complexite complet

Les arbres Propriétés des arbres :

Soit G = (S,A) un graphe non orienté. Les affirmations suivantes sont équivalentes. 1. G est un arbre. 2. Deux sommets quelconques de G sont reliés par un unique

chemin élémentaire. 3. G est acyclique et |A| = |S| - 1 4. G est acyclique, mais si une arête quelconque est ajoutée à

A, le graphe résultant contient un cycle.

Page 85: Cours algorithmique et complexite complet

Arbres – définitions Arbre enraciné (ou arborescence): C’est un arbre dans

lequel l’un des sommets se distingue des autres. On appelle ce sommet la racine. Ce sommet particulier impose en réalité un sens de parcours de l’arbre

Ancêtre : Soit x un noeud (ou sommet) d’un arbre A de racine r. Un noeud quelconque y sur l’unique chemin allant de r à x est appelé ancêtre de x.

Père et fils : Si (y,x) est un arc alors y est le père de x et x est le fils de y. La racine est le seul noeud qui n’a pas de père.

Feuille ou noeud externe (ou terminal) : Une feuille est un noeud sans fils. Un noeud qui n’est pas une feuille est un noeud interne.

Sous-arbre : Le sous-arbre de racine x est l’arbre composé des descendants de x, enraciné en x.

Page 86: Cours algorithmique et complexite complet

Arbres - définitions Degré d’un nœud : Le nombre de fils du nœud x est

appelé le degré de x. Profondeur d’un nœud : La longueur du chemin entre la

racine r et le nœud x est la profondeur de x. Profondeur de l’arbre : c’est la plus grande profondeur

que peut avoir un nœud quelconque de l’arbre. Elle est dite aussi la hauteur de l’arbre.

Arbre binaire : c’est un arbre dont chaque nœud a au plus deux fils.

Arbre binaire complet : Dans un arbre binaire complet chaque nœud est soit une feuille, soit de degré deux. Aucun nœud n’est donc de degré 1.

Page 87: Cours algorithmique et complexite complet

Parcours des arbres Parcours en profondeur Dans un parcours en profondeur, on descend d’abord le

plus profondément possible dans l’arbre puis, une fois qu’une feuille a été atteinte, on remonte pour explorer les autres branches en commençant par la branche « la plus basse » parmi celles non encore parcourues. Les fils d’un nœud sont bien évidemment parcourus suivant l’ordre sur l’arbre.

Algorithme: Algorithme ParPro(A) si A n’est pas réduit à une feuille alors Pour tous les fils u de racine(A) Faire ParPro(u) Fin pour finSi Fin

Page 88: Cours algorithmique et complexite complet

Parcours des arbres Parcours en largeur Dans un parcours en largeur, tous les nœuds à une

profondeur i doivent avoir été visités avant que le premier nœud à la profondeur i+1 ne soit visité. Un tel parcours nécessite l’utilisation d’une file d’attente pour se souvenir des branches qui restent à visiter.

Algorithme: Algorithme Parcours_Largeur(A) F : File d’attente F.enfiler(racine(A)) Tant que F != vide Faire u=F.défiler() Afficher (u) Pour « chaque fils v de » u Faire F.enfiler (v) FinPour Fin Tant que Fin

Page 89: Cours algorithmique et complexite complet

Parcours des graphes Le parcours des graphes est un peu plus compliqué que

celui des arbres. En effet, les graphes peuvent contenir des cycles et il faut éviter de parcourir indéfiniment ces cycles. Pour cela, il suffit de colorier les sommets du graphe. Initialement les sommets sont tous blancs, lorsqu’un sommet est rencontré pour la première fois il est

peint en gris, lorsque tous ses successeurs dans l’ordre de parcours ont été

visités, il est repeint en noir.

Page 90: Cours algorithmique et complexite complet

Parcours des graphes Algorithme Pacours_Profondeur (G) Pour chaque sommet u de G Faire couleur[u]=Blanc FinPour Pour chaque sommet u de G Faire si couleur[u] = Blanc alors VisiterPP(G, u) FinSi FinPour Fin

Algorithme VisiterPP(G, s) couleur[s]=Gris Pour chaque voisin v de s Faire Si couleur[v] = Blanc alors VisiterPP(G, v) FinSi FinPour couleur[s]=Noir Fin

Page 91: Cours algorithmique et complexite complet

Parcours des graphes Algorithme Parcours_Largeur(G, s) F : File d’attente Pour chaque sommet u de G Faire couleur[u] = Blanc FinPour couleur[s]=Gris F.enfiler(s) Tant que F != Vide Faire u=F.défiler() Pour chaque voisin v de u Faire Si couleur(v) = Blanc alors couleur(v)= Gris F.enfiler(v) FinPour Couleur(u)= Noir FinTant que Fin

Page 92: Cours algorithmique et complexite complet

Chapitre 6 – Arbres binaires de

recherche

Page 93: Cours algorithmique et complexite complet

Définitions Un arbre binaire est un graphe qui admet une racine et

sans cycle et dont chaque nœud admet deux fils : fils droit et fils gauche.

Structure de données : Enregistrement Nœud { Info : Type (entier, réel, chaine, …) FilsG : ^ Nœud FilsD : ^ Nœud } Racine : ^Nœud Il existe 3 méthodes de parcours d’un arbre binaire :

parcours préfixe : père, fils gauche, fils droit parcours infixe : fils gauche, père, fils droit parcours postfixe : fils gauche, fils droit, père

Page 94: Cours algorithmique et complexite complet

Parcours préfixé Algorithme Préfixe(racine : ^Nœud) Si (racine != Nil) Alors AfficheRacine(racine) Préfixe (racine^.FilsG) Préfixe (racine^.FilsD) FinSi Fin

Page 95: Cours algorithmique et complexite complet

Parcours préfixé utilisant une pile Le programme suivant est une version dérécursivée

utilisant une pile explicite et permettant le parcours préfixé d’un arbre binaire.

Algorithme Préfixe (racine : ^Nœud) P : pile Empiler (P, racine) Tant que (Non_Vide(P)) racine = depiler (P) Empiler (P,racine^.FilsD) Empiler (P,racine^.FilsG) Fin Tant que Fin

Page 96: Cours algorithmique et complexite complet

Parcours infixé Algorithme infixe(racine : ^Nœud) Si (racine != Nil) Alors infixe (racine^.FilsG) AfficheRacine(racine) infixe (racine^.FilsD) FinSi Fin

Page 97: Cours algorithmique et complexite complet

Parcours postfixé Algorithme Postfixe(racine : ^Nœud) Si (racine != Nil) Alors Postfixe (racine^.FilsG) Postfixe (racine^.FilsD) AfficheRacine(racine) FinSi Fin

Page 98: Cours algorithmique et complexite complet

Arbre binaire de recherche Un arbre binaire de recherche est un arbre binaire dans

lequel chaque nœud est supérieur à son fils gauche et inférieur à son fils droit et il n’y a pas de nœuds égaux.

Un arbre binaire de recherche est intéressant puisqu’il est toujours possible de connaître dans quelle branche de l’arbre se trouve un élément et de proche en proche le localiser dans l’arbre. On peut aussi utiliser un arbre binaire de recherche pour ordonner une liste d’éléments.

Page 99: Cours algorithmique et complexite complet

Recherche dans un arbre binaire de recherche Algorithme Chercher (racine : ^Nœud , X : élément) Si ( racine = Nil) alors retourner 0 Sinon Si( racine^.info = X ) alors retourner 1 Else Si (X < racine^.info ) alors retourner Chercher(racine^.FilsG, X) Else retourner Chercher (racine^.FilsD, X) Finsi FinSi FinSi Fin

Page 100: Cours algorithmique et complexite complet

Insertion dans un arbre binaire de recherche L’élément à ajouter est inséré là où on l’aurait trouvé s’il avait

été présent dans l’arbre. L’algorithme d’insertion recherche donc l’élément dans l’arbre et, quand il aboutit à la conclusion que l’élément n’appartient pas à l’arbre (il aboutit à la terre), il insère l’élément comme fils du dernier nœud visité.

Algorithme Insérer (racine : ^Nœud, X : élément ) Si (racine = nil) alors « ajouter un nœud pour X à cet endroit» Sinon Si (racine^.info = X) Ecrire ("l’élément à insérer existe déjà dans l’arbre") Sinon Si (X> racine^.info) Insérer (racine^.FD, X) Sinon Insérer (racine^.FG, X) Fin

Page 101: Cours algorithmique et complexite complet

Suppression dans un arbre binaire de recherche

1er cas : l’élément à supprimer n’a pas de fils il est terminal et il suffit de le supprimer

Page 102: Cours algorithmique et complexite complet

Suppression dans un arbre binaire de recherche

2ème cas : l’élément a un fils unique on

supprime le nœud et on relie son fils à son père

Page 103: Cours algorithmique et complexite complet

Suppression dans un arbre binaire de recherche

3ème cas : l’élément à supprimer a deux

fils on le remplace par son successeur qui est toujours le minimum de ses descendants droits.

Page 104: Cours algorithmique et complexite complet

Merci pour votre attention !