Algorithmiqueavancee

Télécharger au format pptx, pdf ou txt
Télécharger au format pptx, pdf ou txt
Vous êtes sur la page 1sur 102

Algorithmique et

complexité
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
Objectifs
 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
Chapitre 1 – Introduction
et motivations
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
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.
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.
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
 : Coût de l’algorithme :
P=0 - (n+1) additions
- (n+1) multiplications
Pour i de 0 à n faire
- (n+1) puissances
P = P+ ai
*Xi
finpour
fin
Exemple : Calcul de la valeur d’un
polynôme
 2 variante :
ème
Coût de l’algorithme :
debut - (n+1) additions
Inter=1 - 2(n+1) multiplications
P =0
Pour i de 0 à N faire
P = P+ Inter *ai
Inter = Inter * X
finpour
Fin
Exemple : Calcul de la valeur d’un
polynôme
 3 variante : Schéma de Horner
ème

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
Coût de l’algorithme :
finpour - n additions
Fin - n multiplications

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


et l’implémenter
Chapitre 2 – Complexité
et optimalité
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.
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.
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.
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)
Exempl
e 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.
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
Classes de
complexité
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).


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

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


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

Pour i de indDeb à indFin faire


Traitement
Fin Pour
indFin

 O(T Traitement )
iindDeb
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.
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
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.
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) = n(n 1)  n
 sa complexité O(n2) 2
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)
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
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)
Chapitre 3 –
Les algorithmes de Tri
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...
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
sma
l
l  T(n) = O(n²)

j

;
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].
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  T(n) = O(n²)
Fin pour
Fin
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).
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
FIN si
 T(n) = 2 T(n/2) + Tfusion(n)
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
Fin si
[
sinon i
T[i+j-1]T1[i];
+ ii+1;
Fin si j
sinon -T[i+j-1]T2[j] ;
jj+1 ; Fin si 1
]
FinTanque Retourner T ;FIN  Tfusion(n) = O(n)
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.
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 a )
b
 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)
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é !
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
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)
Chapitre 4 – La
récursivité
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.
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.
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
Les types de
récursivité
Trace d’exécution de la fonction factorielle (calcul de la

valeur de 4!)
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 :
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 :
Les types de
récursivité
4/ La récursivité imbriquée
 Exemple : La fonction d'Ackermann
Récursivité terminale vs. non
terminale
 Une fonction récursive est récursive
terminale si dite tous ses
terminaux. appels sont récursifs
 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
Importance de l’ordre des appels
récursifs
Proc. terminale Proc. non terminale
Procédure AfficherGaucheDroite Procédure AfficherDroiteGauche
( Tab : Tableau entier, N : ( Tab : Tableau entier, N :
entier, i : entier) entier, i : entier)
Si (i<=N) Alors Si (i<=N) Alors
Ecrire(Tab[i]) ; AfficherGaucheDroite AfficherDroiteGauche (Tab,N,i+1) ;
(Tab,N,i+1) ; Ecrire(Tab[i]) ;
Fin Si Fin Si
Fin Fin

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


fonction récursive
Importance de l’ordre des appels
récursifs
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.
Elimination de la récursivité terminale
simple
Algo. récursif Algo. itératif
Procédure ALGOR(X) Procédure ALGOI(X)
Si (COND) Alors Tant que (COND) faire
TRAIT1 TRAIT1
ALGOR(β (X)) X  β (X)
Sinon Fin tant que
TRAIT2 TRAIT2
Fin Si Fin
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).
Application : a est diviseur de
b ? Algo. récursif Algo. itératif
Fonction Diviseur (a,b) : Bool Fonction Diviseur (a,b) : Bool
Si (a <=0) Alors Si (a <=0) Alors
Retourner(Faux) Retourner(Faux)
Sinon Sinon
Si (a>=b) Retourner (a=b) Tant que (a<b) Faire
Sinon b  b-a
Retourner (Diviseur (a,b-a)) Fin tant que
Fin Si Retourner (a=b)
Fin Si Fin Si
Fin Fin
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.
Elimination de la récursivité non terminale
simple
Algo. récursif Algo. itératif
Procédure ALGOR(X) Procédure ALGOI(X)
Si (COND) Alors Pile.init()
TRAIT1 Tant que (COND) Faire
ALGOR(β (X)) TRAIT1
TRAIT2 Pile.empiler(X)
Sinon X  β (X)
TRAIT3 Fin Tant que
Fin Si TRAIT3
Fin Tant que
(Non_vide_pile()) Faire
Pile.dépiler(U)
TRAIT2
Fin Tant que
Fin
Elimination de la récursivité non terminale
simple
Algo. récursif Algo. itératif
Procédure Procédure ALGOI(X)
AfficherDroiteGauche ( Tab Pile.init()
: Tableau entier, N : Tant que (i<=N)
entier, i : entier) Faire
Si (i<=N) Alors /* TRAIT1 */
AfficherDroiteGauche Pile.empiler(Tab[i])
(Tab,N,i+1) ;
i  i+1
Ecrire(Tab[i]) ;
Fin Tant que
Fin Si
/* TRAIT3 */
Fin
Tant que
TRAI
(Non_vide_pile()) Faire
T1 = Ø
Ecrire(Pile.dépiler() )
TRAI
T2 = /* TRAIT2 */
Ecrire(T Fin Tant que
Exemple : Tour 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.
Exemple : Tour 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.
Exemple : Tour 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
Exemple : Tour 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
Exemple : Tour 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
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.
Exemple
1Recherche 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)
Sinonretourner (k2)  T(n) = 2 T(n/2) + cte
FinSi
FinSi
Fin
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
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)
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.
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
Résolution des récurrence des
algorithmes
« Diviser
 Théorèmepour Régner
de résolution de »
la récurrence :
 si a > bk  T(n) = O(nlog a )
b
 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)
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)
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
R
etour
ner
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)
Exemples
d’application
 Exemple 1 : n
T (n)  9T  
 a=9,b=3,k=1 3
n
 a > bk
 Logba = 2
 T(n) = O(n²)
 Exemple 2 :  2n 
T (n)  T 
 a = 1 , b = 3/2 ,1k = 0 3
a = bk
 T(n) = O(nklogn) = O(logn)
Exemples
d’application
 Exemple 3 : n
T (n)  3T   n log
 a = 3 , b = 4 , k = ?? 4
n
or n < nlogn < n² 1 < k < 2
 4 < bk < 16  a < bk
 T(n) = O(f(n)) = O(n logn)
Autres résolutions de
récurrence
 Equations de récurrence linéaires:
n
 (0)  f (i)
T (n)  aTn 1 f n  
n
T (n)  a  T  i 

 a
i1
 Exemple : Les Tours de Hanoi
 n
1   n
 T(n) = 2 T(n-1) + 1  T (n)  2 n  0  2i 2 1
 i1 

Autres résolutions de
récurrence
 Equations de récurrence linéaires sans second
membre (f(n) = cte)
T (n)  a1T n 1 a2T n  2...  akT n  k  
cte
 P(x)  x on
A une telle équation,  a1peut
k
x  associer
a 2 x  ...un k
k 1 k 2

 La résolution ade ce polynôme nous donne m


polynôme:
racines ri ( avec m<=k).
 La solution de l’équation de récurrence est
donnée
ainsi par :
T (n)  c1 r1  c2 r2  c3r3  ...  cm rmn
n n n

 Cette solution est en général exponentielle


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
 5
1 5 1
r1  2 et r2  2 n n
T (n)  a r1 n  b r2n a  1 5   b  1 5 

  2   2 
  1 5 
n

 T (n)     2  
   
Chapitre 5 –
Graphes et arbres
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.
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
Les
graphes
Un sous-chemin p d’un chemin p = (u ,u , …. ,u ) est une sous-
0 0 1 k
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.
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.
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.
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.
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
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
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.
Parcours des
graphes
Algorithme Pacours_Profondeur (G) Algorithme VisiterPP(G, s)
Pour chaque sommet u de G Faire couleur[s]=Gris
couleur[u]=Blanc Pour chaque voisin v de s Faire Si
FinPour couleur[v] = Blanc alors
Pour chaque sommet u de G Faire VisiterPP(G, v)
si couleur[u] = Blanc alors FinSi
VisiterPP(G, u) FinPour
FinSi couleur[s]
FinPour =Noir
Fin Fin
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
Chapitre 6 –
Arbres binaires de
recherche
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
Parcours
préfixé Préfixe(racine : ^Nœud)
Algorithme

Si (racine != Nil) Alors


AfficheRacine(racine)
Préfixe (racine^.FilsG)
Préfixe
(racine^.FilsD)
FinSi
Fin
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
Parcours
infixé
Algorithme infixe(racine : ^Nœud)

Si (racine != Nil) Alors


infixe (racine^.FilsG)

AfficheRacine(racine)
infixe
(racine^.FilsD)
FinSi
Fin
Parcours postfixé
Algorithme Postfixe(racine : ^Nœud)

Si (racine != Nil) Alors


Postfixe (racine^.FilsG)
Postfixe
(racine^.FilsD)
AfficheRacine(racine)
FinSi
Fin
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.
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(r
acine^.Fils
G, X)
Else
retourner Chercher
(racine^.Fi
lsD, X)
Finsi
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
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
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
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.

Vous aimerez peut-être aussi