Vous êtes sur la page 1sur 178

Master en Sciences Informatiques

Synthèse de cours

Méthodes de Programmation
Partie 2 : Constructions avancées

Marc Dessoy

Facultés Universitaires de Namur


Cours en horaire décalé - BIHD3 - Session 2012-2013

Version du 31 décembre 2012


2 BIHD3 - Meth Pgm

2 www.madit.be 31 décembre 2012


Sommaire

Préambule 5

I Concepts communs 7

1 La récursion 9

2 Preuves et temps d’exécution 29

II Représentation des données 51

3 Les types abstraits (TA) 53

4 Implémentation des ensembles 65

5 TA File de priorité 107

III Conception d’algorithmes 113

6 Diviser pour régner 117

7 Programmation dynamique 123

8 Algorithmes gloutons 147

9 Générer et Tester 163

3
4 BIHD3 - Meth Pgm

4 www.madit.be 31 décembre 2012


Préambule

Sources
Ces notes sont majoritairement inspirées des notes [Sch12] du Professeur Pierre-Yves Schobbens
de la faculté d’informatique de Namur ainsi que des notes personnelles prises lors des cours et des
TP.
Ces notes complètent (et parfois corrigent) ma synthèse de cours sur les notions de Méthodes de
programmation vues en bac 2 [Des11]. Cette partie 1 constitue d’ailleurs un pré-requis important
pour la compréhension des notions de cette deuxième partie.

Mise en garde
Le seul objectif de ce document privé est de servir de support d’étude et de mémento structuré à
l’usage de son rédacteur. Il n’est certainement pas réputé exempt d’erreurs ou d’omissions.
Par camaraderie, ce document (PDF) est parfois mis à disposition de tout étudiant qui en fait la
demande, soit directement par le rédacteur, soit "via-via". Vous êtes toutefois invité à n’utiliser
les données qui y apparaissent qu’à des fins éducatives personnelles et certainement pas à des fins
commerciales.
Si vous avez apprécié ce partage, d’autres y trouveront peut-être aussi un bénéfice académique.
Pensez-y ! Toute remarque, toute erreur détectée, toute suggestion pour rendre certains passages
plus clairs, peut (devrait) être communiquée par courriel à l’adresse de l’administrateur du site
www.madit.be. Nous nous ferons un plaisir de corriger le document.
6 BIHD3 - Meth Pgm

6 www.madit.be 31 décembre 2012


Première partie

Concepts communs

7
1
La récursion

Sommaire
1.1 Récursion bien fondée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2 Récursion terminale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Structures de données récursives . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 Exercices de TP - procédures récursives . . . . . . . . . . . . . . . . . . . . 18

9
101.1 Récursion bien fondée BIHD3 - Meth Pgm

Définition 1.1
Une définition est (directement) récursive si le terme défini apparaît lui-même dans la définition.
1
1.1 Récursion bien fondée
1.1.1 Relation bien fondée et cas de base
Définition 1.2
Une relation binaire quelconque est bien fondée s’il ne peut y avoir de suite infinie strictement décroissante.

Ceci dépend de l’ensemble dans lequel on travaille :


par exemple, < sur les nombres entiers naturels N est bien fondée tandis que < sur les nombres entiers relatifs Z
ne l’est pas (nombres négatifs).
Définition 1.3
Une définition récursive est bien fondée ssi il existe une relation bien fondée entre les occurrences du terme
défini telle que les occurrences qui apparaissent dans la définition sont plus simples que le terme défini.

Exemple 1.1.1
factorielle(0) = 1
Il n’y a aucune occurrence dans la définition, donc elles sont toutes plus simples.
factorielle(n) = n * factorielle(n-1) si n > 0
L’occurrence factorielle(n-1) est plus simple que factorielle(n) si on utilise la valeur de l’argument
comme mesure : n − 1 < n.
A contrario, factorielle(n) = factorielle(n) est correct mais pas bien fondé !

Définition 1.4
Les éléments b qui n’ont pas d’élément "plus simple" sont appelés les "cas de base".

Exemple 1.1.2
comme la mesure d’une liste est sa longueur, le cas de base d’un algorithme sur les listes est une liste de
longueur 0, c’est-à-dire une liste vide.
Exemple 1.1.3
comme la mesure d’un arbre est sa hauteur, le cas de base d’un algorithme sur les arbres est une arbre de
hauteur 0, c’est-à-dire un arbre vide.

1.1.2 Preuves par induction générale


Si pour tout élément, en supposant qu’une propriété est vraie pour tous les éléments plus simples, on peut prouver
qu’elle est vraie pour cet élément, alors elle est vraie pour tous les éléments.

∀x : N(∀y < xP (y)) ⇒ P (x)


∀x : NP (x)

où < est bien fondé dans N.


Pour les cas de base b, il faut en fait prouver P (b).
Pour les autres, dès qu’on tombe sur P (y) plus simple, c’est prouvé !

10 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion11

1.1.3 Récursion croisée


Définition 1.5
1
Une récursion est croisée (ou mutuelle) si plusieurs termes définis forment un cycle.

Les récursions croisées sont admises en Pascal. Il faut d’abord déclarer les procédures et fonctions comme forward
pour que le compilateur connaisse leur type avant qu’elles ne soient utilisées :

function p1(n:integer) :integer forward ;

1.1.4 Exemple de procédure récursive : le labyrinthe


Supposons qu’on reçoive un labyrinthe codé dans un tableau à deux dimensions dont les cases sont soit blanches
(pour un couloir) ou ’X’ pour un mur.

X X X X X X X X X X X X
X X X X
X X X X X X
X X X X X
X X X X
X X X X X X X X X X X X

On demande d’imprimer un chemin vers la sortie.


Les objets utilisés par la procédure sont :

type
xdim, ydim { intervalles } ;
direction = nord,est,sud,ouest;
labychar = char { ’X’ , ’ ’ , ’.’} ;
position = record x: xdim; y: ydim end ;
var
laby: array [xdim,ydim] of labychar;

La recherche en profondeur nous trouve un chemin de sortie récursivement.


Elle marquera d’un ’.’ les cases blanches où on est déjà passé.

procedure sortir(pos: position) ;


var d: direction;
begin
marquer(pos) ;
if sortie(pos)
then sortietrouvee;
else for d := nord to ouest
if accessible(pas(pos,d))
then sortir(pas(pos,d)) ;
end


accessible teste que la position n’est pas un mur et n’est pas déjà marquée
pas(pos,d) avance d’un pas dans la direction d.
Base de la preuve : Le programme termine car le nombre de cases blanches (non marquées) diminue toujours,
c’est notre variant. Il atteint toutes les cases accessibles car il essaie toutes les directions possibles.
NB : à chaque impasse, on revient à la position de départ.
Le chemin vers la sortie est constitué par les arguments de sortir ; on peut les conserver dans une liste pour les
afficher.

31 décembre 2012 www.madit.be 11


121.2 Récursion terminale BIHD3 - Meth Pgm

1.2 Récursion terminale


1
1.2.1 Terminale ?
Une fonction récursive terminale [1] est une fonction qui, lorsqu’elle fait appel à elle-même, le fait en tout dernier.
Concrètement il n’y a pas de calcul entre l’appel récursif et l’instruction return. Le terme anglais est "tail-end
recursion". Il s’agit donc d’une manière d’écrire une fonction récursive.
Cela sert à produire du code efficace, clair, et qui ne risque pas de faire déborder la pile d’exécution. D’un point de
vue algorithmique, une fonction récursive terminale est équivalente à une simple boucle. Écrire ainsi les fonctions
récursives - quand cela est possible - permet au compilateur d’optimiser efficacement le code produit.

1.2.2 Exemple avec un accumulateur : la fonction ’factorielle’


La plupart des programmeurs préfèrent utiliser une boucle for :

1 int factorial(int n) {
2 int res=1;
3 for(; n; --n) res *= n;
4 return res;
5 }

Ce code fonctionne parfaitement bien. Il a l’unique défaut d’être finalement assez éloigné de la définition
mathématique de la factorielle, c’est-à-dire que la plupart des gens le jugeront peu naturel.
Considérons maintenant une implémentation plus naturelle, très proche de la définition mathématique ... mais
non-terminale !

1 int factorial (int n) {


2 if (n == 0) return 1;
3 else return n * factorial(n - 1);
4 }

Cette implémentation récursive est assurément très mauvaise. Elle consomme beaucoup de mémoire en pile, donc
c’est lent et ça peut aussi se terminer prématurément si on l’appelle avec un paramètre trop grand (stack overflow).
La version récursive terminale pallie précisément ce problème :

1 int tfactorial (int n, int accu) {


2 if (n <= 1) return accu;
3 else return tfactorial(n - 1, n * accu);
4 }

Le paramètre accu joue le rôle d’un accumulateur. L’évaluation de f(5,1) conduit à la suite d’appels
f(5,1) -> f(4,5) -> f(3,20) -> f(2,60) -> f(1,120).

1.2.3 L’accumulateur
Pour écrire une fonction récursive sous forme terminale, il est souvent nécessaire de modifier sa signature en y
ajoutant un paramètre.
Ce paramètre accu joue le même rôle que la variable de boucle res. On peut aussi le considérer comme un
paramètre qui fait sens et qui améliore notre fonction. Ainsi dans l’exemple tfactorial, ce paramètre accu sert
à choisir la valeur du cas de base (factorielle(0)).
En général, on souhaite masquer ce paramètre en trop. On définit alors deux fonctions : la première que l’on cache
et qui prend deux paramètres, la seconde qui appelle la première et expose la signature voulue. Pour faire cela, il
existe diverses méthodes selon le langage (et les paradigmes) utilisé.

[1]. Cette notion a été abordée en TP, mais sans support. Source intéressante : http ://blog.fastconnect.fr/ ?p=174

12 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion13

En C, on peut utiliser le mot static pour empêcher une fonction d’être exposée dans l’interface du module.

1
2
static int sFactorial (int n, int accu) {
if (n == 0) return accu;
1
3 else return sFactorial(n-1, n*accu);
4 }
5 int factorial (int n) {
6 return sFactorial(n, 1);
7 }

La plupart des langages objets offrent des modificateurs d’accès. En Java, on utilisera les modificateurs public et
private :

1 public Mathematiques {
2 private static sFactorial(int n, int accu) {
3 if (n == 0) return accu;
4 else return sFactorial(n-1, n*accu);
5 }
6 public static factorial (int n) {
7 return sFactorial(n, 1);
8 }
9 }

Certains langages, tels que Python ou C], permettent de donner une valeur par défaut à un paramètre. Ce qui
peut s’avérer utile.

1 def factorielle(n, accu=1):


2 if n == 0: return accu
3 else: return factorielle(n-1, n*accu)

1.2.4 Deux accumulateurs (la suite de Fibonacci)


La fonction de Fibonacci est particulière à bien des égards. L’implémentation naïve, outre le fait de ne pas être
terminale, présente le défaut de nous faire calculer de nombreuses fois les mêmes valeurs
Exemple 1.2.1
Fibonacci(4) = Fibonacci(3) + Fibonacci(2) et Fibonacci(3) = Fibonacci(2) + Fibonacci(1)
Donc nous calculons deux fois Fibonacci(2)

1 int fibonacci_naive(int n) {
2 if (n == 0) return 0 ;
3 if (n == 1) return 1 ;
4 return fibonacci_naive(n-1) + fibonacci_naive(n-2) ;
5 }

Pour écrire la fonction de Fibonacci sous forme récursive terminale, nous avons besoin d’utiliser deux accumulateurs
(accu0 et accu1).
L’origine vient de l’implémentation itérative qui utilise deux variables de boucle (res0 et res1) et du fait que la
définition récursive donne deux cas de base (fibonacci(0) et fibonacci(1)).

1 int fibonacci_recursive_terminale(int n, int accu0, int accu1) {


2 if (n == 0) return accu1 ;
3 return fibonacci_recursive_terminale(n-1, accu1, accu0+accu1) ;
4 }
5 int fibonacci(int n){
6 return fibonacci_recursive_terminale(n,1,0);
7 }

Dans ce cas, l’implémentation récursive terminale apporte beaucoup plus qu’une bonne gestion de la pile d’exécution.

1.2.5 Récursivité terminale et les langages


Est-ce que ça marche pour tous les langages ? Malheureusement non !
Voir le blog sur http://blog.fastconnect.fr/?p=174

31 décembre 2012 www.madit.be 13


141.3 Structures de données récursives BIHD3 - Meth Pgm

1.3 Structures de données récursives


1
1.3.1 Liste
On peut définir des structures de données récursivement.
Par exemple, une liste peut être définie comme étant
– soit une liste vide,
– soit composée d’un élément (la tête) et d’une liste (le reste).
Exemple 1.3.1
1 — 3 — 7 — 5 — 3 est une liste ; sa tête est 1 ; son reste est 3 — 7 — 5 — 3

 Codage en Pascal
Les structures de données récursives sont interdites en Pascal, sauf pour les pointeurs.
On les implémente donc par des pointeurs. Ce codage se dérive automatiquement de la définition récursive.

type
liste = ^cell ;
cell = record
tête: information;
reste: liste ;
{ inv : longueur ( l^.reste) < longueur(l) } ;
end ;

Cette façon de coder les listes s’appelle les listes (simplement) chaînées.
Remarque : il faut ajouter l’invariant de données sur liste, car rien ne garantit en Pascal que les pointeurs sont
sans cycles (bien fondés).

14 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion15

1.3.2 Arbres binaires


L’invariant de représentation : un arbre binaire est 1
– soit un arbre vide,
– soit composé d’une information et de deux arbres (le fils gauche et le fils droit).
Une mesure bien fondée est la hauteur de l’arbre.
– arbre vide : hauteur = 0
– un seul noeud : hauteur = 1

 Arbres binaires en Pascal

type
arbreBinaire = ^cell;
cell = record
tête: information;
gauche: arbreBinaire
{ invariant : hauteur (a^.gauche) < hauteur (a) } ;
droit : arbreBinaire
{ invariant : hauteur (a^.droit) < hauteur (a) } ;
end ;

Attention à l’invariant

 Construire un arbre binaire


On peut construire n’importe quel arbre binaire au moyen de deux opérations :

function arbreVide: arbreBinaire;


begin
arbreVide := nil;
end

function newNode(f: information; g,d: arbreBinaire) : arbreBinaire;


var r: arbreBinaire;
begin
new r;
r^.tête := f;
r^.gauche := g;
r^.droit := d;
newNode := r;
end

 Observer un arbre binaire


Attention : les pointeurs sont une source d’erreurs ! Donc concentrer leur utilisation dans des types abstraits, le
reste du programme ne d’occupant plus de ces pointeurs.

function info(a: arbreBinaire) : information;


begin
info := a^ .tête
end

function gauche(a: arbreBinaire) : arbreBinaire;


begin
gauche := a^.gauche
end

function droit(a: arbreBinaire) : arbreBinaire;


begin
droit := a^.droit
end

31 décembre 2012 www.madit.be 15


161.3 Structures de données récursives BIHD3 - Meth Pgm

 Recherche dans un arbre binaire


1 La fonction dans nous dit si une information se trouve dans l’arbre :

function dans(e:information; a: arbreBinaire) : boolean ;


dans(e, arbreVide) = false
dans(e, newNode(f,g,d)) = (e=f) or dans(e,g) or dans(e,d)

La relation dans(e,g) ou dans(e,d) est bien fondée car <

1.3.3 Arbres binaires triés (ABT)


L’invariant de représentation : Un arbre binaire est trié ssi :
– il est vide ou
– son information est plus grande que toutes les informations contenues dans le fils de gauche,
– et plus petite que toutes les informations contenues dans le fils de droite,
– et ses deux fils sont des arbres binaires triés.

3 8

2 4 7 9

 Implémentation de l’ABT en Pascal

function dans(e:information; a: abt) : boolean ;


begin
if a = arbreVide
then dans := false
else if e = info(a)
then dans := true
else if e < info(a)
then dans := dans(e,gauche(a))
else dans := dans(e,droit(a))
end;

 Recherche dans un arbre binaire trié


La fonction dans nous dit si une information se trouve dans l’ABT :

function dans(e:information; a: abt) : boolean ;

dans(e, arbreVide) = false

dans(e, newNode(f,g,d)) =
true si e = f
or dans(e,g) si e < f
or dans(e,d) si e > f

On ne parcourt qu’une branche dans l’arbre. La complexité est la hauteur de l’arbre, donc en O(n) plutôt que le
nombre d’éléments en O(2n )

16 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion17

1.3.4 Arbres ordonnés (AO)


un arbre ordonné est vide ou composé d’une information et d’une liste d’arbres (ses fils). 1
Le premier fils est appelé l’aîné (the eldest), le suivant dans l’ordre est appelé le cadet (the youngest), le dernier
est appelé le puîné (ou benjamin) (the younger).

père

ainé cadet de l’ainé benjamin

petit-fils

 Implémentation de l’AO en Pascal

type
listearbreOrd = ^cell;
arbreOrd = listearbreOrd; { inv(a) : a <> nil }
cell = record
e: information;
son: listearbreOrd;
brother: listearbreOrd;
end;

Notons que son représente (un pointeur vers) la liste des fils, tandis que brother est le pointeur vers le cadet (le
reste de la liste).

arbreOrd

brother brother brother


A B C

son son son


brother
D

son

 Invariant de représentation
Un arbre ordonné est

• soit un arbre vide


• soit un arbre
◦ composé d’une information et de deux arbres, un pour ses fils et un pour ses frères
◦ acyclique en profondeur, soit pour tout arbre a , on a NOT dans(a.e,a.son) et NOT dans(a.e,a.brother)
◦ acyclique frères/fils, soit pour tout arbre a ,
on a si NOT estVide(a.son) alors NOT dans(a.son.e.e,a.brother)
et si NOT estVide(a.brother) alors NOT dans(a.brother.e,a.son)

[TODO] la REP n’est pas parfaite ! ! !

31 décembre 2012 www.madit.be 17


181.4 Exercices de TP - procédures récursives BIHD3 - Meth Pgm

1.4 Exercices de TP - procédures récursives


1
1.4.1 Fonctions récursives terminales
Exercice 1.1
Spécifier et construire de manière récursive une fonction Fact permettant de calculer le factoriel d’un entier n
en entrée.
F (n) = n! = n × (n − 1) × (n − 2) × . . . × 1 ; ∀n ≥ 0
Modifier la fonction Fact de telle sorte que la récursivité soit terminale.
Précondition : n integer >= 0
Postcondition : fact(n) integer >= 1
cas de base : n=0, fact(0) = 1
cas récursif : n>0, fact(n) = n * fact(n-1)
relation bien fondée : 0 <= n-1 < n
Récursivité classique :

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int fact(int n) {
5 if (n<0) exit(1) ; // check precondition
6 if (n==0) return 1 ;
7 return n * fact(n-1);
8 }
9
10 /**/
11 void main() {
12 int n = 10;
13 printf("\n Le factoriel de %d est %d \n ", n , fact(n));
14 }

Récursivité terminale (Voir section 1.2 page 12) :

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 static int fact_terminale(int n, int accu) {
5 if (n==0) return accu ;
6 return fact_terminale(n-1, n*accu);
7 }
8
9 /* fonction appelante */
10 int tfact{int n} {
11 if (n<0) exit(1) ; // check precondition
12 return fact_terminale(n,1); // 1 = cas de base
13 }
14
15 /**/
16 void main() {
17 int n = 10;
18 printf("\n Le factoriel de %d est %d \n ", n , tfact(n));
19 }

static : masquer la fonction dans l’interface en C


exit(1) : sortie en mode erreur

18 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion19

1.4.2 Recherche du minimum


Exercice 1.2
1
Soit le tableau d’entiers a[la].
On demande de spécifier et construire une fonction récursive permettant de trouver le minimum de a[la].
Pour ce faire, on suivra le principe suivant : on coupe le tableau en deux parties de tailles presqu’égales ; on
suppose que, récursivement, on connaît les minima de ces deux parties ; le minimum de ce tableau sera la plus
petite des deux valeurs.
On réalisera pas à pas les étapes ci-après.
1. Spécification de la fonction.
2. Construction de la fonction (et justification de cette construction).
3. Description du déroulement de la fonction pour a[4] = (2, 5, 3, 1).

f (first) est le premier index du (sous-)tableau ; l (last) est le dernier index


Précondition : a init ; SIZE > 0
Postcondition : binarySearchMin(a,f,l) est la valeur minimale du tableau
cas de base : f = l ; min(a, f, l) = a[f ]
cas récursif : f < l ; min(a, f, l) = min { min(a, f, (f + l)/2), min(a, ((f + l)/2) + 1, l) }
bien fondée : (l − (f + l)/2) + 1) ≤ ((f + l)/2 − f ) < (l − f )

1 #include <stdio.h>
2 #include <stdlib.h>
3 #define SIZE 4
4
5 int minimum(int a, int b) {
6 if (a < b) return a;
7 return b;
8 }
9
10 static int binarySearchMin(int[] a, int first , int last) {
11 if (last < first)
12 exit(1);
13 if (last == first)
14 return a[first];
15 int mid = (first+last)/2;
16 int leftmin = binarySearchMin (a, first, mid);
17 int rightmin = binarySearchMin (a, mid+1, last);
18 return minimum(leftmin,rightmin);
19 }
20
21 int findMinimum (int[] a) {
22 int size = sizeof(a)/sizeof(*a); // calcul de la taille du tableau
23 return findMin(a,0,SIZE-1);
24 }
25
26 void main() {
27 int t[SIZE];
28 printf("\n Enter a sorted array of %d digits", SIZE);
29 for(x = 0; x < SIZE; x++){
30 scanf("%d",&num[x]); // saisie et affectationd dans un tableau
31 }
32 printf("\n The minimum value is %d",findMinimum(t));
33 }

voir saisie et affectation dans un tableau


voir calcul de la taille d’un tableau

31 décembre 2012 www.madit.be 19


201.4 Exercices de TP - procédures récursives BIHD3 - Meth Pgm

1.4.3 Algorithme du tri fusion


1 Exercice 1.3
Le principe de l’algorithme de tri par fusion consiste a fusionner deux sous-séquences triées en une séquence triée.
Il exploite le principe du "divide-and-conquer" qui consiste, en la division d’un problème en ses sous problèmes
et en des recombinaisons bien choisies des sous-solutions :
• On découpe les données a trier en deux parties plus ou moins égales
• On trie les 2 sous-parties ainsi déterminées
• On fusionne les deux sous-parties
Spécifier et construire une version itérative de cet algorithme.
f est le premier index du (sous-)tableau ; l est le dernier index.
Précondition : a0 init
Postcondition : a est trié ; a = permutation(a0 )
cas de base : f = l ; mergesort(a, f, l) = a
cas récursif : f < l ; mergesort(a, f, mergesort(a, f, (f + l)/2), mergesort(a, ((f + l)/2) + 1, l))
bien fondée : (l − (f + l)/2) + 1) ≤ ((f + l)/2 − f ) < (l − f )

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 void mergesort(int a[], int b[],int first, int last);
5 void merge(int a[], int b[], int firstw, int mid, int high);
6
7 int main() {
8 int a[8]={10,1,15,6,9,17,2,13};
9 int b[8];
10 mergesort(a,b,0,7);
11 printf("the sorted array is\n");
12 for(int i=0; i<8; i++) printf("%d\n",a[i]);
13 return 0;
14 }
15
16 void mergesort(int a[], int b[], int first, int last) {
17 if (first <= last) {
18 int med=(first+last)/2;
19 mergesort(a, b, first, med);
20 mergesort(a, b, med+1, last);
21 merge(a, b, first, med, last);
22 }
23 }
24
25 // Efficient variant
26 void merge(int a[], int b[], int first, int med, int last) {
27 // copy first half of array a to auxiliary array b
28 int j = first ;
29 int i = 0;
30 while (j <= med) b[i++]=a[j++];
31 // copy back next-greatest element at each time
32 int k = first ;
33 i = 0;
34 while (k<j && j<=last) {
35 if (b[i]<=a[j])
36 a[k++]=b[i++];
37 else
38 a[k++]=a[j++];
39 }
40 // copy back remaining elements of first half (if any)
41 while (k<j) a[k++]=b[i++];
42 }

jeu des indices du merge


déclaration anticipée de méthodes en mode forward

20 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion21

1.4.4 Listes chaînées


Exercice 1.4
1
Spécifier et construire (de manière récursive) des fonctions permettant de résoudre les problèmes suivants.
1. Détruire complètement une liste simplement chaînée d’entiers.
2. Tester la présence d’une valeur donnée dans une liste simplement chaînée d’entiers.
3. Calculer le nombre d’occurrences d’une valeur donnée dans une liste simplement chaînée d’entiers.
4. Supprimer toutes les occurrences d’une valeur donnée dans une liste simplement chaînée d’entiers.
5. Réaliser une copie d’une liste simplement chaînée d’entiers.
6. Renverser une liste simplement chaînée d’entiers.

Définition des structures

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 typedef struct node {
5 int val;
6 struct node *next;
7 } node;
8
9 typedef struct node* list;

Construction

1 /** ajout en tête, version 1 : retour par val */


2 list ajouterEnTete1(list l, int val) {
3 list newNode = (list) malloc(sizeof(node));
4 newNode->val = val;
5 newNode->next = l;
6 l = newNode;
7 return l;
8 }

1 /** ajout en tête, version 2 : retour par référence */


2 void ajouterEnTete2(list* l, int val) {
3 list newNode = (list) malloc(sizeof(node));
4 newNode->val = val;
5 newNode->next = *l;
6 *l = newNode;
7 }

1 /** ajout en queue, version 1 : retour par val */


2 list ajouterEnQueue1(list l, int val) {
3 list newNode = (list) malloc(sizeof(node));
4 newNode->val = val;
5 newNode->next = NULL;
6 if (l==NULL)
7 l = newNode;
8 else {
9 list p = l;
10 while (p->next != NULL) p = p->next;
11 p->next = newNode;
12 }
13 return l;
14 }

31 décembre 2012 www.madit.be 21


221.4 Exercices de TP - procédures récursives BIHD3 - Meth Pgm

1 /** ajout en queue, version 2 : retour par référence */


1 2
3
void ajouterEnQueue2(list* l, int val) {
list newNode = (list) malloc(sizeof(node));
4 newNode->val = val;
5 newNode->next = NULL;
6 if (*l==NULL)
7 *l = newNode;
8 else {
9 list p = *l;
10 while (p->next != NULL) p = p->next;
11 p->next = newNode;
12 }
13 }

Affichage (récursif) de la liste chainée

1 /** affichage récursif de la list */


2 void afficher(list l) {
3 if (l!=NULL) {
4 printf("%d ",l->val);
5 afficher(l->next);
6 }
7 }

Détruire complètement (et récursivement) une liste simplement chaînée d’entiers.

1 void detruire(list *l) {


2 if(*l != NULL) {
3 detruire(&( (*l)->next ));
4 free(*l);
5 }
6 }

Tester la présence d’une valeur donnée dans une liste simplement chaînée d’entiers.

1 bool tester(list l, int val) {


2 if (l==NULL) return 0;
3 else {
4 if (l->val == val) return 1 ;
5 else return tester(l->next, val);}
6 }

Calculer le nombre d’occurrences d’une valeur donnée dans une liste simplement chaînée d’entiers.

1 int occurences(list l, int val) {


2 if (l==NULL) return 0 ;
3 else {
4 if (l->val == val) return 1 + occurences(l->next,val);
5 else return occurences(l->next,val);
6 }
7 }

Supprimer toutes les occurrences d’une valeur donnée dans une liste simplement chaînée d’entiers.

[TODO] vérifier ce code ! ! !

1 void deleteOccurences(list *l, int val) {


2 if (l!=NULL) {
3 if (l->val == val) {
4 list p = *l;
5 l* = l->next;
6 free(*p);
7 }
8 deleteOccurences(l->next,val);
9 }
10 }

22 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion23

Réaliser une copie d’une liste simplement chaînée d’entiers.

1
2
list copie(list l) {
list p;
1
3 if (l==NULL) p = NULL ;
4 else p = ajouterEnTete1(copie(l->next),l->val);
5 return p;
6 }

Renverser une liste simplement chaînée d’entiers.

1 list renverser(list l) {
2 list p;
3 if(l==NULL) p=NULL;
4 else {
5 p=renverser(l->next);
6 p=ajouterEnQueue1(p,l->val);
7 }
8 return p;
9 }

31 décembre 2012 www.madit.be 23


241.4 Exercices de TP - procédures récursives BIHD3 - Meth Pgm

1.4.5 Valeurs communes à deux listes


1 Exercice 1.5
On demande de spécifier et construire une fonction récursive permettant de construire une liste simplement
chaînée et strictement triée d’entiers correspondant aux valeurs communes à deux listes d’entiers simplement
chaînées et strictement triées.
Cette fonction devra avoir l’en-tête liste Commun(liste a,b,c) où le type liste est déclaré comme dans
l’exercice précédent.
A partir des méthodes de l’exercice précédent

1 list commun (list a, list b, list c) {


2 if ((a ==NULL) || (b==NULL))
3 return c;
4 else {
5 if (a->val == b->val) {
6 c=ajouterEnQueue1(c, a->val);
7 c=commun(a->next, b->next, c);
8 }
9 else
10 if(a->val > b->val)
11 c = commun(a->next, b, c);
12 else
13 c = commun(a, b->next, c);
14 }
15 }

le main commun aux exo 3 et 4

1 void main() {
2 list l1=NULL;
3 list l2=NULL;
4 list l3=NULL;
5
6 for (int i=1 ; i<10 ; i++) l1 = ajouterEnTete1(l1,i);
7
8 printf("\n Affichage de la liste l1 ");
9 afficher(l1);
10
11 printf("\n \n Le nombre d'occurrences de '2' est %d \n ", occurence(l1,2));
12
13 l2=copie(l1);
14 printf("\n Le résultat de la copie de l1 est l2 = ");
15 afficher(l2);
16
17 l3=commun(l1,l2,l3);
18 printf(" \n \n Les valeurs communes entre deux listes sont ");
19 afficher(l3);
20
21 l1=renverser(l1);
22 printf(" \n \n L'inverse de l1 est \n");
23 afficher(l1);
24
25 detruire(&l1);
26 printf(" \n \n La liste l1 est détruite \n");
27 }

24 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion25

1.4.6 Évaluation binaire


Exercice 1.6
1
Spécifier et construire une fonction récursive permettant l’évaluation d’un nombre à partir d’une représentation
binaire de celui-ci ; plus précisément, spécifier et construire une fonction renvoyant l’entier représenté par un
tableau de booléens (true représente 1 et false représente 0).
Pre-condition : the array a is initialized, the values of an array must be either 0 or 1
Post-condition : the num(a,index, size) is an integer

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int num(int a[],int i, int size);
5 int exp(int);
6
7 int main(void) {
8 int a[8]={1,0,0,0,0,0,0,0};
9 printf("size of array %d", (int)(sizeof(a)/sizeof(*a)));
10 printf("!!!The number %d = ", num(a,0,7));
11 return EXIT_SUCCESS;
12 }
13
14 int num(int a[], int i, int size){
15 if(i >= size) return 0;
16 return a[i] * exp(size-i) + num(a,i+1,size);
17 }
18
19 int exp(int e){
20 if(e==0) return 1;
21 return 2 * exp(e-1);
22 }

Voir notes manuscrites !

31 décembre 2012 www.madit.be 25


261.4 Exercices de TP - procédures récursives BIHD3 - Meth Pgm

1.4.7 Multiplication binaire


1 Exercice 1.7
On demande de construire une fonction vérifiant la spécification ci-dessous.
En-tête : int M(int x, y ,z)
Précondition : x, y ≥ 0
Postcondition : z = x ∗ y
Cette fonction devra être construite de manière récursive en supposant ne disposer que de la multiplication par
2 et de la division entière par 2. Il s’agit en fait de s’appuyer sur les propriétés suivantes de la multiplication :

0.a = 0
(2.a).b = 2.(a.b)
(2.a + 1).b = 2.(a.b) + b

Voir notes manuscrites !

26 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 1. La récursion27

1.4.8 Suites équilibrées


Exercice 1.8
1
Spécifier et construire une fonction récursive permettant de tester si un préfixe de tableau d’entiers a[la] constitue
ou non une suite équilibrée.
On appelle suite équilibrée toute suite finie construite par applications répétées de la règle suivante :
Si n est un naturel et si S1 , S2 , . . . , Sn sont des suites équilibrées déjà construites, la suite (nS1 S2 . . . Sn ) est
équilibrée.
En particulier, la suite (0) est équilibrée car on l’obtient en appliquant la règle avec n = 0.
Autres exemples : (2, 0, 0), (1, 1, 0), (5, 0, 1, 0, 2, 0, 0, 3, 1, 0, 0, 0, 0).

Voir notes manuscrites !

31 décembre 2012 www.madit.be 27


281.4 Exercices de TP - procédures récursives BIHD3 - Meth Pgm

28 www.madit.be 31 décembre 2012


2
Preuves et temps d’exécution

Sommaire
2.1 Preuves de programmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.2 Temps d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.3 Espace en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

29
302.1 Preuves de programmes BIHD3 - Meth Pgm

2.1 Preuves de programmes


2.1.1 Règles
Pour que les règles de preuve simplifiées soient correctes, il faut que :
2 • chaque fonction n’a pas d’effet de bord : elle ne modifie ni ses variables non-locales, ni ses paramètres
par variable (elle peut modifier ses variables locales ou ses paramètres par valeur, mais ceci n’a pas d’effet
visible).
• chaque fonction est déterministe : son résultat ne dépend que de la valeur de ses arguments.
• il n’y a pas de variables synonymes (pas d’aliasing) : lors d’un appel de procédure, les paramètres par
variable et les variables globales doivent désigner des emplacements distincts.
• Chaque sous-programme p (fonction ou procédure) aie sa pré- et postcondition, notées pre(p), post(p) et
son variant V p si elle est récursive.

2.1.2 Prouver la correction partielle


Définition 2.1
Soit S un fragment de programme et P, P 0 , Q, Q0 des assertions à propos des variables dans S. On a

P ⇒ P0 wp
{P 0 } S {Q0 }
sp Q0 ⇒ Q
{P } S {Q}

Donc pour conclure à {P } S {Q}, il suffit de prouver la correction de S par rapport à une postcondition Q0 plus
forte que Q et/ou une précondition P 0 plus faible P . Ce qui conduit à deux approches.
A chaque structure algorithmique correspond une méthode adéquate. En général avant de prouver une méthode, il
faut prouver sa précondition.

 Notations pour la précondition


pre(e) est la précondition de e définie ci-dessous :

pre(x) = init(x)
pre(ab) = pre(a) ∧ ¬f ree(a)
pre(a.c) = pre(a)
pre(a[i]) = pre(i) ∧ (min ≤ i ≤ max) ∧ init(a[i])
pre(~e) = ∧ ni=1 pre(ei ) = pre(e1 ) . . . pre(en )
pre(f (~e)) = pre(f )[~x:=~e] ∧ pre(~e)

 Notations pour les variables


• x0 : une valeur en entrée de méthode
• x0 : une valeur intermédiaire entre x0 et x
• P [x/x0 ] : le remplacement de x par x0 dans P , soit l’affectation x := x0

30 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution31

2.1.3 Preuves par la méthode progressive "sp"


Voir [Des11, chap. 3]

 L’affectation à une variable simple

Définition 2.2 2

S = (x := e) ⇒ sp(x := e , P ) ≡ P[x/x0 ] ∧ x = e[x/x0 ]

Exemples : voir [Des11, § 3.2.1]

 L’affectation dans un tableau

Définition 2.3

S = (a[i] := e) ⇒ sp(a[i] := e , P )
≡ P[a/a0 ] ∧ a[i[a/a0 ] ] = e[a/a0 ] ∧ ∀j 6= i[a/a0 ] : a[j] = a0 [j]

La dernière assertion mentionne ce qui ne varie pas !


Exemples : voir [Des11, § 3.2.2]

 Séquence

Définition 2.4

sp(S1 ; S2 ; · · · ; Sn , P ) = An

A0 = P

Ai = sp(Si , Ai−1 ) ∀i : 1 ≤ i ≤ n

Méthode :

1. entre chaque étape de la séquence appliquer la définition 2.4 ;


2. pour chaque sp(Si , Ai−1 ) appliquer la définition adéquate (ex : 2.2 pour l’affectation)

Exemples : voir [Des11, § 3.2.3]

 Sélection
soit une condition C,
Définition 2.5



 sp(S1 , P ∧ C)

sp(if C then S1 else S2 , P ) = ∨


 sp(S , P ∧ ¬C)

2

Méthode :

1. pour chaque variante de la sélection, appliquer la définition 2.5 ;


2. pour chaque sp appliquer la définition adéquate (ex : 2.2 pour l’affectation)

Exemples : voir [Des11, § 3.2.4]

31 décembre 2012 www.madit.be 31


322.1 Preuves de programmes BIHD3 - Meth Pgm

 Itération
Invariant
Définition 2.6
Un invariant (ou relation invariante) d’une boucle,

2 while C do S

où C est la condition de la boucle, est une assertion I telle que

{I ∧ C} S {I} est vrai

La plus forte postcondition d’une boucle


Définition 2.7
Soit une instruction S et C et P des propositions sur les variables en S, alors

sp( while C do S , P ) = I ∧ ¬C


• ¬C est la condition d’arrêt ;
• et I est l’invariant de boucle tel que P ⇒ I (invariant vrai en entrée dans la boucle)

Preuve de terminaison
Définition 2.8
Prouver la terminaisond’une itération revient à trouver une fonction
 {f (~x) = f0 }S{f (~x) < f0 }

f : ~x 7→ N telle que :
 {f (~x) = 0} ⇒ ¬C

Méthode

1. Traiter la séquence d’initialisation


(a) P 0 : Précondition de départ (éventuellement d’une séquence précédente)

(b) Traiter la séquence et déterminer la plus forte postcondition juste avant la boucle
Q0 = sp(S1 ..Si , P 0 ) ≡ { P 0 ∧ Sinit }
2. Déterminer l’invariant de la boucle
(a) Trouver le plus fort Inv (intuitivement)
?
(b) Contrôle 1 (Après) : { Inv ∧ ¬C ∧ Scloture } ⇒ P ost. Sinon : renforcer Inv.
?
(c) Contrôle 2 (Avant) : Q0 ⇒ Inv
?
(d) Contrôle 3 (Pendant) : { Inv ∧ C ∧ Sboucle } ⇒ Inv
3. Déterminer la plus forte postcondition de la boucle (invariant et condition d’arrêt) :

sp(while C do S , P ) = Inv ∧ ¬C
4. Prouver la terminaison de la boucle
(a) Déterminer les paramètres de variation ~x et leur domaine de variation
(b) Déterminer la fonction f (~
x)
?
(c) Prouver la décroissance, soit { f0 ∧ Sboucle } ⇒ f1 < f0
?
(d) Prouver l’arrêt, soit { f (~
x) = 0 } ⇒ ¬C

Exemples : voir [Des11, § 3.2.7]

32 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution33

2.1.4 Preuves par la méthode régressive "wp"


 L’affectation à une variable simple

Définition 2.9

S = (x := E) ⇒ wp(x := E , Q) = Q[x/E] ∧ pre(E) 2


Noter : le calcul de la plus faible précondition d’une affectation ne nécessite pas l’introduction de variables
intermédiaires (comme pour le calcul de la sp).
Exemples : voir [Des11, § 3.4.1]

 L’affectation dans un tableau


Voir [Sch12, slides 36,37] et [Des11, § 3.4.5]

 Séquence

Définition 2.10

wp(S1 ; S2 ; · · · ; Sn , Q) = A0

An = Q

Ai = wp(Si , Ai+1 ) ∀i : 0 ≤ i < n

Exemples : voir [Des11, § 3.4.2]

 Sélection

Définition 2.11

 C ∧ wp(S1 , Q)


wp(if C then S1 else S2 , Q) = ∨

 ¬C ∧ wp(S , Q)

2

Exemples : voir [Des11, § 3.4.3]

 Itération

Définition 2.12

wp( while C do Sboucle , Q) = Inv


où I est un invariant de la boucle tel que
• ¬C ∧ Inv ⇒ Q ;
• { Inv ∧ C ∧ Sboucle } ⇒ Inv

Voir aussi [Sch12, slides 38,39] sur boucle for et boucle while
Exemples : voir [Des11, § 3.4.4]

31 décembre 2012 www.madit.be 33


342.1 Preuves de programmes BIHD3 - Meth Pgm

2.1.5 Preuve d’appel de procédures, de fonctions


Appel de procédure

{ Inv ∧ pre[~ x] ∧ pre(~


a/~ a) ∧ V p(~
a) < v0 } p(~
a) { Inv ∧ post[~ x] }
a/~

2 Appel de fonction

a)) ∧ V p(~
pre(f (~ a) < v0 ⇒ post[f (~
a)/f, ~
a/~
x]

~a arguments effectifs
~x paramètres formels
Inv invariant de contexte. Il ne contient pas de variables modifiées par p
V p le variant de chaque appel récursif doit être plus petit doit être plus petit que chaque sous-programme q
dans lequel il se trouve. v0 est la valeur initiale de V q(~x).

Ces axiomes peuvent être utilisés n’importe où dans une preuve.

34 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution35

2.2 Temps d’exécution


2.2.1 Ordre de grandeur
• Le temps d’exécution dépend de la taille des données.
Soit n cette taille, on veut une expression TP (n) du temps d’exécution du programme P .
• Le temps d’exécution varie d’un facteur plus ou moins constant d’un ordinateur à l’autre.
2
• La temps d’exécution pour les petites données est négligeable.
On calcule seulement l’ordre de grandeur O de T (n), à une constante près,
et pour des données assez grandes (asymptotique).

O(T (n)) ≤ O(f (n)) ⇔ ∃c, n0 > 0 : ∀n > n0 : T (n) ≤ c ∗ f (n)

La notation officielle est f (n) = Θ(g(n))

 Règles de calcul

O(f (n)) = O(g(n))



O(f (n)) ≤ O(g(n)) ∧ O(f (n)) ≥ O(g(n))

Soit la variable n ≥ 0, les constantes k, l ≥ 0 et f (n) ≥ 0 :

O(1) + O(1) = O(1)


O(1) + O(n) = O(n)
O(n) + O(n) = O(n)
k ∗ O(1) = O(1)
n ∗ O(1) = O(n)
O(k ∗ f (n)) = O(f (n))
O(f (n) + g(n)) = O(f (n)) si O(g(n)) ≤ O(f (n))
k l
O(f (n) ) ≤ O(f (n) ) si 0 ≤ k ≤ l
O(nl ) ≤ O(k n ) si k > 1
O(ln ) ≤ O(k n ) si k > l
O(f (n)) ∗ O(g(n)) = O(f (n) ∗ g(n))
O(max(f (n), g(n))) = O(f (n)) si O(g(n)) ≤ O(f (n))

31 décembre 2012 www.madit.be 35


362.2 Temps d’exécution BIHD3 - Meth Pgm

2.2.2 Temps d’exécution des algorithmes itératifs


 Actions simples
Pour les affectations, comparaisons, . . ., TE n’est évalué qu’en cas d’appel de fonction

2 Tx:=E (n) = O(1) + TE (n)

 Séquence
Additionner les temps de chaque instruction

TS1 ;S2 (n) = TS1 (n) + TS2 (n)

 Sélection
Additionner le temps du test + max(’then’,’else’)

Tif C then S1 else S2 (n) ≤ TC (n) + max { TS1 (n) + TS2 (n) }

 Boucle for
Calculer les bornes l et h puis h − l + 1 fois le test, le corps et l’incrément

Tfor i=l...h do S = Tl + Th + (h − l + 1) ∗ (Tl≤h + TS + O(1))

En pratique, on a souvent
Tfor i=l...h do S = (h − l + 1) ∗ TS

 Boucle while
Trouver le variant V qui diminue à chaque passage dans la boucle. Sa valeur initiale est une borne supérieure du
nombre d’itérations

Twhile C do S ≤ (V0 + 1) ∗ TC + V0 ∗ TS

La formule devient une égalité si le variant diminue de 1 à chaque passage, et qu’on sort de la boucle quand le
variant vaut 0.
En pratique on a souvent TC ≤ TS

Twhile C do S ≤ V0 ∗ TS

 Appel de méthode
Le temps d’évaluer un appel de sous-programme (procédure ou fonction) est le temps d’évaluer ses arguments,
d’appeler le sous-programme, d’exécuter le corps du sous-programme.
Vf est le variant de f , une fonction des paramètres de f qui mesure la complexité.

m
X
Tf (a1 ,...,am ) = Tai + O(1) + Tf (Vf (~
a))
i=1

36 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution37

2.2.3 Temps d’exécution des algorithmes récursifs


 Règles
Attention O ne s’applique qu’à une fonction déjà définie. Pour les appels récursifs, il ne faut pas l’employer.
Ne pas supprimer les constantes !
Exemple 2.2.1
2
T (n) = T (n − 1) + k ∗ T (0) a comme solution T (n) = k ∗ (n − 1), soit un temps linéaire
Exemple 2.2.2
T (n) = 2 ∗ T (n − 1) a comme solution T (n) = 2n ∗ k, soit un temps exponentiel !
Voyons quelques itérations

n T (n)
1 2k
2 4k
3 6k
4 8k
... ...
n 2n ∗ k

Cas de base : T (0) = k


0
Hypothèse d’induction : ∀n0 < n : T (n0 ) = 2n ∗ k
Pour n : T (n) = 2 ∗ T (n − 1) = 2 ∗ 2n−1 ∗ k = 2n−1+1 ∗ k = 2n ∗ k

 Équations récurrentes

Établir une équation récursive donnant T (n) en fonction soit de T (n − 1) soit de T (n/d).
c est le nombre d’appels récursifs.


c=1 ⇒ T (n) = O(nk+1 )
T (n) = c ∗ T (n − 1) + a.nk 
c>1 ⇒ T (n) = O(cn )

c > dk ⇒ T (n) = O(nlogd c )
n
T (n) = c ∗ T ( ) + a.nk

 c = dk ⇒ T (n) = O(nk log n)
d 
c < dk ⇒ T (n) = O(nk )

2.3 Espace en mémoire


Comme pour le temps, on travaille en ordre de grandeur. L’espace dépend du type de chaque variable :

• array : espace proportionnel au produit de ses tailles ;


• integer, real, boolean, record : espace constant ;
• file : calculer le nombre d’éléments ;
• set : dépend de l’implémentation, voir chapitre 4 ;

Pour les sous-programmes récursifs, on crée une copie des paramètres et variables locales à chaque appel récursif.
Il faut donc multiplier l’espace de ceux-ci par la profondeur d’appel, qui est bornée par la valeur initiale du variant.

31 décembre 2012 www.madit.be 37


382.4 Exemples BIHD3 - Meth Pgm

2.4 Exemples
2.4.1 Primalité
 Code Pascal
2 function estPremier(n: integer) : boolean ;
{ Pré : n > 0 }
{ Post : premier = exists i : i divise n et 1 < i < n }
var
i : integer ;
p : boolean ; { premier }
begin
i := 2 ;
p := true ;
while ((sqr(i) <= n) and p) do
{ inv : p = forall j, 1 < j < i : j ne divise pas n }
{ variant: floor(sqrt(n)) - i }
begin
p := (n mod i <> 0) ;
i := i + 1;
end ;
estPremier := p;
end ;

NB :
– dans une fonction en Pascal, ne jamais utiliser le nom de la fonction comme variable au sein de l’algorithme.
– sqr : carré ; sqrt : racine carrée ! !

 Temps
Temps d’exécution avant et après la boucle : affectations en O(1)
Invariant de la boucle I = { ∀j : 1 < j < i : n mod j 6= 0 }

Variant de la boucle V = b nc − i

Soit V0 avec i = 2 en entrée de la première itération : V = b nc − 2
Temps de la boucle :

Twhile C do S
≤ (V0 + 1) ∗ TC + V0 ∗ TS
√  √ 
≤ ( n − 2 + 1) ∗ O(1) + ( n − 2) ∗ O(1)
√  √ 
≤ ( n − 1) ∗ O(1) + ( n − 2) ∗ O(1)
√ √
≤ O( n) ∗ O(1) + O( n) ∗ O(1)

≤ O( n)


⇒ Tp (n) = O( n)

 Espace mémoire
Ep (n) = O(1)

38 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution39

2.4.2 Fibonacci
 Définition

 √ n  √ n
1+ 5
− 1−2 5
F ib(n) =
2

5 2
de manière récursive :

F ib(n) = F ib(n − 1) + F ib(n − 2)

 Preuve
En posant :
√ √
1 1+ 5 1− 5
a= √ R1 = R2 =
5 2 2

où R1 et R2 sont les racines de x2 = x + 1 (les nombres d’or).


On a
F ib(n) = a ∗ (R1n − R2n ) = a ∗ (R1n−1 − R2n−1 ) + a ∗ (R1n−2 − R2n−2 )
Car
aR1n = aR1n−2 R12 = aR1n−2 (R1 + 1) par eq du nb d’Or x2 = x + 1

 Code Pascal

function Fib(n: integer ) : integer ;


{ Pré : n>= 0 }
{ Variant = n }
begin
if n < 2
then Fib := n
else Fib := Fib(n-1) + Fib(n-2)
end ;

 Temps d’exécution
équation de récurrence

T (0) = T (1) = c
T (n) = T (n − 1) + T ((n − 2) + b(: n > 1)
= c ∗ F ib(n + 1) + b ∗ (F ib(n + 1) − 1)
= O(R1n )

Temps exponentiel . . . donc très lent !

 Espace mémoire
On a (espace var et paramètres * la profondeur des appel), soit 1 ∗ V0 = O(n)

31 décembre 2012 www.madit.be 39


402.4 Exemples BIHD3 - Meth Pgm

2.4.3 Calcul de l’index du max


 Version de base
Calculons le temps d’exécution de ce code :

2 function indexOfMax( var a : tableau ; bi, bs : integer ) : integer ;


{ Pré : bi <= bs ; a [bi..bs] initialisé }
{ Post : forall i dans bi..bs : a[indexOfMax] >= a[i] ; indexOfMax dans bi..bs }
{ Variant V = bs-bi+1 }
begin
if bi >= bs
then indexOfMax := bi
else if a[bi] < a[indexOfMax(a, bi+1, bs)]
then indexOfMax := indexOfMax(a, bi+1, bs)
else indexOfMax := bi
end;

Soit n la taille du tableau : n = bs − bi + 1


Pour n = 1 ⇒ bs = bi on exécute le then du premier test, soit en O(1)
Pour n > 1 ⇒ bs > bi on exécute le else, soit

T (n) = O(1) + T (n − 1) + max(T (n − 1) + O(1), O(1)) = 2T (n − 1) + O(1)

⇒ T (n) = O(2n )
Un temps exponentiel très inefficace !

 Une simple amélioration

function indexOfMax( var a : tableau ; bi, bs : integer ) : integer ;


{ Pré : bi <= bs ; a [bi..bs] initialisé }
{ Post : forall i dans bi..bs : a[indexOfMax] >= a[i] ; indexOfMax dans bi..bs }
{ Variant V = bs-bi+1 }
var
posReste : integer ;
begin
if bi >= bs
then indexOfMax := bi
else
begin
posReste := indexOfMax(a, bi+1, bs);
if a[bi] < a[posReste]
then indexOfMax := posReste
else indexOfMax := bi
end
end;

et nous avons pour n > 1 . . . un temps linéaire :

T (n) = O(1) + T (n − 1) + max(O(1), O(1)) = T (n − 1) + O(1)

⇒ T (n) = O(n)

40 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution41

2.4.4 Tri par sélection


Le code

procedure tri(var a: tableau) ;


var i: integer ;
begin
for i := n downto 1
do swap(a[i] ,a[indexOfMax(a,1,i)] ) ;
2
end ;

Temps de calcul, avec décroissance

T (n) = b.(n + (n − 1) + · · · + 2 + 1)
Xn
= b. i
i=1
n(n + 1)
= b.
2
= O(n2 )

Temps de calcul, avec la version basique de indexOfMax


Boucle for exécutée n fois
n
!
X
T (n) = n ∗ O(1) + i2
i=1
n
X
=n∗ i2
i=1
= n ∗ (2n+1 − 1)
= O(n.2n )

rappel de simplifications importantes

n
X n(n + 1)
i=
2
i=1

n
X
i2 = 2(n+1) − 1
i=1

31 décembre 2012 www.madit.be 41


422.4 Exemples BIHD3 - Meth Pgm

2.4.5 Tri par fusion


Supposons que fusion(a,bi,m,bs) s’exécute en temps O(bs − bi) = O(n).

procedure tri( var a :tableau ; bi,bs :integer ) ;


var
c : integer ; { centre du tableau }
2 begin
if bs > bi { O(1) }
then
begin
c := (bi+bs) div 2 ; { O(1) }
tri(a, bi, c) ; { T(n/2) }
tri(a, c+1 ,bs) ; { T(n/2) }
fusion(a,bi,c,bs) ; { O(n) }
end
end ;

Qui donne l’équation récurrente :


T (n) = 2T (n/2) + bn
⇒ T (n) = O(nlogn) < O(n2 )
Cet algorithme est beaucoup plus rapide !

42 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution43

2.5 Exercices
2.5.1 Exercices du TP2 - temps d’exécution
Exercice 2.1
Construire deux fonctions, une non-récursive (utilisant une boucle) et une récursive, correspondant à la
spécification suivante. Calculer leur complexité théorique.
2
En-tête : int sommeimpairs(int n)
Précondition : n est positif
Pn ou nul
Postcondition : res = i=1 (2i − 1) (res = la somme des n premiers naturels impairs)

Exercice 2.2
Construire deux fonctions, une non-récursive (utilisant une boucle) et une récursive, correspondant à la
spécification suivante. Calculer leur complexité théorique.
En-tête : int nbch(tab t, int n)
Précondition : 0 ≤ n ≤ 200 ∧ t[1..n] est initialisé
Postcondition : res est le nombre de chiffres dans t[1..n]
Remarque : Pour déterminer si un caractère c est un chiffre ou pas, on peut utiliser l’expression de test (c ≥ ’0’)
and (c ≤ ’9’).

Exercice 2.3
Pour chercher le minimum d’un tableau d’entiers, on peut procéder comme suit : on divise le tableau en deux
parties aussi égales que possible ; on calcule le minimum de chacun de ces deux "sous-tableaux" ; puis on prend
le plus petit de ces minima.
Construire une fonction récursive correspondant à cette méthode ; calculer ensuite sa complexité.
En-tête : int mintab(tab t, int bi,int bs)
Précondition : 1 ≤ bi ≤ bs ≤ 200 ∧ t[bi..bs] est initialisé
Postcondition : mintab = mint[k] | bi ≤ k ≤ bs

Exercice 2.4
On peut calculer le plus grand commun diviseur (pgcd) de deux naturels strictement positifs a et b comme suit.
Si a = b, alors a est le pgcd. Sinon, il s’agit du pgcd de b et a - b (en supposant que a est la plus grande des
deux valeurs). Ainsi, pour calculer le pgcd des entiers 126 et 54, on se ramène à 54 et 126-54 = 72 ; puis à 54
et 72-54 = 18 ; puis à 18 et 54-18 = 36 ; puis à 18 et 36-18 = 18 : il s’agit donc de 18.
Construire une fonction récursive en appliquant ce principe.
En-tête : bool pgcd(int a,int b)
Précondition : a, b > 0
Postcondition : pgcd est le pgcd de a et b

Exercice 2.5
(Spécification)
Soient n un entier naturel, a[1..n], b[1..n], c[1..n] des tableaux d’entiers. Spécifiez les énoncés suivants :
• b est une copie triée de a
• b contient les plateaux de a par ordre de longueur décroissante. Un plateau est un intervalle où le tableau
contient des valeurs égales.
• b[i] contient le plus grand commun diviseur de a[1..i] ;

31 décembre 2012 www.madit.be 43


442.5 Exercices BIHD3 - Meth Pgm

Exercice 2.6
(Spécification)
Soient n, m des entiers strictement positifs, a[1..n], b[1..n], c[1..n] des tableaux d’entiers. Spécifiez les énoncés
suivants :

1. c est l’intersection de a et b, considérés comme des multi-ensembles et c est décroissant, m est la taille
de c. Exemple : si a = [1|3|2|2|1|1], b = [1|2|4|2|5|0], c = [2|2|1].
2 2. c contiendra le sous-tableau de a de somme maximale et m sa taille.
3. c contiendra le segment de a de somme maximale et m sa taille.
4. c contiendra le plus grand segment de a strictement croissant et m sa taille

Exercice 2.7
(Temps d’exécution)
Le Prof. Conquère propose de calculer la somme des éléments d’un tableau en utilisant "diviser en deux parties
égales". Il fait donc un appel récursif sur chaque moitié du tableau, puis somme les résultats.
1. Donnez le programme obtenu en suivant son idée.
2. Quel est son temps d’exécution ?

Exercice 2.8
(Temps de calcul)
Le programme ci-dessous est-il correct ? Donnez l’ordre de grandeur et son temps de calcul. Donnez un autre
algorithme plus efficace pour puissance, et l’ordre de grandeur de son temps de calcul.

1 float puissance(float r, int n) {


2 if (n==0)
3 return(1.0);
4 else
5 if ((n-2)==0)
6 return (puissance(r,n-1)*puissance(r,n-1)/puissance(n-2));
7 else
8 return (puissance(r,n-1)*r);
9 }

Exercice 2.9
(Temps de calcul : voyageur de commerce)
On reçoit un tableau D qui donne la distance du trajet pour aller d’un noeud à un autre. On veut construire un
algorithme qui donne le circuit le plus court qui passe une fois par tous les noeuds.
Donnez l’ordre de grandeur du temps de calcul du programme suivant, qui construit les circuits qui commencent
par visited, et garde la distance la plus courte :

1 void tours(list visited){


2 if(length(visited)==n){
3 keepmin(total(D,visited))
4 } else {
5 for(i=1; i<=n; i++){
6 if(!in(i,visited)){
7 tours(add(i,visited))
8 }
9 }
10 }
11 }

44 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution45

Exercice 2.10
(Temps de calcul)
Un plateau d’un tableau est un intervalle d’indices où la valeur du tableau est toujours la même. Le Prof.
Conquère propose de calculer la longueur du plus grand plateau d’un tableau en utilisant "diviser en un élément/le
reste". On risque de couper le bon plateau par cette division. Il propose donc l’algorithme : Tester si toutes les
valeurs du tableau sont les mêmes ; Si oui, le bon plateau est tout le tableau, on renvoie sa longueur u − l + 1 ;
Si non, faire des appels récursifs sur l..u − 1 et l + 1..u et renvoyer la meilleure longueur trouvée.
1. écrivez le programme obtenu en suivant son idée.
2
2. Est-il correct ?
3. Quel est son temps d’exécution ?
4. Donnez l’algorithme le plus rapide (en ordre de grandeur).

Exercice 2.11
(Temps de calcul)

1. Donnez l’algorithme de tri par fusion.


2. Donnez ses pré- et postconditions.
3. Donnez l’ordre de grandeur de son temps de calcul et de l’espace-mémoire nécessaires à son exécution.

Voir 6.2.3 page 120


Exercice 2.12
(Temps de calcul)
L’algorithme de tri (ascendant) à bulles est le suivant : on parcourt le tableau de gauche à droite, et si un
élément du tableau est plus grand que son successeur, on les permute. On recommence jusqu’à ce qu’aucune
permutation ne soit effectuée lors d’un parcours.

1. Cet algorithme est-il correct ? (Pour répondre à cette question, il faut spécifier le problème, préciser
l’algorithme, puis faire la preuve grâce à l’invariant et au variant)
2. Donnez l’ordre de grandeur de son temps de calcul.
3. Existe-t-il un autren algorithme plus efficace pour ce problème ? Lequel ?

Réponse : voir [Des11, § 6.6.3 page 129]

31 décembre 2012 www.madit.be 45


462.5 Exercices BIHD3 - Meth Pgm

2.5.2 Solutions du TP2


Voir fichier annexe

46 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution47

2.5.3 Notes du TP2

Méthodes de programmation – Partie 2 (IHDCB331)


hajer.ayed@fundp.ac.be - Octobre 2012

TP 2 : Notes sur la complexité des algorithmes de tri


2
1. Tri par fusion
a. Principe
 Diviser le tableau en deux sous-tableaux de taille égale
 Trier chacune des parties
 Fusionner
b. Solution récursive

void fusion(int tableau[],int deb1,int fin1,int fin2)


{
int *table1;
int deb2=fin1+1;
int compt1=deb1;
int compt2=deb2;
int i;

table1=malloc((fin1-deb1+1)*sizeof(int));

//on recopie les éléments du début du tableau


for(i=deb1;i<=fin1;i++)
{
table1[i-deb1]=tableau[i];
}

for(i=deb1;i<=fin2;i++)
{
if (compt1==deb2) //c'est que tous les éléments du premier tableau ont été utilisés
{
break; //tous les éléments ont donc été classés
}
else if (compt2==(fin2+1)) //c'est que tous les éléments du second tableau ont été
utilisés
{
tableau[i]=table1[compt1-deb1]; //on ajoute les éléments restants du premier
tableau
compt1++;
}
else if (table1[compt1-deb1]<tableau[compt2])
{
tableau[i]=table1[compt1-deb1]; //on ajoute un élément du premier tableau
compt1++;
}
else
{
tableau[i]=tableau[compt2]; //on ajoute un élément du second tableau
compt2++;
}
}
free(table1);
}

void tri_fusion_bis(int tableau[],int deb,int fin)


{

31 décembre 2012 www.madit.be 47


482.5 Exercices BIHD3 - Meth Pgm

Méthodes de programmation – Partie 2 (IHDCB331)


hajer.ayed@fundp.ac.be - Octobre 2012

if (deb!=fin)
{

2 int milieu=(fin+deb)/2;
tri_fusion_bis(tableau,deb,milieu); -> T(n/2)
tri_fusion_bis(tableau,milieu+1,fin); -> T(n/2)
fusion(tableau,deb,milieu,fin);-> O(n) (A démontrer)
}
}

void tri_fusion(int tableau[],int longueur)


{
if (longueur>0)
{
tri_fusion_bis(tableau,0,longueur-1);
}
}

c. Complexité
T(n) = O(1) ; si n = 1 (Appel à la fonction)
ème
2 . T(n/2) + O(n) = 2. T(n/2) + n ; si n > 1 -> O ( n .log(n) ) (4 ligne du tableau avec c = 2, d =2, b =1, k=1 )

2. Tri par sélection


a. Principe
 Chercher le plus grand élément (ou plus petit)
 Le mettre au début
b. Solution récursive
void tri_selection(int tab[],int longueur)
{
int maxi, i;
while(longueur>0)
{
maxi=0;
for(i=1;i<longueur;i++)
{
if(tab[i]>tab[maxi]) maxi=i;
}
permuter(tab,maxi,(longueur-1));
longueur--;
}
}

c. Complexité

Moyenne = pire= O(n) : Complexité quadratique

48 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 2. Preuves et temps d’exécution49

Méthodes de programmation – Partie 2 (IHDCB331)


hajer.ayed@fundp.ac.be - Octobre 2012

3. Tri par insertion


a.

b.
Principe
 Insérer un élément dans la partie déjà triée
Solution
2
void inserer(int element_a_inserer, int tab[], int taille_gauche)
{
int j;
for (j = taille_gauche; j > 0 && tab[j-1] > element_a_inserer; j--)
tab[j] = tab[j-1];
tab[j] = element_a_inserer;
}

void tri_insertion(int tab[], int taille)


{
int i;
for(i = 1; i < taille; ++i)
inserer(tab[i], tab, i);
}

c. Complexité
Meilleur cas : Chaque élément est inséré à la fin de la partie triée (tout le tableau est trié). -> O(n)
ème
Pire des cas : Le tableau est trié dans l’ordre inverse. C’est-à-dire que la i itrétion génère (i-1) comparaisons et
2
échanges. -> O(n )
2
Moyenne : -> O(n / 4)

31 décembre 2012 www.madit.be 49


502.5 Exercices BIHD3 - Meth Pgm

50 www.madit.be 31 décembre 2012


Deuxième partie

Représentation des données

51
3
Les types abstraits (TA)

Sommaire
3.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.2 Avantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.3 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.4 Donner les propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.5 Complétude . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.6 Quelques types abstraits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.7 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

53
543.1 Définition BIHD3 - Meth Pgm

3.1 Définition
Définition 3.1
Un type abstrait =
• un type
• des sous-programmes qui exécutent ce type
• des propriétés

Définition 3.2

3 Un type concret = un type défini par une combinaison de types de données (ex : structures en C)

 Exemple : TA Liste
Une liste contient une suite finie d’éléments : le premier, le deuxième, . . .
On se donne des fonctions :

• listeVide construit une liste vide


• cons(x,l) construit une nouvelle liste en ajoutant x devant l
• head(l) donne le premier élément de la liste
• valueAt(i,l) (ou nth(i,x) ) donne le i-ème élément de la liste
• tail(l) donne le reste de la liste (le deuxième, . . . )
• append (l1,l2) donne une liste formée de la concaténation de l1 suivi de l2.
• length(l) la longueur de la liste

3.2 Avantages
• Abstraction : on peut construire un programme utilisant le TA sans connaitre son implémentation
• Modifiabilité : on peut modifier l’implémentation du TA, pourvu que les propriétés restent vraies, SANS
devoir changer le programme utilisateur.
• Encapsulation : les données ne sont changées que par les sous-programmes de l’interface, ce qui garantit
un invariant des données
• Réutilisation : les TA sont employés dans un grand nombre de programmes.

3.3 Syntaxe
Voir [Sch12, slides 75-77]

54 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 3. Les types abstraits (TA)55

3.4 Donner les propriétés


• par modèles
• par axiomes

3.4.1 Par modèles


Modèles : VDM, Z, B

1. On donne une "implémentation" abstraite basée sur les ensembles


2. Les sous programmes sont spécifiés par pré/postconditions (sur l’implémentation abstraite).
3
 Raffinement
Pour prouver une implémentation concrète C, on définit un invariant de représentation (ou d’abstraction ou de
collage) R(a, c) entre le type abstrait A et le type concret C.

∀a ∈ A, ∃c ∈ C : R(a, c)

L’invariant de données décrit la partie des données concrètes utilisées

D(c) = ∃a : R(a, c)

Les preuves reposent sur les hypothèses fixées (dans l’exemple : jamais de boucle).
Pour cette partie du type concret, on a par définion la propriété inverse :

∀c ∈ D, ∃a ∈ A : R(a, c)

Si ce a est unique, on définit une fonction d’abstraction Inf o : D → A


et R(a, c) peut s’écrire comme : D(c) et a = Inf o(c)
Pour chaque fonction fA (a : A; b : B) : A implémentée par fC (c : C; b : B) : C, on a

• pre(fC (c, b)) = D(c) ∧ pre(fA (Inf o(c), b))


• post(fC (c, b)) = D(fC (c, b)) ∧ post(fA (Inf o(c), b))

En résumé : on suppose l’invariant de données avant chaque opération, mais on doit le prouver pour les résultats.

31 décembre 2012 www.madit.be 55


563.4 Donner les propriétés BIHD3 - Meth Pgm

 Exemple : TA listes défini par modèle


Les listes sont définies comme un ensemble de couples (indice dans la séquence, valeur) avec les propriétés :

1. il n’y a pas de valeur à l’indice 0 (on commence à 1)


2. il ne peut y avoir deux valeurs au même indice
3. les indices vont de 1 à n

par exemple la liste (5, 4, 3) est représentée par l’ensemble { (1, 5), (2, 4), (3, 3) }
Invariant de données :
3 Type liste = P(N × elem) tel que

@e : (0, e) ∈ l
@i, e1 , e2 : (i, e1 ) ∈ l ∧ (i, e2 ) ∈ l ∧ e1 6= e2
∀(i, e) ∈ l, i > 2 ⇒ ∃e2 : (i − 1, e2 ) ∈ l

Modèles :

1 function listeVide : liste


2 Post : listeVide = {}
3
4 function cons(x : elem; l : liste) :liste
5 Post : cons(x, l) = {(1, x)} union {(i + 1, y) tq (i, y) in l}
6
7 function head(l : liste) :elem
8 Pré : l != {} ou exists x : (1, x) in l)
9 Post : (1, x) in l then head = x
10
11 function tail(l : liste) : liste
12 Pré : l != {}
13 Post : tail = {(i, x) tq (i + 1, x) in l et i >= 1}
14
15 function null(l : liste) : boolean
16 Post : null = (l = {})
17
18 function append(l1, l2 : liste) : liste
19 Post : append = l1 union {(n + i, x) tq (i, x) in l2, n = |l1|}

NB : pas de pre = supposé toujours TRUE.

3.4.2 Par axiomes


On donne des propriétés en termes des sous-programmes déclarés dans l’interface.

 Fonctions
Pour les fonctions (sans effet de bord) on emploie la logique du premier ordre.
Exemple 3.4.1
∀x : integer ; A, B : ensemble : dans(x, union(A, B)) ⇔ (dans(x, A) ∨ dans(x, B))

 Procédures
Pour les procédures On ajoute la logique dynamique :
[Prog]Formule signifie : Après toute exécution de Prog, la formule est vraie.
Exemple 3.4.2
[ajouter(x,A)]dans(x,A)

56 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 3. Les types abstraits (TA)57

3.5 Complétude
Comment être sûr d’avoir donné assez d’axiomes ?
Lorsqu’il n’y a que des fonctions, 2 méthodes :

• Méthode des constructeurs


• Méthode des observateurs

3.5.1 Méthode des constructeurs


1. Choisir un sous-ensemble des fonctions qui permet de construire toutes les valeurs du TA. 3
2. Donner toutes les combinaisons de la forme n(c1(. . . ), c2(. . . )) = e où

• n est une fonction, mais pas un constructeur


• c1, c2 sont des constructeurs.
• e est une expression plus simple (pour un ordre bien fondé).

3. Puis les "équations entre constructeurs" de la forme c1(c2(. . . )) = e

 Exemple : Listes par la méthode des constructeurs


Constructeurs : {listeVide, cons } (ex : (1,2) = cons(1,cons(2,listeVide)) )
Axiomes par la méthode des constructeurs.

1 – head(listeVide) = indéfini
2 – head(cons(x,l)) = x
3 – tail(listeVide) = indéfini
4 – tail(cons(x,l)) = l
5 – null(listeVide) = true
6 – null(cons(x,l)) = false
7 – append(listeVide,l2) = l2
8 – append(cons(x,l),l2) = cons(x,append(l,l2))

3.5.2 Méthode des observateurs


1. Choisir un sous-ensemble de fonctions qui permet d’observer toute différence entre 2 valeurs.
2. Donner toutes les combinaisons de la forme o(n(x)) = e . . . o(x) . . . où
• o est un observateur.
• n est une fonction non observateur.
• e est une expression qui ne contient pas o(x).

 Exemple : Listes par la méthode des observateurs


Observateurs : {head, tail, null }

1 – head(listeVide) = indéfini
2 – tail(listeVide) = indéfini
3 – null(listeVide) = true
4 – head(cons(x,l)) = x
5 – tail(cons(x,l)) = l
6 – null(cons(x,l)) = false
7 – head(append(l1,l2))
8 = head(l1) si null(l1) = false.
9 = head(l2) si null(l1) = true.
10 – tail(append(l1,l2))
11 = append(tail(l1),l2) si null(l1) = false
12 = tail(l2) si null(l1) = true
13 – null(append(l1,l2)) = null(l1) and null(l2)

31 décembre 2012 www.madit.be 57


583.6 Quelques types abstraits BIHD3 - Meth Pgm

3.6 Quelques types abstraits


3.6.1 TA Pile (LIFO)
Une pile est une collection d’éléments récupérée dans l’ordre inverse de celui où on les a mises : en anglais "Last
In, First Out" (LIFO)

 Fonctions
• pileVide donne une pile vide
3 • empile (ou push ) ajoute un élément en sommet de pile
• dépile (ou pop ) retire le sommet de pile
• sommet (ou top ) renvoie le sommet de pile

 Axiomes
• sommet(pileVide) = indéfini.
• sommet(empile(x,l)) = x.
• dépile(pileVide) = indéfini.
• dépile(empile(x,l)) = l.

 Implémentation
Implémentation classique des piles

type pile = record


a: array [1..max] of elem;
p: 0..max
end

a[p] contient le sommet de pile


Cette implémentation convient mieux en procédural
En Pascal, on doit borner le tableau. Il faut donc ensuite démontrer que le tableau ne déborde pas.

58 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 3. Les types abstraits (TA)59

3.6.2 TA Ensemble fini


NB : ce type fait partie du Pascal, mais il est souvent inefficace.

type ens array [...] of elem ;

 Fonctions

function singleton (e:elem) : ens ; { [e] }


function ensVide : ens ; { [] }
function union (x,y: ens) : ens ; { x + y }
function
function
function
intersection (x,y: ens) : ens ; { x * y }
ajout (e: elem; x :ens) : ens ; { [e] + x }
moins (x,y :ens) : ens ; { x \ y }
3
function dans (e: elem; x :ens) : boolean ; { e in x }

 Axiomes
Constructeurs : {ensVide, ajout}.

– singleton(e) = ajout(e, ensVide).


– union(ensVide,y) = y
– union(ajout(e,x),y) = ajout(e,union(x,y))
– intersection(ensVide,y) = ensVide
– intersection(ajout(e,x),y)
= intersection(x,y) si dans(e,y) = false
= ajout(e,intersection(x,y)) si dans(e,y) = true
– dans(e,ensVide) = false.
– dans(e,ajout(f,x)) = (e=f) ou dans(e,x)
{ équations entre constructeurs }
– ajout(e,ajout(e,x)) = ajout(e,x) { pas de doublons }
– ajout(e,ajout(f,x)) = ajout(f,ajout(e,x)) { pas ordonné }

31 décembre 2012 www.madit.be 59


603.6 Quelques types abstraits BIHD3 - Meth Pgm

3.6.3 TA Dictionnaire
Ce TA permet de retrouver la "définition" d’un "mot", aussi appelé "clé".
On l’appelle aussi Map en Java 2.

 Fonctions

type dico
function ajout(d : def; m : mot; di : dico) : dico;
function dicoVide : dico;
function def_de(m : mot; di : dico) : def;
3 function défini(m : mot; di : dico)
function apres(di1,di2:dico) :dico;
: boolean ;
{superposition pour recherche dico multiples}

Toutes nos implémentation d’ensembles peuvent être adaptées pour le type dictionnaire.

 Axiomes par constructeurs

1 – défini(m, dicoVide) = false.


2 – défini(m, ajout(d, m, di)) = true.
3 – défini(m, ajout(d, n, di)) = défini(m, di) (pour n != m).
4 – def_de(m, dicoVide) = indéfini.
5 – def_de(m, ajout(d, m, di)) = d.
6 – def_de(m, ajout(d, n, di)) = def_de(m, di) (pour n != m).
7 – après(dicoVide, di2) = di2.
8 – après(ajout(d, m, di), di2) = ajout(d, m, après(di, di2)).
9 – ajout(d2, m, ajout(d1, m, di)) = ajout(d2, m, di).
10 – ajout(d2, m, ajout(d1, n, di)) = ajout(d1, n, ajout(d2, m ,di)) (pour n != m).

 Ensembles avec procédures

type ens = P(elem) ;

procedure insérer(e: elem; var x: ens) ;


{Post : x = x0 + [e]}

procedure supprimer(e: elem; var x: ens) ;


{Post : x = x0 - [e]}

60 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 3. Les types abstraits (TA)61

31 décembre 2012 www.madit.be 61


623.7 Exercices BIHD3 - Meth Pgm

3.7 Exercices
3.7.1 Exercices du TP4

1 Types Abstraits

3 1.1 Piles d’entiers (un exemple de type abstrait)


Une pile d’entiers est, intuitivement, une série d’entiers que l’on "empile". Une pile peut être vide ; on
peut ajouter un entier au sommet de la pile ou en retirer un du sommet de la pile (si elle n’est pas vide) ;
on peut vouloir tester si une pile est vide ou pas ; on peut vouloir lire l’entier au sommet de la pile (si elle
n’est pas vide) ; finalement, on peut vouloir connaître le nombre d’entiers qui se trouvent dans la pile. Ce
sont des opérations naturellement associées aux piles d’entiers. On peut décrire un tel type en donnant une
représentation mathématique "de haut niveau", qu’il faudra ensuite traduire en implémentation pratique.

Spécification des piles d’entiers par modèle mathématique (de haut niveau)

On représente les piles d’entiers par des tuples (couples, triplets, quadruplets, ...) d’entiers. Si P est
l’ensemble de ces objets, on a donc :
P = {<>} ∪ {< z >: z ∈ Z} ∪ {< z1 , z2 >: z1 , z2 ∈ Z} ∪ {< z1 , z2 , z3 >: z1 , z2 , z3 ∈ Z} ∪ . . .

Les opérations sur les piles sont les suivantes.

Opération Valeur Précondition


vide :→ P vide =<> −
ajoute : ZxP → P ajout(z, < z1 , . . . , zn >) =< z, z1 , . . . , zn > z initialisé
suite : P → P suite(< z1 , z2
, . . . , zn >) =< z2 , . . . , zn > la pile n’est pas vide
vrai si p =<>
estvide : P → Bool estvide(p) = −
f aux sinon
sommet : P → Z sommet(< z1 , . . . , zn >) = z1 la pile n’est pas vide
long : P → N long(p) = n, où p =< z1 , . . . , zn > −

Une autre manière de décrire un type abstrait consiste à citer les propriétés que les opérations doivent
vérifier, sans donner de représentation/modèle. Par exemple, si on applique l’opération "ajouter 3 au som-
met de la pile" puis l’opération "retirer le sommet de la pile", il faut retrouver la pile de départ.

Spécification des piles d’entiers par axiomes

On note P le type des piles d’entiers. Les opérations sur les piles sont les suivantes.

Opération Précondition Axiomes


vide :→ P − suite(ajoute(z, p)) = p
ajoute : ZxP → P z initialisé estvide(vide) = vrai
suite : P → P la pile n’est pas vide estvide(ajoute(z, p)) = f aux
estvide : P → Bool − sommet(ajoute(z, p)) = z
sommet : P → Z la pile n’est pas vide long(vide) = 0
long : P → N − long(ajoute(z, p)) = 1 + long(p)

62 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 3. Les types abstraits (TA)63

1.2 Spécification de types abstraits

Exercice 1 (Files d’entiers)


Les files d’entiers (queues en anglais) sont, comme les piles d’entiers, des "séquences" d’entiers. Mais, alors
qu’on ajoutait et retirait des valeurs au sommet des piles, dans le cas des files d’entiers, on ajoute des
valeurs à la fin et on en retire au début, à la manière d’une file d’attente où les nouveaux clients viennent
se rajouter à la fin et les clients du début de file partent peu à peu une fois qu’ils ont été servis. On parle de
structure LIFO (Last In, First Out : le dernier rentré sort le premier) dans le cas des piles et de structure
FIFO (First In, First Out : le premier arrivé/entré sort en premier) dans le cas des files.
Tous ces développements décrits dans le cours à propos des piles peuvent être réalisés dans le cas des
files : spécification d’un modèle de haut niveau spécification par axiomes, implémentations selon diverses
3
représentations (on pourra utiliser les mêmes représentations que pour les piles, ou en inventer d’autres).

Exercice 2 (Files avec priorité)


Une file avec priorité est une suite d’objets possédant une priorité. C’est par exemple une modélisation
possible pour traiter les avions qui arrivent à un aéroport, auxquels il faut attribuer une piste pour qu’ils
puissent se poser : on les traite normalement dans l’ordre d’arrivée, mais certains avions peuvent passer
"devant les autres" en cas d’urgence (problème technique, problème de santé, . . .).
Un modèle de haut niveau pour les files avec priorité utilise les entiers comme "cotes de priorité", avec 1
représentant la plus haute priorité et 2, 3, 4, . . . représentant des priorités moindres. Une file avec priorité
est alors une séquence < (p1 , v1 ), . . . , (pn , vn ) > où les pi sont des entiers (les priorités) et les vi sont des
valeurs (les noms des avions par exemple, ou quelque chose de plus complexe). Les opérations suivantes
sont nécessaires sur les files avec priorité : tester si une file est vide ou pas, rechercher le prochain couple
(pi , vi ) à traiter (c’est-à-dire le premier couple possédant la plus haute priorité, c’est-à-dire le plus petit
entier pi ), obtenir la file restante (sans le couple prioritaire), ajouter un valeur v à priorité k à (la fin d’)
une file, et déterminer la taille d’une file.
Construire un type abstrait pour ces files et l’implémenter en C (ici aussi, plusieurs représentations sont
possibles).

Exercice 3 (Nombres complexes)


Construire, puis implémenter en C, un type abstrait pour les nombres complexes. Ce type abstrait devra
comporter des opérations pour additionner, soustraire, multiplier et diviser deux nombres complexes,
pour calculer le module et le conjugué d’un nombre complexe, pour indiquer la partie réelle et la partie
imaginaire d’un nombre complexe, ainsi qu’un constructeur donnant, à partir de deux réels a et b, le
nombre complexe a + bi.

Exercice 4 (Fractions)
Faire de même pour les fractions. L’implémentation devra permettre d’additionner, soustraire, multiplier
et diviser des fractions, d’obtenir le numérateur et le dénominateur d’une fraction, de transformer un
entier en une fraction, ou un couple d’entiers (a, b) en la fraction a/b. On tentera, dans l’implémentation,
de toujours fournir des représentations où numérateur et dénominateur sont simplifiés.

Exercice 5 (Ensembles finis d’entiers)


On considère un type abstrait pour les ensembles finis d’entiers. Il faudrait évidemment pouvoir représenter
l’ensemble vide ; calculer l’union, l’intersection, et la différence de deux ensembles ; ajouter un entier à un
ensemble ; déterminer le cardinal (nombre d’objets) d’un ensemble ; tester si un entier donné appartient
à un ensemble ; et extraire un élément d’un ensemble (au hasard). Cette dernière opération devra donner
non seulement l’élément extrait mais aussi l’ensemble restant (c’est-à-dire l’ensemble de départ privé de
l’élément extrait).

Exercice 6 (Fonctions des entiers vers les entiers)


On considère maintenant les fonctions (partielles, c’est-à-dire pas forcément définies pour tous les entiers)
des entiers vers les entiers. Il faudrait pouvoir obtenir le domaine d’une fonction (l’ensemble des entiers pour
lesquels la fonction est définie), l’image d’une fonction (l’ensemble des entiers atteints par la fonction, c’est-
à-dire l’ensemble des "f(x)"), ainsi que la valeur d’une fonction en un entier donné. On devrait également
pouvoir créer une fonction définie en un seul entier, en donnant cet entier et l’image qui lui est associée ;
composer deux fonctions données ; modifier une fonction en un point (de son domaine ou non). Pour toutes
les opérations qui utilisent des ensembles, on utiliserait le type abstrait construit à l’exercice précédent.

31 décembre 2012 www.madit.be 63


643.7 Exercices BIHD3 - Meth Pgm

3.7.2 Solutions du TP4


voir fichier annexe.

64 www.madit.be 31 décembre 2012


4
Implémentation des ensembles

Sommaire
4.1 Tableau de Booléens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.2 Liste avec doublons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.3 Liste sans doubles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.4 Listes triées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.5 Tables de hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.6 Arbres binaires de recherche (ABR) . . . . . . . . . . . . . . . . . . . . . . . 76
4.7 Arbres rouges/noirs (ARN) . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.8 B-Arbres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.9 Exercices de TP - implémentation des ensembles . . . . . . . . . . . . . . . 98

65
664.1 Tableau de Booléens BIHD3 - Meth Pgm

4.1 Tableau de Booléens


 Définitions
Cette implémentation, aussi appelée vecteur de bits est définie comme :

type ens = packed array [elem] of boolean

NB : packed = moins de place en mémoire, chaque élément n’étant plus un mot complet (de 32 ou 64 bits)
Invariant de représentation :
e ∈ A ⇔ C[e] = true

Notes

• elem doit être un type discret.

4 • Pour les boucles for il faut disposer des bornes MIN et MAX.
• le résultat d’une fonction (ens ) doit être un type non structuré en Pascal pur, mais la plupart des Pascal
admettent cette extension.

4.1.1 Fonctions et procédures


type
elem = MIN..MAX ; { pour complexité: n = |elem| = MAX-MIN+1 }
ens = packed array [elem] of boolean ;

function dans(e: elem; x: ens) : boolean ; { O(1) }


begin
dans := x[e] ;
end ;

function ensVide : ens; { O(n) }


var
i : elem;
res : ens;
begin
for i := MIN to MAX do res[i] := false ; { = invariant de représentation }
ensVide := res;
end ;

function ajout(e: elem ; x: ens) : ens; { O(n) }


var
res: ens;
begin
res := x ; { copie dans var temp car impossible de modifier un arg en pascal }
res[e] := true ;
ajout := res;
end ;

function singleton(e: elem) : ens; { O(n) }


var
i : elem;
res : ens;
begin
for i := MIN to MAX do res[i] : = false ;
res[e] := true ;
singleton := res;
end ;

66 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles67

function union(x,y:ens) : ens; { O(n) }


var
i : elem;
res : ens;
begin
for i := MIN to MAX do res[i] := x[i] or y[i] ; { union = logical OR }
union := res;
end ;

function intersection(x,y:ens) : ens; { O(n) }


var
i : elem;
res : ens;
begin
for i := MIN to MAX do res[i] := x[i] and y[i] ; { intersection = logical AND }
intersection := res;
end ;

procedure insérer(e: elem; var x: ens) ; { O(1) }


begin
4
x[e] := true ;
end ;

procedure supprimer(e: elem; var x: ens) ; { O(1) }


begin
x[e] := false ;
end ;

31 décembre 2012 www.madit.be 67


684.2 Liste avec doublons BIHD3 - Meth Pgm

4.2 Liste avec doublons


4.2.1 Définition
Le problème des vecteurs de bits est que l’on obtient une complexité en O(|elem|) (nombre de valeurs possibles)
or on voudrait O(|x|) (nombre de valeurs utilisées).
On peut se servir d’un TA liste ( page 56) pour implémenter les ensembles car ils ont des constructeurs similaires.

ajout → cons
ensV ide → listeV ide

4
4.2.2 Fonctions et procédures

 dans

L’implémentation de dans se déduit de ses équations ; Deux cas de base et un cas récursif.

function dans(e: elem; x: ens) : boolean ;


begin
if null(x)
then dans := false { CB 1 }
else if head(x) = e
then dans := true { CB 2 }
else dans := dans(e, tail(x)) ; { CR }
end ;

 union

Ses équations sont :

union(ajout(e, x), y) = ajout(e, union(x, y))


union(cons(e, x), y) = cons(e, union(x, y))

On remarque qu’elles sont exactement celles de append (voir  page 57).

 intersection

function intersection(x,y: ens) : ens;


var
e : elem;
begin
if null(x) OR null(y)
then
intersection := listeVide
else
begin
e := head(x) ;
if dans(e,y)
then
intersection := ajout(e, intersection(tail(x) ,y)
else
intersection := intersection(tail(x) ,y) ;
end
end ;

68 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles69

4.2.3 Temps de calcul


Si les listes sont implémentées par des listes chaînées, on a :

f Tf
listeVide O(1)
cons O(1)
append O(length(x))
head O(1)
tail O(1)

Les fonctions sur les ensembles prennent donc :

f Tf
ensVide
ajout
O(1)
O(1) 4
dans O(length(x))
singleton O(1)
union O(length(x))
intersection O(length(x) ∗ length(y)) = O(n2 )

4.2.4 Place en mémoire


De l’ordre de la longueur de la liste l.
Problème : Pour un ensemble x , la longueur de la liste n’est pas bornée ! Les doublons créent une augmentation
incontrôlée de la taille.
Exemple 4.2.1
l’ensemble x = { 1, 2 } peut être représenté par la liste [1, 2, 1, 2, 1, 2, 1, 2].

31 décembre 2012 www.madit.be 69


704.3 Liste sans doubles BIHD3 - Meth Pgm

4.3 Liste sans doubles


4.3.1 Définition
Pour que
length(x) ≤ O(|x|)

on pose comme invariant de représentation "pas de doubles" :

∀i, j ∈ N0 : i, j ≤ length(x) ∧ i 6= j ⇒ valueAt(i, x) 6= valueAt(j, x)

4.3.2 Fonctions
Il faut garantir que chaque fonction respecte bien le nouvel invariant :
— ensVide : OK.
4 — singleton : OK.
– ajout : teste qu’on ne crée pas de double

function ajout(e: elem; x: ens) : ens;


begin
if dans(e,x)
then ajout := x
else ajout := cons(e,x) ;
end ;

Les autres fonctions se déduisent des axiomes.

4.3.3 Temps d’exécution (Liste chaînée)


f Tf
ensVide O(1)
ajout O(length(x))
dans O(length(x))
singleton O(1)
union O(length(x) ∗ length(y)) = O(n2 )
intersection O(length(x) ∗ length(y)) = O(n2 )

Apparemment, le temps augmente, mais pour les listes avec doubles, l n’est PAS bornée !

4.3.4 Place en mémoire


De l’ordre du nombre d’éléments dans l’ensemble.

70 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles71

4.4 Listes triées


4.4.1 Définition
But : diminuer le temps de union , intersection.
On pose comme invariant de représentation "strictement trié" (= pas aussi de double) :

∀i, j ∈ N : 0 < i < j ≤ length(x) ∧ i 6= j ⇒ valueAt(i, x) < valueAt(j, x)

4.4.2 Fonctions
Il faut garantir que chaque fonction respecte bien le nouvel invariant :
— ensVide : OK.
— singleton : OK. 4
– ajout : insérer à la bonne place (en récursif)

function ajout(e: elem; x: ens) : ens;


begin
if null(x)
then ajout := cons(e, x)
else if e < head(x)
then ajout := cons(e, x)
else if e = head(x)
then ajout := x
else ajout := cons(head(x), ajout(e, tail(x)) ;
end ;

– dans : ok mais peut aller plus vite

function dans(e: elem; x: ens) : boolean ;


begin
if null(x)
then dans := false
else if e < head(x)
then dans := false
else if e = head(x)
then dans := true
else dans := dans(e, tail(x) ) ;
end ;

– union : ok mais peut aller plus vite

function union(x,y: ens) : ens;


begin
if null(x)
then union := y
else if null(y)
then union:= x
else if head(x) < head(y)
then union := cons(head(x),union(tail(x), y) )
else if head(x) = head(y)
then union := cons(head(x), union(tail(x), tail(y) ) )
else union := cons(head(y), union(x, tail(y) ) ) ;
end ;

31 décembre 2012 www.madit.be 71


724.4 Listes triées BIHD3 - Meth Pgm

– intersection : ok mais peut aller plus vite

function intersection(x,y: ens) : ens;


begin
if null(x)
then intersection := listeVide
else if null(y)
then intersection := listeVide
else if head(x) < head(y)
then intersection := intersection(tail(x) ,y)
else if head(x) = head(y)
then intersection := cons(head(x), intersection(tail(x) ,tail(y) ) )
else intersection := intersection(x, tail(y) ) ;
end ;

4.4.3 Temps d’exécution

4 f
ensVide
Tf
O(1)
ajout O(length(x))
dans O(length(x))
singleton O(1)
union O(length(x)+length(y)) = O(n)
intersection O(length(x)+length(y)) = O(n)

4.4.4 Place en mémoire


La même : de l’ordre du nombre d’éléments.

72 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles73

4.5 Tables de hachage


4.5.1 Définition
Variante du vecteur de bits (Celui-ci utilise un tableau trop grand).
On diminue le type elem par une fonction qui le "compresse".

function h(e: elem) : indice

où elem est grand, mais indice est petit, par exemple 0..M par ex. en prenant mod (M + 1).
Problème : Lorsque deux éléments distincts ont le même indice, il y a COLLISION.
Solutions :

1. 4.5.2 : Représenter l’ensemble des éléments de même indice (p.ex. par une liste triée).
2. 4.5.3 : Représenter la liste dans la table : Adressage ouvert.
3. 4.5.4 : Éviter les collisions en agrandissant la table .
4
4.5.2 Implémentation par un ensemble des indices
type
indice = 0..M;
ens = array [indice] of ens2;
{ens2 = par ex. liste chaînée }

function dans(e: elem; x: ens) : boolean ;


begin
dans := dans2(e, x[h(e)])
end;

procedure inserer(e: elem; var x: ens) ;


begin
inserer2(e, x[h(e)])
end;

procedure supprimer(e: elem; var x: ens) ;


begin
supprimer2(e,x[h(e)])
end;

function ensVide : ens;


var
i : indice;
res : ens; { rappel : une fct ne peut pas manipuler son arg de retour }
begin
for i := 0 to M do res[i] := ensVide2; { respect invariant: tous les ptr nil }
ensVide := res;
end;

function ajout(e: elem; x: ens) : ens;


var
i : indice;
res : ens;
begin
res := x ; { ! peut créer du partage }
res[h(e)] := ajout2(e, x[h(e)]) ;
ajout := res;
end;

31 décembre 2012 www.madit.be 73


744.5 Tables de hachage BIHD3 - Meth Pgm

function union(x,y: ens) : ens;


var
i : indice;
res : ens;
begin
for i := 0 to M do res[i] : = union2(x[i] ,y[i]) ;
union := res;
end;

 Temps d’exécution

Ops Temps au pire : O(. . . ) Temps moyen : O(. . . )


insérer Tinserer2 (|x|) 1
dans Tdans2 (|x|) 1
supprimer Tsupprimer2 (|x|) 1
fusionner M ∗ Tf usionner2 (|x|) M
4 union
intersection
M ∗ Tunion2 (|x|)
M ∗ Tintersection2 (|x|)
M
M
ensVide M ∗ TensV ide2 (|x|) M

h est supposée en O(1)


Le hachage est plus lent (worst case), mais la probabilité d’une collision doit être réduite (temps moyen)

 Place en mémoire
O(M ) = O(|x|) si M est bien choisi

 Conclusion
Améliore le temps d’exécution de dans, insérer, supprimer.
Dégrade le temps d’exécution de ensVide, ajout.
Pourtant souvent employé car ensVide et ajout sont rares.

74 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles75

4.5.3 Implémentation par une table d’adressage ouvert


On met les éléments en collision à d’autres places libres du tableau (pas de pointeurs, donc une économie de place
en mémoire).
On pourrait les mettre à la place suivante (si elle est libre) mais risque de collision en chaîne ou cluster.
Double hachage : h1 (donne l’indice), h2 (donne l’incrément) telle que h2(e) est premier par rapport à M + 1
(ex. : M + 1 = 2n , h2 = 2 ∗ h1 + 1 est impair).
On suppose une valeur spéciale de elem : vide .
La suite des indices explorés est :

function h(e: elem; i: indice) : indice;


{ h(e,0),..., h(e,M) est une permutation de 0..M }
begin
h := (h1(e) + i * h2(e)) mod M+1
end ;

Hypothèse : supprimer n’est pas dans le TA (sinon on crée des "trous") 4


Invariant :

∃j : e = X[j] ⇒ (∀i ∈ [0..k[) ∨ (h(e, k) = j, X[h(e, i)] 6= vide)

Pour insérer, on parcourt la séquence d’indices jusqu’à trouver une case vide.
Pour rechercher, on parcourt la liste d’indices jusqu’à trouver la clé ou tomber sur une case vide.

 Temps d’exécution
Dépend du risque de collision en chaîne. Si négligeable, même temps moyen.

 Place en mémoire
L’économie ne change pas l’ordre de grandeur : O(M ) = O(|x|)

4.5.4 Implémentation en agrandissant la table de hachage


Chaque fois que nous avons une collision, on double la taille [1] du tableau ! Ce n’est pas possible en Pascal car la
taille des tableaux est fixe mais possible en C/C++. C’est une procédure lourde mais en doublant cette taille (1)
on assure que le cas ne se produira pas souvent et (2) on garde notre fonction de hachage.
Nécessite une série de fonctions de hachage. On prend souvent :
h(e, n) ∈ [0..2n − 1]
On suppose que pour n grand, h ne donne plus de collisions.

type ens = record


n : integer ; { >= n0 > 0 }
t : array [ 0..2^n -1] of elem { pas du PASCAL ! }
end ;

procedure inserer(e: elem; var x: ens) ;


begin
j := h(e, x.n) ;
if x.t[j] = vide
then x.t[j] := e
else if x.t[j] <> e
then begin
dedoubler(x) ;
insérer(e,x)
end
end;

[1]. "bonne pratique" : toujours définir la taille des tableau en puissance de 2 (-1)

31 décembre 2012 www.madit.be 75


764.6 Arbres binaires de recherche (ABR) BIHD3 - Meth Pgm

4.6 Arbres binaires de recherche (ABR)


4.6.1 Définition d’un AB
Un arbre binaire est
– soit un arbre vide,
– soit constitué d’une valeur, et de deux sous-arbres : le fils de gauche et le fils de droite.

type arbre = arbreVide | cons(e: elem; g,d : arbre)

e est appelé la valeur de la racine de l’arbre. g et d , les fils gauche et droit.

tree tree

4 e

g d

arbre {binaire} = ^n\oe ud;

n\oe ud = record
e : elem;
g : arbre;
d : arbre;
end ;
{ inv : x.g < x , x.d < x pour "<" bien fondé càd pas de cycles }

4.6.2 Définition d’un ABR


Un arbre binaire est trié (ou de recherche), si les valeurs du sous-arbre de gauche sont plus petites que la racine,
et celles du sous-arbre de droite sont plus grandes, et récursivement.
La recherche (dans ) y est plus efficace.

type abr { arbre binaire de recherche } = arbre


{ inv : si x <> nil :
forall e1 dans Info(x^.g) and forall e2 dans Info(x^.d) : e1 < x^.e < e2 ;
x^.g et x^.d sont des abr } ;

abr

5
<5 >5

3 7

6 8

NB : les pointeurs NIL ne sont généralement pas représentés (par simplification)

76 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles77

4.6.3 Fonctions et Procédures


 Construire

function cons(e: elem; g,d : arbre) : arbre;


var
p : arbre;
begin
new(p) ;
p^.e := e;
p^.g := g;
p^.d := d;
cons := p;
end ;

function ensVide : abr;


begin
ensVide := nil
end ; 4
function singleton(e: elem) :abr;
begin
singleton := cons(e,ensVide,ensVide)
end ;

 Recherche

function dans(e: elem; x: abr) : boolean ;


begin
if x = nil then dans := false
else if e < x^.e then dans := dans(e, x^.g)
else if e > x^.e then dans := dans(e, x^.d)
else dans := true
end ;

Avec élimination de la récursivité terminale

function dans2(e: elem; x: abr) : boolean ;


var trouve : boolean ;
begin
trouve := false ;
while (x <> nil) and not trouve do
begin
if e < x^.e then x := x^.g
else if e > x^.e then x:= x^.d
else trouve := true
end ;
dans2:= trouve
end ;

 Insertion
Avec une fonction

function ajout(e: elem; x: abr) :abr;


begin
if x = nil
then ajout := singleton(e)
else if e < x^.e
then ajout := cons(x^.e, ajout(e, x^.g) , x^.d)
else if e > x^.e { pas de doublons}
then ajout := cons(x^.e, x^.g, ajout(e, x^.d) )
else ajout := x
end ;

31 décembre 2012 www.madit.be 77


784.6 Arbres binaires de recherche (ABR) BIHD3 - Meth Pgm

Avec une procédure

procedure inserer(e: elem; var a: abr) ;


begin
if a = nil then a := singleton(e)
else if e < a^.e then inserer(e, a^.g)
else if e > a^.e then inserer(e, a^.d)
{ if e = a^.e : il s’y trouve déjà , ne pas insérer de doublon }
end ;

 Supprimer
Cas 1. Si le nœud à supprimer n’a pas de fils gauche, on peut l’enlever

3 3
4 1 5 1 5

2 4 6 4 6

Cas 2. Sinon, il nous manque une valeur pour séparer les deux sous-arbres : on remonte le max du fils gauche. On
pourrait mettre un sous-arbre sous l’autre, mais ça déséquilibrerait l’arbre

3 2

1 5 1 5

2 4 6 4 6

procedure supprimer(var a: abr ; e: elem) ;


begin
if a <> nil
then if e < a^.e
then supprimer(e,a^.g)
else if e > a^.e
then supprimer(e, a^.d)
else SupprimerRacine(a)
end ;

procedure SupprimerMax (var a: abr; var m: elem) ;


(* Pré : a <> nil *)
(* Post : m = Max(Info(a0)) et Info(a) = Info(a0)\{m} *)
var
p : abr;
begin
if a^.d = nil
then begin
m := a^.e ;
p := a ;
a := a^.g ;
dispose(p) ;
end
else SupprimerMax(a^.d, m)
end ;

78 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles79

procedure SupprimerRacine (var a: abr) ;


(* Pré : a <> nil *)
(* Post : Info(a) = Info(a0)\{a0^.e} *)
var
p : abr;
begin
if a^.g = nil
then begin
p := a ;
a := a^.d ;
dispose(p) ;
end
else SupprimerMax(a^.g, a^.e)
end ;

Exemple 4.6.1 Exécution de supprimer 3

supprimer(trois,3); { ptr sur n\oe ud = valeur alpha }


-> a = trois ; e = 3
4
-> e = trois^.e -> supprimerRacine(trois)

supprimerRacine
-> trois^.g <> NIL
-> trois^.d = un
-> supprimerMax(un, trois^.e)

supprimerMax
-> un^.d <> NIL
-> un^.d = deux
-> supprimerMax(deux, trois^.e)

supprimerMax
-> deux^.d = NIL
-> trois^.e = m = deux^.e = 2 { valeur copiée par Ref }
-> p = deux
-> deux = deux^.g = NIL { modification du pointeur }
-> dispose p { ancien n\oe ud 2 libéré }

4.6.4 Temps d’exécution


Le temps est de l’ordre de la hauteur, soit en log(n), mais au pire elle peut être de l’ordre du nombre d’éléments
n car l’abre peut être complètement déséquilibré.

Ops Temps au pire : O(. . . ) Temps moyen : O(. . . )


dans n log(n)
insérer n log(n)
ajout n log(n)
supprimer n log(n)
ensVide 1 1

Donc, il faut équilibrer l’arbre !

31 décembre 2012 www.madit.be 79


804.7 Arbres rouges/noirs (ARN) BIHD3 - Meth Pgm

4.7 Arbres rouges/noirs (ARN)

4.7.1 Définition

Un arbre rouge et noir est un arbre binaire de recherche où on ajoute une "couleur" binaire à chaque nœud, et
surtout un invariant de représentation pour que la hauteur soit logarithmique :

• le nombre de nœuds noirs est le même sur toutes les branches ;


• le fils d’un nœud rouge est noir ;

Les nœuds rouges donnent un peu de souplesse à la contrainte d’équilibre. Si on oublie ces nœuds rouges, on
obtient un arbre binaire parfaitement équilibré.

4 La racine est soit noire, soit rouge. Mais on peut supposer que cette racine est toujours noire car le changement
de couleur ne change pas les propriétés.

En contrôlant cette information de couleur dans chaque nœud, on garantit qu’aucun chemin ne peut être deux fois
plus long que n’importe quel autre chemin, de sorte que l’arbre reste relativement équilibré. Même si l’équilibrage
n’est pas parfait, on montre que toutes les opérations de recherche, d’insertion et de suppression sont en O(log(n)).

17

7 23

3 11 21 31

18 22 29

19

Certaines sources nomment "nœuds externes" les pointeurs NULL vers des sous arbres vides. Les nœuds internes
sont donc ceux qui possèdent une valeur. Un ARN ayant n nœuds internes possède n + 1 nœuds externes. La suite
ne fait mention que des nœuds internes en omettant le qualificatif.

 Déclaration Pascal

arn = ^node;
node = record
e : elem ;
g : arn ;
d : arn ;
red : boolean
end ;

80 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles81

4.7.2 Invariants de données


Ü l’arbre est acyclique :
h(a) > h(ab.g) ∧ h(a) > h(ab.d)

Ü l’arbre est trié :

a <> nil ⇒ { ∀e1 ∈ inf o(ab.g) ⇒ e1 < ab.e } ∧ { ∀e2 ∈ inf o(ab.d) ⇒ e2 > ab.e }

Ü le fils d’un rouge est noir :

red(a) ⇒ ¬red(ab.g) ∧ ¬red(ab.d)

Ü toute branche contient le même nombre de nœuds noirs :

hn(ab.g) = hn(ab.d)

Ü l’invariant est récursif : ab.g et ab.d sont des ARN


4
a : un arbre rouge-noir (ARN) h(a) : hauteur de l’arbre
hn(a) : hauteur "noire" de l’arbre

4.7.3 Hauteur d’un ARN


Les ARN sont relativement bien équilibrés. La hauteur d’un ARN est de l’ordre de grandeur de log(n) où n est le
nombre de nœuds.
En effet, la hauteur h d’un ARN ayant n nœuds vérifie l’inégalité

h(a) ≤ 2 log(n + 1)

Démonstration : voir [Cor04, lemme 13.1]

 La hauteur noire
Pour un ARN, on appelle hauteur noire le nombre hn(x) de nœuds noirs le long d’une branche de la racine à une
feuille.
La hauteur d’un ARN est donc au moins celle des nœuds noirs (sans nœuds rouges), soit de 2 fois la hauteur noire
si on compte les rouges et les noirs, +1 si la racine est rouge.

hn ≤ h ≤ 2hn + 1

function hn(a: arn) : integer ;


begin
if a=nil
then hn := 0
else if a^.red
then hn := hn(a^.d)
else hn := 1 + hn(a^.d)
end

31 décembre 2012 www.madit.be 81


824.7 Arbres rouges/noirs (ARN) BIHD3 - Meth Pgm

 Relation entre la hauteur et le nombre de nœuds


Dans un ARN de racine a, la moitié au moins des nœuds vers une feuille doivent être noirs.

h
hn ≥
2

Nous avons donc aussi

2hn − 1 ≤ n ≤ 2h − 1

La première partie est démontrée par récurrence (voir [Cor04, lemme 13.1]) :

4 2hn − 1 ≤ n

• Si h = 0, alors a est obligatoirement une feuille nulle. Les sous-arbres enracinés en a contiennent donc bien
2hn − 1 = 20 − 1 = 0 nœuds internes ;
• Si h > 0, alors chacun des fils d’un nœud interne x a une hauteur noire égale soit à hn si x est rouge, soit
à hn − 1 si x est noir.
Comme la hauteur des fils de x est inférieure à celle de x, on peut donc appliquer l’hypothèse d’induction
au sous arbre de x qui contient au moins (2hn−1 − 1) + (2hn−1 − 1) + 1 = 2hn − 1 nœuds internes. 

La deuxième partie . . . ? ? ?
n ≤ 2h − 1

[TODO]

 Ordre de grandeur
On démontre que
h = O(log n)

h/2 ≤ hn la hauteur noire est au moins la moitié de h


hn
∧ 2 −1≤n comme démontré ci-avant
h/2 hn
⇒ 2 −1≤2 −1≤n
h/2
⇒ 2 −1≤n par transitivité
⇒ 2h/2 ≤ n + 1 tout + 1
h/2
⇒ log(2 ) ≤ log(n + 1) tout log2
⇒ h/2 ≤ log(n + 1)
⇒ h ≤ 2 ∗ log(n + 1) tout ∗ 2
⇒ h = O(log n) ordre de grandeur, constantes négligées

82 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles83

4.7.4 Fonctions de base


Adapter les fonctions des arbres binaires triés pour qu’elles traitent les ARN

 Construire
Lors de cons d’un nœud x, il va recevoir la couleur (red = 0 ∨ 1), la précondition doit être plus forte :

• hn(xb.g) = hn(xb.d)
• hn(xb.red) = 0 ∨ { hn(xb.red) = 1 ∧ hn(xb.gb.red) = 0 ∧ hn(xb.db.red) = 0 }
• ∀y ∈ inf o(xb.g) ⇒ y < e
• ∀z ∈ inf o(xb.d) ⇒ z > e

function cons(e:elem ; g,d: arn ; red: boolean) : arn;


var
p: arn ; {rappel: une fct ne peut pas manipuler ses arguments} 4
begin
new(p) ;
p^.e := e ;
p^.g := g ;
p^.d := d ;
p^.red := red ;
cons := p
end ;

 Rechercher
Pas de changement, la recherche s’exécute en temps logarithmique.

function dans(e: elem ; x: arn) : boolean ;


begin
if x = nil then dans := false
else if e < x^.e then dans := dans(e, x^.g)
else if e > x^.e then dans := dans(e, x^.d)
else dans := true
end ;

31 décembre 2012 www.madit.be 83


844.7 Arbres rouges/noirs (ARN) BIHD3 - Meth Pgm

4.7.5 Rotations
Les rotations sont des modifications locales d’un arbre binaire. Elles consistent à échanger un nœud avec l’un de
ses fils. Dans la rotation droite, un nœud devient le fils droit du nœud qui était son fils gauche. Dans la rotation
gauche, un nœud devient le fils gauche du nœud qui était son fils droit. Les rotations gauche et droite sont inverses
l’une de l’autre. Elle sont illustrées à la figure ci-dessous (les triangles désignent des sous-arbres).

x x
Q P

P C A Q

4 A B B C

 Rotation gauche
Une rotation gauche prend un temps constant.
Elle raccourcit les branches de C et rallonge celles de A.
L’arbre reste trié mais pas toujours rouge-noir !

procedure RotationGauche(x: abr) ;


var
y : abr;
begin
y := x^.d; { <> n i l } {1}
echanger(x^.e, y^.e) ; {2}
x^.d := y^.d; {3}
y^.d := y^.g; {4}
y^.g := x^.g; {5}
x^.g := y; {6}
end ;

x x x
2 3
P Q Q
y y y
A Q A P A P
1

B C B C B C

x x x
Q Q Q
6
y y y
A P C P C P C
4
5
B A B A B

84 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles85

4.7.6 Insertion
1. Insertion d’une feuille rouge
2. Si son père est rouge, nous avons ce qui est appelé une bulle : il faut rétablir l’invariant

Cas 1 : si l’oncle de la bulle B est rouge


⇒ le problème est remonté sur le grand-père en changeant les couleurs
Cas 2 : si l’oncle de la bulle B est noir et que B est du coté opposé à son père
⇒ faire une rotation entre père et fils ⇒ poursuivre avec cas 3
Cas 3 : si l’oncle de la bulle B est noir et que B est du même coté que son père
⇒ on fait une rotation entre père et grand-père qui résout le problème

Source : [Cor04, fig 13.4]

31 décembre 2012 www.madit.be 85


864.7 Arbres rouges/noirs (ARN) BIHD3 - Meth Pgm

La procédure inserer débute la recherche à la racine a. Elle ne traite que a et ses fils examinés à partir de a. Si
l’élément n’a pu y être inséré à un emplacement NIL, elle passe ensuite le relais à la procédure insererRec avec
une chaîne de père (root) + fils + petit-fils.

ab.g ab.d

ab.gb.g ab.gb.d ab.db.g ab.db.d

procedure inserer(e: elem; var a: arn) ; { O(1) }


begin
if a = nil then a := singleton(e)
else
4 begin
a^.red := false ; { forcer la racine noire }
if e < a^.e
then if a^.g = nil
then a^.g := singleton(e)
else if e < a^.g^.e
then insererec(e, a, a^.g, a^.g^.g)
else if e > a^.g^.e
then insererec(e, a, a^.g, a^.g^.d)
{else ne rien faire }
else
if e > a^.e
then if a^.d = nil
then a^.d := singleton(e)
else if e < a^.d^.e
then insererec(e, a, a^.d, a^.d^.g)
else if e > a^.d^.e
then insererec(e, a, a^.d, a^.d^.d)
{else ne rien faire }
end
end ;

La procédure insererec prend en paramètre une filiation de 3 nœuds a, b, c tels que ab.bb.c (avec a, b non NIL).
Si c n’est pas NIL, elle descend récursivement dans la branche correspondante.
Sinon, elle insère une feuille rouge en c et doit ensuite rétablir l’invariant.
La remontée dans la pile des appels garanti le rétablissement de l’invariant vers le haut de l’ARN.

procedure insererec(e: elem ; var a,b,c : rougenoir) ;


(* Pré : a,b <> nil *)
(* Post : a = a0 ; b = b0 ; Info(c) = Info(c0) union {e} *)
var
y : arn; {le n\oe ud 'oncle'}
begin
if c = nil then c := singleton(e)
{ sinon recherche descendante récursive }
else if e < c^.e then insererec(e,b,c,c^.g)
else if e > c^.e then insererec(e,b,c,c^.d) ;
{ rétablir invariant à ce niveau }
if b^.red and c^.red { a est donc noir }
then begin
if b = a^.g
then begin { cas gauche }
y := a^.d; { oncle à droite }
if y^.red
then begin { oncle rouge = cas 1 : permutation de couleur }
b^.rouge := false ;
y^.rouge := false ;
a^.rouge := true ;
end
else
begin { oncle noir }
if c = b^.d { neveu même coté que oncle = cas 2 : rotation opposée sur père }

86 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles87

then rotationGauche(b);
{ cas 3 : rotation coté oncle sur grand-père }
rotationDroite(a);
end
end
else begin {cas droite}
y := a^.g; { oncle à gauche }
if y^.red
then begin { oncle rouge = cas 1 : permutation de couleur }
b^.rouge := false ;
y^.rouge := false ;
a^.rouge := true ;
end
else
begin { oncle noir }
if c = b^.g { neveu même coté que oncle = cas 2 : rotation opposée sur père }
then rotationDroitee(b);
{ cas 3 : rotation coté oncle sur grand-père }
rotationGauche(a) ;
end

end
end 4
end;

Schéma de rétablissement de l’invariant :

b.red
∧ c.red

b = ab.g b = ab.d

y := ab.d y := ab.g

y.red ¬y.red y.red ¬y.red

CAS 1 c = bb.d c = bb.g CAS 1 c = bb.g c = bb.d

red(a) CAS 2 red(a) CAS 2


black(b) black(b)
black(y) black(y)
rotation rotation
droite(b) gauche(b)

CAS 3 CAS 3

rotation rotation
gauche(a) droite(a)

retour appel (niv -1)

31 décembre 2012 www.madit.be 87


884.7 Arbres rouges/noirs (ARN) BIHD3 - Meth Pgm

4.7.7 Supprimer
Comme pour l’insertion d’une valeur, la suppression d’une valeur dans un ARN commence par rechercher le nœud
à supprimer comme dans un arbre binaire de recherche (voir  page 78).
Cependant, les propriétés des ARN doivent être respectées

• Si le nœud supprimé est rouge, la hauteur noire n’est pas modifiée et la propriété (1) reste vérifiée.
• Si le nœud supprimé est noir, alors sa suppression va diminuer la hauteur noire de certains chemins dans
l’arbre. La propriété est retrouvée en rectifiant les couleurs.

Posons s le nœud dont la valeur est à supprimer. Plusieurs cas sont à traiter :

• s n’a pas de fils :

4 ◦ s est rouge → supprimer s ne modifie pas l’invariant 3


◦ s est noir → supprimer s et son père devient double-noir
→ rétablir l’invariant sur p 7
• s a un seul fils f (gauche ou droite) :
◦ s est rouge → f prend sa place puis supprimer s 3
◦ s est noir et f est rouge → f prend sa place et sa couleur, puis supprimer s 3
◦ s et f sont noirs f prend sa place puis supprimer s. mais f devient un double-noir
→ rétablir l’invariant sur f 7
• s a deux fils g et d :

◦ s n’est pas supprimé, seul son contenu sera adapté


◦ Rechercher le maximum m vers la branche gauche (et son sous-arbre droit) comme pour un ABR
◦ Copier les valeurs de m dans le nœud s mais s conserve sa couleur !
◦ Pour supprimer m (0 ou 1 fils), adapter supprimerRoot et supprimerMax aux cas précédents.

 Rétablir l’invariant
On considère que le nœud qui remplace le nœud supprimé porte une couleur noire en plus, cela signifie qu’il devient
double-noir s’il était déjà noir. La hauteur noire reste ainsi vérifiée mais il faut supprimer cette double propriété
sur un noeud.
Soit x le nœud doublement noir. Il vient les cas suivants

Cas 0 x est la racine de l’arbre.


Le nœud x devient simplement noir. 3
C’est le seul cas où la hauteur noire de l’arbre diminue.

x x

g d g d

88 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles89

Cas 1 x à un frère f rouge.


Comme f est rouge, le père p de f ainsi que ses deux fils g et d sont donc noirs.
(1) permuter les couleurs de p et de f
(2) effectuer une rotation sur p vers x (rotation gauche si x est à gauche et inversement)
(3) x reste double noir 7
Le nouveau frère de x est noir (l’un des enfants de f ) ⇒ voir cas suivants

p p f

x f x f p d 7

g d g d x g

p p f
4
f x f x g p 7

g d g d d x

Cas 2 x à un frère f noir et deux neveux g et d noirs.


(1) Le nœud x devient noir et f devient rouge.
(2) Le nœud p devient noir
– p était rouge → 3
– p était noir → p devient le nouveau double-noir à traiter (la bulle remonte) 7

p p

x f x f 3

g d g d

p p

x f x f 7

g d g d

31 décembre 2012 www.madit.be 89


904.7 Arbres rouges/noirs (ARN) BIHD3 - Meth Pgm

Cas 3 x à un frère f noir et le neveux opposé est rouge.


(1) permuter les couleurs de f et de ce neveux opposé à x
(2) p devient noir
(3) effectuer une rotation vers x sur p
– p était rouge → 3
– p était noir → p devient le nouveau double-noir à traiter 7

p p f

x f x f p d 3

g d g d x g

4
p p f

x f x f p d 7

g d g d x g

p p f

f x f x g p 3

g d g d d x

p p f

f x f x g p 7

g d g d d x

90 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles91

Cas 4 x à un frère f noir son neveux proche est rouge.


(1) (f devient rouge et) le neveu du coté de x devient noir
(2) effectuer une rotation sur f vers le neveu opposé
(3) effectuer une rotation sur p vers x
(4) (f devient noir et) x devient simple noir → 3

p p p

x f x f x g

g d g d l f

l r l r r d

4
g g

p f p f 3

x l d x l d

4.7.8 Web
Une animation sur les ARN : http ://www.cse.yorku.ca/-aaw/Sotirios/RedBlackTree.html

31 décembre 2012 www.madit.be 91


924.8 B-Arbres BIHD3 - Meth Pgm

4.8 B-Arbres
4.8.1 Définition
Un arbre B (ou B-Tree) est un arbre binaire de recherche équilibré dans lequel tout nœud (excepté la racine) a
entre m/2 et m fils (avec m > 1 un entier fixé, appelé l’ordre de l’arbre).
Cette structure de données est très intéressante lorsqu’une grande partie de l’arbre est stocké en mémoire lente
(mémoire auxiliaire) car la hauteur de l’arbre (et donc le nombre d’accès à la mémoire) peut être assez réduite.
Plus m est élevée, plus la hauteur de l’arbre est réduite.
Les B-arbres utilisent la même idée que les arbres rouges-noirs. Ils regroupent un paquet de nœuds bicolores dans
un nœud de B-arbres, typiquement de la taille d’une page mémoire (ex : un bloc de 8ko).

1. Si on regroupe un nœud noir avec son éventuel fils rouge, et qu’on met la racine à noir, on obtient un arbre
où toutes les branches ont la même longueur mais dont le nombre de fils varie.
4 2. On peut en regrouper plusieurs niveaux – pour obtenir un nœud de B-arbre qui a la taille d’un bloc disque
3. Dans une feuille, tous les fils sont vides, on peut donc supprimer les pointeurs pour mettre plus de valeurs
(par ex. dans un tableau).
Exemple 4.8.1
Un B-arbre d’ordre 2
– chaque nœud (sauf la racine) contient k clés avec 2 ≤ k ≤ 4
– la racine contient k clés avec 1 ≤ k ≤ 4

On choisit

const
{ pour un n\oe ud interne non feuille : }
Minelems = ... ; { > 0: nombre min de clefs }
Maxelems = ... ; { > 2 * Minelems : nombre max de clefs }
{ pour une feuille : }
Mindonnees = ... ; { > 0: nombre min d ’ éléments }
Maxdonnees = ... ; { > 2 * Mindonnees : nombre max d’éléments }

Le nombre minimum de fils t est Minelems+1 .


Soit Min = Mindonnees pour une feuille, Minelems sinon.
La cellule racine peut aller en dessous du Min, mais doit avoir au moins 1 clef et 2 fils.

92 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles93

 Déclaration

Barbre = ^n\oe ud;


{ invariant : b <> nil }
n\oe ud = record
uti : integer ; { nombre d ’ éléments utilisés }
case feuille: boolean of
true : ( donnees: array [1..Maxdonnees] of elem ) ;
false : ( elems: array [1..Maxelems] of elem ; fils: array [0..Maxelems] of Barbre ) ;
end ;
end ;

 Invariant de données
Avec uti le nombre d’élément utilisés
1. 0 ≤ uti
2. 0 < uti si la racine n’est pas une feuille 4
3. uti ≤ M ax
4. M in ≤ uti pour tout nœud sauf la racine
5. ∀ i ∈ [1..uti] : e ∈ Inf o(f ils[i − 1]) ∧ e < elems[i] (les valeurs inférieures à gauche)
6. ∀ i ∈ [1..uti] : e ∈ Inf o(f ils[i]) ∧ e > elems[i] (les valeurs supérieures à droite)
7. tous les fils ont la même hauteur.
8. la partie utilisée de donnees et elems est triée

 Hauteur
L’invariant nous donne une hauteur logarithmique : la racine a au min une clé et deux fils, ses fils ont au minimum
t fils, etc.
Xh
n ≥ 1 + (t − 1) ∗ 2ti−1
i=1

 
n+1
h ≤ logt
2

31 décembre 2012 www.madit.be 93


944.8 B-Arbres BIHD3 - Meth Pgm

4.8.2 Procédures et fonctions


 Recherche
On recherche la clef dans le tableau elems.
Soit on l’y trouve, sinon on trouve le fils dans lequel il pourrait se trouver.
Cette recherche peut être linéaire, ou dichotomique si Max est grand.

 Ensemble vide
L’ensemble vide est représenté par une feuille avec uti=0 .

 Diviser
Un nœud plein peut être divisé en deux nœuds, et la clé centrale est remontée dans le père pour séparer ces deux
4 nœuds.

1. Technique préventive : On met dans la précondition que le père n’est pas plein.
2. Technique corrective : On divise le père après si nécessaire par un appel récursif.

 Insérer
On voudrait insérer une nouvelle clef dans la feuille où on devrait la trouver, mais ceci peut faire déborder la feuille
si elle était pleine.

1. Technique préventive : on divise tous les nœuds pleins qu’on traverse


2. Technique corrective : on divise en remontant.

Exemple 4.8.2
Insertion d’un enregistrement de clé 25 [2]

avant

après

[2]. source : http ://www.montefiore.ulg.ac.be/ pw/cours/psfiles/fichiers-transp7.pdf

94 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles95

Cas de base. Seul cas qui permet de modifier la hauteur de l’arbre

procedure inser(e: elem; var b: Barbre) ;


var
p = Barbre ;
begin
if plein(b)
then begin
new(p) ;
p^.feuille := false ;
p^.uti := 0 ;
p^.fils[0] := b ;
b := p;
diviser(p, 0) ;
end ;
inserec(e,b) ;
end ;

Cas récursifs

procedure inserec(e: elem; var b: Barbre) ; 4


{ Pré : b^ est non plein }
{ Post : b est un B-arbre ; e s'ajoute à son contenu }
var
i : integer ;
begin
i := rech(e,b) ;
if b^.feuille
then if absent(e,i,b)
then insererpos(i,e,b)
else if absent(e,i,b)
then if plein(b^.fils[i])
then begin
diviser(b,i) ;
if b^.elems[i+1] <= e then i := i+1;
if b^.elems[i] <> e then inserec(e,b^.fils[i]) ;
end
else inserec(e,b^ .fils[i] )
end ;

 Fusion
Symétriquement, la fusion de deux blocs (lors d’une suppression) supprime une clef du père qui pourrait ainsi
passer en dessous du minimum.

31 décembre 2012 www.madit.be 95


964.8 B-Arbres BIHD3 - Meth Pgm

 Supprimer
Technique préventive : on ne descend dans un fils que s’il est au-dessus du min.
Pour garantir cela, soit on prend des fils d’un frère (s’il n’est pas au min), soit on fusionne avec un frère.
De même lors de la suppression du minimum/maximum ci-dessous.
Si la valeur à supprimer est dans un nœud intérieur, il faut la remplacer soit :

1. par la valeur suivante : le minimum du fils de droite


2. par la valeur précédente : le maximum du fils de gauche

Si ces deux fils sont au min, on les fusionne avant.

Exemple 4.8.3
Suppression d’un enregistrement de clé 7 [3]

4 avant

après

 Animation web
http ://deptinfo.cnam.fr/Enseignement/CycleA/SD/demonstration/B-Arbre.html
http ://www.youtube.com/watch ?v=coRJrcIYbF4

[3]. source : http ://www.montefiore.ulg.ac.be/ pw/cours/psfiles/fichiers-transp7.pdf

96 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles97

31 décembre 2012 www.madit.be 97


984.9 Exercices de TP - implémentation des ensembles BIHD3 - Meth Pgm

4.9 Exercices de TP - implémentation des ensembles


4.9.1 Arbres binaires ABR et ARN

1 Arbres binaires / Arbres rouge-noir


1.1 arbres binaires
Un arbre est (définition récursive)
– soit un arbre vide ;
– soit un noeud comportant une valeur v (qu’on supposera entière dans le cadre de ces TP) accompa-
gnée d’un sous-arbre gauche SAG et d’un sous-arbre droit SAD (qui sont tous les deux des arbres

4 et peuvent donc être vides).

On peut les implémenter grâce aux pointeurs, sous la forme suivante.


type arbre = ^noeud;
noeud = record
val:integer;
sag,sad:arbre;
end;

1.2 Exercices sur les arbres binaires


Dans les spécifications suivantes, "a est un arbre" signifie soit que soit a = nil soit a 6= nil mais alors
a^.val est initialisé et a^.sag et a^.sad sont des arbres. Autrement dit, a est initialisé, et toutes les
valeurs des noeuds de a (s’il y en a) sont également initialisées.

Il vous est demandé de spécifier et d’implémenter les fonctions suivantes :

- Tester la présnece d’un élément dans un arbre binaire


- Calculer le nombre d’occurences d’un élément dans un arbre binaire
- Vérifier si deux arbres binaires sont identiques
- Déterminer la hauteur d’un arbre binaire
- Calculer le nombre de feuilles d’un arbre binaire
- Calculer le nombre d’élements d’un arbre binaire
- Chercher le maximum dans un arbre binaire
- Détruire complètement un arbre binaire

1.3 Définition récursive arbre binaire trié


Un arbre est trié si, pour lui et chacun de ses sous-arbres, les valeurs du sous-arbre gauche sont
inférieures à la valeur du noeud et que les valeurs du sous-arbre droit sont supérieures à la valeur du
noeud.

Exercice 1 (Définition)
Donner une “définition récursive” d’un type concret pour un arbre binaire strictement trié (d’éléments de
type t).

98 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles99

Exercice 2 (Parcours d’arbres binaires)

p :

?
2
@
@
R
@
3 5
@
@
R
@
4 6 8
@
@
R
@
7 4
1. Quelle sera la séquence d’entiers rencontrée lors d’un parcours de l’arbre p dans l’ordre préfixé, dans
l’ordre infixé, dans l’ordre postfixé ?
Implémenter les fonctions de parcours d’un arbre suivantes.

En-tête : procedure aplatir(a :arbre ; var l :liste)


Précondition : a est un arbre
Postcondition : l est une liste chaînée reprenant les valeurs de a reprises dans un ordre infixé

Exercice 3 ()

En-tête : procedure pre-aplatir(a :arbre ; var l :liste)


Précondition : a est un arbre
Postcondition : l est une liste chaînée reprenant les valeurs de a reprises dans un ordre préfixé

Exercice 4 ()

En-tête : procedure post-aplatir(a :arbre ; var l :liste)


Précondition : a est un arbre
Postcondition : l est une liste chaînée reprenant les valeurs de a reprises dans un ordre postfixé

2 Arbres rouge-noir
Premier aperçu et premières définitions Un arbre rouge-noir est une arbre binaire dont chaque noeud
est coloré soit en noir, soit en rouge et vérifiant les propriétés suivantes

1. L’arbre est strictement trié ;


2. Les enfants d’un noeud rouge sont noirs. Entre autres, on attribue, par convention, la couleur noire
aux arbres vides (et donc aux pointeurs nil) ;
3. Tous les chemins menant de la racine à n’importe quelle feuille (c’est-à-dire à n’importe quel nil)
contiennent le même nombre de noeuds noirs.

On appelle profondeur noire le nombre de noeuds noirs visités pour aller de la racine d’un arbre rouge-
noir jusqu’à une de ces feuilles. (D’après la condition 3, le nombre obtenu est le même, quelle que soit la
feuille choisie.)

31 décembre 2012 www.madit.be 99


1004.9 Exercices de TP - implémentation des ensembles BIHD3 - Meth Pgm

Exercice 5 (Réflexion)
1. Quel est l’intérêt de ces arbres par rapport aux arbres binaires (triés) ?
2. Les arbres rouges-noirs sont plus complexes que les "simples" arbres strictement triés : quel est le
gain apporté pour cette complexité ajoutée ?
3. Combien y-a-t’il d’arbres rouges-noirs différents (sans tenir compte des valeurs des noeuds) de hau-
teur noire 0 ?
4. Combien y-a-t’il d’arbres rouges-noirs différents (sans tenir compte des valeurs des noeuds) de hau-
teur noire 1 ?
5. Quelle est la forme des arbres rouges-noirs constitués uniquement de noeuds noirs ?
6. Quelle est la forme des arbres rouges-noirs constitués uniquement de noeuds rouges ?

Exercice 6 (Définition inductive)


4 On définit les arbres par induction : un arbre est soit un arbre vide, soit une valeur accompagnée d’un
sous-arbre gauche et d’un sous-arbre droit qui sont eux-mêmes des arbres. De même, on peut définir les
arbres triés par : un arbre trié est soit un arbre vide, soit un arbre de valeur v, de sous-arbre gauche g et
de sous-arbre droit d tel que g et d sont eux-aussi des arbres triés, toutes les valeurs de g sont inférieures
à v et toutes les valeurs de d sont supérieures à v.

On cherche à faire de même pour les arbres rouges-noirs. On décide de construire une définition in-
ductive divisée en trois cas : un cas de base, l’arbre vide ; et deux cas de récurrence, qui concernent d’une
part les arbres dont la racine est noire et d’autre part les arbres dont la racine est rouge. Quelles sont les
conditions à ajouter pour que cette définition inductive soit correcte ?

Exercice 7 (Réflexion)
1. Combien de valeurs, au minimum, un arbre rouge-noir de hauteur noire hn peut-il contenir ?
2. Quelle relation existe entre la hauteur (normale) h et la hauteur noire (hn) d’un arbre rouge-noir ?
3. Si on place n valeurs dans un arbre rouge-noir, quelle sera, au pire, sa hauteur noire ? Et sa hauteur ?

Exercice 8 (Rotations dans les arbres rouges-noirs)


Il s’agit d’opérations qui réorganisent un arbre strictement trié de telle sorte que le résultat soit encore un
arbre strictement trié, en déplaçant la valeur d’un noeud fils dans le noeud père.

Rotation gauche :

Le noeud père et le noeud fils peuvent être de n’importe quelle couleur avant la rotation. Après la
rotation, le noeud père doit avoir la même couleur que le noeud père avant l’opération ; de même pour le
noeud fils.

Cette opération sera utilisée pour transformer un arbre binaire trié dont les noeuds sont colorés en un
arbre rouge-noir. L’arbre de départ n’est pas forcément un arbre rouge-noir, mais on sait qu’il est "bien

100 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles101

défini" (au sens où, si on suit ses branches, on ne tombe pas sur des valeurs ou des pointeurs indéfinis).
On utilisera ce terme ("bien défini") comme précondition dans la spécification de ces opérations.

Rotation droite :

4
Il vous est demandé de spécifier et d’implémenter les fonctions suivantes.
Exercice 01
En-tête : procedure rotationgauche(t :arbreRN)
Précondition : t est "bien défini" et sa racine a un fils droit
Postcondition : Une rotation gauche a été effectuée sur t

Exercice 02
En-tête : procedure rotationdroite(t :arbreRN)
Précondition : t est "bien défini" et sa racine a un fils gauche
Postcondition : Une rotation droite a été effectuée sur t

3 Implémentation d’un type logique avec des ARNs


3.1 Type abstrait Map
Un tableau associatif ou une Map(aussi appelé dictionnaire, table d’association) est un type abstrait
de données composé d’un ensemble fini de clefs et d’un ensemble fini de valeurs, où chaque clef est associée
à une valeur. La notion de tableau associatif est donc proche de celle de fonction à domaine fini en
mathématiques.
Du point de vue du programmeur, une fonction Map associe des valeurs d’un type arbitraire à des
valeurs d’un autre type.

Les opérations usuellement fournies pour un TA Map sont :


* Create : création * Add : association d’une nouvelle valeur à une nouvelle clef ;
* Update : association d’une nouvelle valeur à une ancienne clef ;
* Get : détermination de la valeur associée à une clef, si elle existe.
* Delete : suppression d’une clef ;

Il vous est demandé de :


- Spécifier un TA Map : int -> int et d’implémenter les fonctions précédentes en utilisant des arbres
rouge-noir
- Construire la composée de deux Maps

31 décembre 2012 www.madit.be 101


1024.9 Exercices de TP - implémentation des ensembles BIHD3 - Meth Pgm

4.9.2 Code C complet pour un arbre

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <math.h>
4 #include <time.h>
5 #include <stdbool.h>
6
7 typedef struct Arbre{
8 int val;
9 struct Arbre* SAG;
10 struct Arbre* SAD;
11 }Arbre;
12
13 struct List{
14 int val;
15 struct List* next;
16 };
4 17
18 typedef struct List* List;
19
20 void insert(struct Arbre** tree, struct Arbre * item);
21 void aplatir(struct Arbre * tree) ;
22 void pre_aplatir(struct Arbre * tree);
23 void post_aplatir(struct Arbre * tree);
24 int size_Arbre(struct Arbre* arbre);
25 int presense_element(struct Arbre* arbre, int key);
26 int number_occurence(struct Arbre* arbre, int key);
27 int hauteur(struct Arbre* arbre);
28 int maxValue(struct Arbre* arbre);
29 int nfeuilles(struct Arbre* arbre);
30 void detruireabre(struct Arbre ** arbre);
31
32 int main(void) {
33 struct Arbre * curr, * root;
34 int i;
35
36 root = NULL;
37 srand((unsigned)time(NULL));
38 for(i=1;i<=20;i++) {
39 curr = malloc(sizeof(struct Arbre));
40 curr->SAG = curr->SAD = NULL;
41 int x = rand()%20 + 1;
42 curr->val = x;
43 insert(&root, curr);
44 }
45 // detruireabre(&root);
46 /*printf("Element exist binary search tree: %d\n",presense_element(root,3));
47 printf("Number of occurrences in binary search tree: %d\n",number_occurence(root,3));
48 printf("The height of the binary search tree: %d\n",hauteur(root));
49 printf("The maximum value of the binary search tree: %d\n",maxValue(root));
50 printf("The number of leaf nodes of the binary search tree: %d\n",nfeuilles(root));
51 printf("size of binary search tree: %d\n",size_Arbre(root));*/
52
53 printf("In order display of binary tree\n");
54 aplatir(root);
55 printf("pre-order order display of binary tree\n");
56 pre_aplatir(root);
57 printf("post- order display of binary tree\n");
58 post_aplatir(root);
59
60 return EXIT_SUCCESS;
61 }
62
63 void insert(struct Arbre** tree,struct Arbre * item) {
64 if(!(*tree)) {
65 *tree = item;
66 return;
67 }
68 if(item->val<=(*tree)->val)
69 insert(&(*tree)->SAG, item);
70 else if(item->val>(*tree)->val)

102 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles103

71 insert(&(*tree)->SAD, item);
72 }
73
74 void display(List list){
75 while(list!= NULL){
76 printf("%d\n",list->val);
77 list = list->next;
78 }
79 }
80
81 /*
82 * Précondition : a est un arbre
83 * Postcondition : aplatir est une liste chainee reprenant les valeurs de a reprises dans un ←-
ordre indexé
84 */
85 void aplatir(struct Arbre * tree) {
86 if(tree== NULL)
87 return;
88 aplatir(tree->SAG);
printf("%d\n",tree->val);
89
90
91 }
aplatir(tree->SAD); 4
92
93 /*
94 * Précondition : a est un arbre
95 * Postcondition : pre-aplatir est une liste chainée reprenant les valeurs de a reprises dans ←-
un ordre préfixé
96 */
97 void pre_aplatir(struct Arbre * tree) {
98 if(tree== NULL)
99 return;
100 printf("%d\n",tree->val);
101 pre_aplatir(tree->SAG);
102 pre_aplatir(tree->SAD);
103 }
104
105 /*
106 * Précondition : a est un arbre
107 * Postcondition : post-aplatir est une liste cha”née reprenant les valeurs de a reprises dans←-
un ordre postfixé
108 */
109 void post_aplatir(struct Arbre * tree) {
110 if(tree== NULL)
111 return;
112 post_aplatir(tree->SAG);
113 post_aplatir(tree->SAD);
114 printf("%d\n",tree->val);
115 }
116
117 /*
118 * Précondition : a est un arbre
119 * Postcondition : size-arbre est le nombre de noeuds
120 */
121 int size_Arbre(struct Arbre* arbre) {
122 if (arbre==NULL) {
123 return(0);
124 } else {
125 return (size_Arbre(arbre->SAG) + 1 + size_Arbre(arbre->SAD));
126 }
127 }
128
129 /*
130 * Précondition : a est un arbre
131 * Postcondition : nombre d'occurrences de key dans a
132 */
133 int number_occurence(struct Arbre* arbre, int key) {
134 if (arbre == NULL) {
135 return (0);
136 } else {
137 if(arbre->val == key)
138 return 1 + presense_element(arbre->SAG,key) + presense_element(arbre->SAD,key);
139 if(arbre->val < key)
140 return presense_element(arbre->SAG,key);

31 décembre 2012 www.madit.be 103


1044.9 Exercices de TP - implémentation des ensembles BIHD3 - Meth Pgm

141 else
142 return presense_element(arbre->SAD,key);
143 }
144
145 }
146
147
148 /*
149 * Précondition : a est un arbre
150 * Postcondition : vrai/faux selon que x apparait dans a
151 */
152 int presense_element(struct Arbre* arbre, int key){
153 if(arbre == NULL)
154 return (false);
155 if(arbre->val == key)
156 return (true);
157 else if(arbre->val < key)
158 return presense_element(arbre->SAG,key);
159 else
return presense_element(arbre->SAD,key);
4 160
161
162
}

163 /*
164 * Précondition : a et b sont des arbres
165 * Postcondition : vrai/faux selon que a et b sont identiques
166 */
167 int egalArbre(struct Arbre* a, struct Arbre* b) {
168 // 1. both empty -> true
169 if (a==NULL && b==NULL) return(true);
170 // 2. both non-empty -> compare them
171 else if (a!=NULL && b!=NULL) {
172 return(
173 a->val == b->val &&
174 egalArbre(a->SAG, b->SAG) &&
175 egalArbre(a->SAD, b->SAD)
176 );
177 }
178 // 3. one empty, one not -> false
179 else return(false);
180 }
181
182 /*
183 * Précondition : a est un arbres
184 * Postcondition : hauteur est la hauteur de a
185 */
186 int hauteur(struct Arbre* arbre) {
187 if (arbre==NULL) {
188 return(0);
189 }
190 else {
191 // compute the depth of each subtree
192 int lHauteur = hauteur(arbre->SAG);
193 int rHauteur = hauteur(arbre->SAD);
194 // use the larger one
195 if (lHauteur > rHauteur) return(lHauteur+1);
196 else return(rHauteur+1);
197 }
198 }
199
200 /*
201 * Précondition : a est un arbre d'entriers strictement positifs
202 * Postcondition : maxValue est le maximum des valeurs de l'arbre a ou 0 si l'arbre est vide
203 */
204 int maxValue(struct Arbre* arbre) {
205 struct Arbre* current = arbre;
206 // loop down to find the leftmost leaf
207 while (current->SAD != NULL) {
208 current = current->SAD;
209 }
210 return(current->val);
211 }
212
213 /*

104 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 4. Implémentation des ensembles105

214 * Précondition : a est un arbre


215 * Postcondition : nfeuilles est le nombre de feuilles de l'arbre a
216 */
217 int nfeuilles(struct Arbre* arbre)
218 {
219 if(arbre == NULL)
220 return 0;
221 if(arbre->SAG == NULL && arbre->SAD==NULL)
222 return 1;
223 else
224 return nfeuilles(arbre->SAG) +
225 nfeuilles(arbre->SAD);
226 }
227
228 /*
229 * Précondition : a est un arbre
230 * Postcondition : a est detruit (et l'espace mémoire qu'il occupait est libéré
231 */
232 void detruireabre(struct Arbre** arbre){
if(*arbre!=NULL){
233
234
235
detruireabre(&((*arbre)->SAG));
detruireabre(&((*arbre)->SAD));
4
236 free(*arbre);
237 *arbre = NULL;
238 }
239 }

31 décembre 2012 www.madit.be 105


1064.9 Exercices de TP - implémentation des ensembles BIHD3 - Meth Pgm

106 www.madit.be 31 décembre 2012


5
TA File de priorité

Sommaire
5.1 Spécification par modèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.2 Implémentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.3 Le tri par Tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111

107
1085.1 Spécification par modèle BIHD3 - Meth Pgm

5.1 Spécification par modèle


Une file de priorité est un sous-type d’Ensemble : restriction des opérations possibles (seulement deux).

 insérer

procedure inserer(E: ens; x: elem)


(* Post: E = E_0 union {x} *)

 supprimerMax

NB : avec une procédure (et non une fonction) car l’ensemble passé en paramètre est modifié (suppression et
renvoi du maximum m)

procedure supprimerMax(var E: ens; var m: elem)


(* Pre : E non vide *)
(* Post : m = max(E0) et E = E0 \ {m} *)

5
5.2 Implémentation

5.2.1 Possibilités

1. B-Arbres : inserer et supprimerMax se réalisent en log n (temps optimaux).

2. Tas [1] : implémentation plus simple.

5.2.2 Tas

 Arbre partiellement ordonné : APO

Un Tas implémente un APO.

Invariant de l’APO : un père est toujours plus grand que ses fils (racine = le max).

 Invariant

Plus contraignant que l’APO

• un père est toujours plus grand que ses fils (APO)


• tous les niveaux, sauf le dernier, sont remplis (càd que tous les chemins ont la même longueur à 1 près)
• le dernier niveau est rempli d’abord à gauche

Faiblesse : recherche difficile (faiblesse de l’invariant).

[1]. Tas = heap, mais différent que celui destiné à la gestion de la mémoire.

108 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 5. TA File de priorité109

 Représentation

[1]
11

[2] [3]
10 7

[4] [5] [6] [7]


8 3 5 1

[8] [9]
6 4

La numérotation des nœuds

i
5
2i 2i + 1

Cette numérotation autorise l’utilisation des index dans un tableau (structure plus rapide). par rapport aux B-arbres
et aux ARN, un tableau ne conserve que les valeurs. Aucun espace n’est perdu pour les pointeurs.

i 1 2 3 4 5 6 7 8 9
A[i] 11 10 7 8 3 5 1 6 4

31 décembre 2012 www.madit.be 109


1105.2 Implémentation BIHD3 - Meth Pgm

5.2.3 Implémentation du Tas


 Procédures ’privées’
Un ’swap’ trivial

procedure swap( var A : INTARRAY ; i,j : integer ) ;


var temp : integer ;
begin
temp := A[i] ;
A[i] := A[j] ;
A[j] := temp ;
end ;

Le ’bubbleUp’ est utilisé à l’insertion pour faire remonter une valeur à sa place

procedure bubbleUp( var A: INTARRAY; i: integer ) ; { i : index de la bulle }


begin
if i > 1 {cas de base 0 = la racine (rien à faire)}
then
if A[i] > A[i div 2] {fils > père ?}
then
begin
swap(A, i, i div 2) ; {le père descend}
5 bubbleUp(A, i div 2) {ctl de la 'bulle' avec le nouveau père}
end
end ;

bubbleDown, utilisé en suppression, fait descendre un élément responsable de la violation de la propriété APO
jusqu’à ce qu’une feuille soit atteinte.

procedure bubbleDown( var A : INTARRAY ; i,n : integer ) ;


var child : integer ;
begin
child := 2 * i ;
if child < n {fils dans les bornes, avec plusieurs fils}
then
if A[child+1] > A[child]
then child := child + 1 ; { indice = fils max }
if child <= n then {si un seul fils, traité directement ici}
if A[i] < A[child] then
begin
swap(A, i, child) ; { swap père/fils }
bubbleDown(A, child, n) ;
end
end ;

 Insertion

procedure insert( var A : INTARRAY; x: integer ; var n: integer ) ;


{ x : valeur à insérer ; n : taille du tableau }
begin
n := n + 1 ;
A[n] := x ;
bubbleUp (A, n) ; { faire monter la valeur à sa place }
end ;

Optimaliser : record(tableau, taille)

110 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 5. TA File de priorité111

 Extraction du maximum

procedure deletemax( var A: INTARRAY ; var n: integer ; var m : elem) ;


begin
x := A[1] ; { le max }
swap(A, 1, n) ; { le dernier elem passe à la racine }
n := n - 1 ; { décrémenter la taille }
bubbleDown(A, 1, n) ; { rétablir invariant }
end ;

Exécuté en O(log n)

5.3 Le tri par Tas


 Vers un tas pour trier : heapify
Transformer un tableau en tas.

procedure heapify( var A : INTARRAY ; n : integer ) ;


var i : integer ;
begin
for i := n div 2 downto 1 do bubbleDown(A,i,n) ;
end ; 5
On part de la moitié vers la racine car la seconde moitié du tas n’a pas de fils !
Exécuté en O(n) si on analyse finement l’algorithme.

 Tri en tas
Tri par tas dans un tableau.

procedure heapsort( var A : INTARRAY; n : integer ) ;


var i : integer ;
begin
heapify(A,n) ; { en O(n) }
i := n;
while i > 1 do
deletemax(A,i,A[i]) { pour rappel, fait passer le max en dernière position }
{ T= O(log n) * n-1 }
end ;

Exécuté en O(n log n), soit un temps optimal.


Second avantage : tri sur place (pas besoin d’espace mémoire supplémentaire).
Mais si tableau déjà trié, pas de gain (car il détruit tout pour commencer le tri).

31 décembre 2012 www.madit.be 111


1125.3 Le tri par Tas BIHD3 - Meth Pgm

112 www.madit.be 31 décembre 2012


Troisième partie

Conception d’algorithmes

113
 Question de départ
Comment passer d’un problème à un programme ?

Décrire une spécification


↓ Architecture + conception d’algorithme (efficacité)
Un algorithme
(une idée de solution, indépendante du langage)
↓ codage
un programme
↓ Compilation
un exécutable

Ce cours est limité à la conception d’algorithmes.

 Workflow pour trouver la bonne méthode


Tout d’abord rendre les énoncés précis et établir la spécification.
Ensuite : comment choisir la méthode ?
1. Essayer la méthode gloutonne.
(a) Preuve ?
(b) Si la preuve est bonne, c’est la méthode la plus rapide → 3
(c) Sinon → 7→ point 2.
2. Essayer Diviser pour régner.

(a) Preuve ?
(b) Ne fonctionne pas → 7→ point 3.
(c) Si preuve bonne + pas de recalculs → 3.
(d) Si preuve bonne mais on a des recalculs : programmation dynamique avec mémoïsation.
3. Si les autres ne fonctionnent pas, utiliser la méthode générer et tester.

 Recommandation du professeur pour l’examen


• L’algo rendu doit fonctionner, même si ce n’est pas la méthode optimale !
• Si on utilise des fonctions triviales, leur donner une spécification minimale ;
• Montrer les étapes du choix de la méthode utilisée ;
• Indiquer les étapes du développement ;
• Indiquer les ordres de grandeur pour les temps d’exécution et la mémoire utilisée ;
• S’il on juge qu’il manque des éléments dans l’énoncé, choisir une hypothèse de travail en mentionnant et
en justifiant ses choix.
6
Diviser pour régner

Sommaire
6.1 Idée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.2 Exemples : quelques tris . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.3 Exemples : multiplications . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

117
1186.1 Idée BIHD3 - Meth Pgm

6.1 Idée
Utiliser la récursion (appel un sur invariant décroissant), ce qui implique de diviser un problème en sous-problèmes
similaires mais plus simples.

1. Diviser le problème en n sous-problèmes similaires plus simples (typiquement : n = 2).


2. Ces sous-problèmes sont résolus récursivement jusqu’aux "cas de base".
3. Les solutions sont recombinées pour donner une solution du problème originel.

6.1.1 Cas de base


La notion de "plus simple" doit être une relation bien fondée.
Les cas de base sont les cas minimaux de cette relation. Pour < sur N, c’est le cas 0.
La fonction résoudre(d) est donc de la forme

• Si cas de base (d) ⇒ r := résoudre-directement(d)


• Sinon

◦ (d1, d2, ..., dn) := diviser(d)


◦ pour tout i : ri := résoudre(di)
◦ r := combiner(r1, r2, ..., rn)
6
6.1.2 Choix possibles
On peut choisir diviser ou combiner d’où on déduit l’autre. On peut diviser un problème :

1. suivant la définition récursive des données.


Cette méthode à l’avantage d’être simple, même si elle est parfois déséquilibrée et donc moins efficace.
Pour rappel : T (n) = T (n − 1) + nk = O((nk+1 )

2. en sous-problèmes de même taille (souvent plus efficace).


Dans ce cas, on doit aussi traiter le cas 1 comme cas de base.
n
Pour rappel : T (n) = 2T ( ) = O(log(n))
2
Exemple 6.1.1
comme liste = listevide | cons(elem,liste) ⇒ division en tête et reste.
Exemple 6.1.2
deux listes ayant environ la même longueur.

118 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 6. Diviser pour régner119

6.2 Exemples : quelques tris


Illustration avec quelques tris. . .

6.2.1 La Spécification
En entrée : l : liste
En sortie : e : liste
Postcondition : e est triée et est une permutation [1] de l.
Si on veut qu’une solution existe toujours, on suppose que l’ordre de tri est réflexif ce qui permet les doublons.

6.2.2 Solution D1 : Tri par INSERTION


D1 = diviser en un premier élément et le reste.

diviser(l) = (tête(l), reste(l))

Cas de base l = nil.


insert(e,nil) = constr(e,nil) ;

On déduit combiner = insérer un élément dans une liste triée. (soit en O(n))
Le temps d’exécution sera donc de l’ordre de O(n2 ).
1 insert(e,constr(t,r)) = {e <= t} ? {constr(e, constr(t,r))} : {cons(t, insert(e, r))} ; 6
Algo complet [Des11, § 6.6.4]
TYPE
indice = 0..MAX;
tabint = array[indice] of Integer ;

PROCEDURE trieInsertion (var r : tabint ; n : indice) ;


VAR i : indice ;

PROCEDURE inserer (k : indice) ;


VAR j : indice ;
BEGIN
r[0] := r[k] ;
j := k-1 ;
WHILE r[o] < r[j] DO BEGIN
r[j+1] := r[j] ;
j := j-1
END
r[j+1] := r[0]
END;

BEGIN
FOR i:=2 TO n DO inserer(i)
END;

[1]. une permutation = les mêmes éléments dans un autre ordre

31 décembre 2012 www.madit.be 119


1206.2 Exemples : quelques tris BIHD3 - Meth Pgm

6.2.3 Solution D2 : Tri par FUSION


D2 = diviser en deux sous-listes égales à 1 près.

diviser(l) = (l1, l2)

tel que l1 + l2 = l et |l1| = |l2| ± 1, en O(log n).

Case de base |l| ≤ 1, dans lequel il faut prendre 0 mais aussi 1 comme taille des plus petites listes triées.
On déduit combiner = fusionner deux listes triées soit exécuté en O(n).
Le temps d’exécution T (n) = 2T (n/2) + n sera donc de l’ordre de O(n log(n)).

6.2.4 Solution C1 : Tri par SELECTION


C1 = combiner, le 1er élément devant les autres

combiner = cons(e,l)

cas de base l = nil .


On déduit diviser = trouver le minimum des éléments de l . (en O(n))
Le temps d’exécution sera donc de l’ordre de O(n2), car on fait n appels à diviser.

6 Algo complet [Des11, § 6.6.2]


PROCEDURE triSelection(var r : tabint ; N : indice) ;
VAR
i, j, pos : indice ;
max : integer ;
BEGIN
FOR i:=N DOWNTO 2 DO BEGIN
max := r[i] ;
pos := i ;
FOR j:=(i-1) DOWNTO 1 DO
IF r[j] > max THEN BEGIN max := r[j] ; pos := j END ;
r[pos] := r[i] ;
r[i] := max
END
END;

6.2.5 Solution C2 : QUICKSORT


C2 = combiner deux parties

combiner = append(l1, l2)

Cas de base |l| ≤ 1.


On déduit diviser = trouver la médiane de l, puis diviser en plus grand, plus petit autour de cette valeur
pivot.

Le temps d’exécution sera donc de l’ordre de O(n log n) en moyenne.

120 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 6. Diviser pour régner121

Algo complet (tri en place) [Des11, § 6.6.5]


PROCEDURE partager(var r : tabint ; d,f : indice ; var p : indice) ;
VAR v, t, i, j : integer ;
BEGIN
v := r[f] ;
i := d-1 ;
j := f ;
REPEAT
REPEAT i := i+1 UNTIL r[i] >= v ;
REPEAT j := j-1 UNTIL r[j] <= v ;
IF i < j THEN BEGIN
t := r[j] ;
r[i] := r[j] ;
r[j] := t END
UNTIL j <= i ;
r[f] := r[i] ;
r[i] := v ;
p := i
END;

PROCEDURE quicksort (var r : tabint ; d, f : indice) ;


VAR p : indice ;
BEGIN
IF d < f THEN BEGIN
partager(r, d, f, p) ;
quicksort(r, d, p-1) ;
quicksort(r, p+1, f) END
END;

PROCEDURE triRapide (var r : tabint ; n : indice) ;


BEGIN
quicksort(r, 1, n)
END;
6
6.2.6 Temps d’exécution minimal d’un tri
Si le tri se fait par comparaisons, on peut représenter les exécutions possibles par un arbre de décisions binaires

Le nombre de feuilles est au moins n! soit le nombre de permutations possibles de la liste d’entrée.
Le temps d’exécution au pire (en ne comptant que les comparaisons) est la hauteur de l’arbre d’exécutions.
Un arbre binaire de hauteur h a au plus 2h feuilles.
n! ≤ 2h ⇔ log2 (n!) ≤ h
or log(n!) = O(n log n) par l’approximation de Stirling.
⇒ T ≥ O(n log n)

⇒ O(n log n) est le meilleur ordre de grandeur possible pour un tri par comparaisons.

31 décembre 2012 www.madit.be 121


1226.3 Exemples : multiplications BIHD3 - Meth Pgm

6.3 Exemples : multiplications


6.3.1 Multiplication binaire
 Méthode classique
Voir [Sch12, slide 223]

 Avec diviser pour régner


Voir [Sch12, slides 224-226]

6.3.2 Puissances rapides


La nieme puissance d’un nombre r est rn = r.r.r.....r.
Une implémentation évidente est de faire une boucle for, avec un temps O(n.m) où m est le temps d’une
multiplication.
Par diviser pour règner, on a l’idée de couper la liste de r en deux parties égales.
Code Pascal

function puissance(r: real ; n: natural) : real ;


begin
if n=0
then puissance := 1
else if pair(n)
6 then puissance :=
else puissance :=
sqr(puissance(r,n div 2))
puissance(r,n-1)*r
end

Code C

1 float puissance(float num, int p) {


2 if (p == 0) {
3 return (1.0) ; // T(1)
4 }
5 else {
6 float d ; // demi puissance
7 if ((p \% 2) == 0) { // T(1)
8 d = puissance(num, p/2) ; // T(p/2)
9 return d * d ; // T(1)
10 } else {
11 d = puissance(num, (p-1)/2) ; // T(p/2)
12 return d * d * num ; // T(1)
13 }
14 }

Complexité
T (1) + T (n/2) : k = 0, c = 1, d = 2, dk = 1 = c ⇒ O(nk log n)
O(log n)

6.3.3 Fibonacci par puissances rapides


    
F ib(n) 0 1 F ib(n − 1)
=
F ib(n + 1) 1 1 F ib(n)
et donc (ou 0, 1 est le vecteur des valeurs de départ)
   
F ib(n) 0
= Mn
F ib(n + 1) 1
On peut donc utiliser l’algorithme des puissances rapides aussi pour les puissances de matrices.
f ibo : Olog n

122 www.madit.be 31 décembre 2012


7
Programmation dynamique

Sommaire
7.1 Memoïsation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.2 La récursivité ascendante . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
7.3 La programmation dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . 130
7.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

123
1247.1 Memoïsation BIHD3 - Meth Pgm

7.1 Memoïsation
7.1.1 Principe
On remarque que l’arbre des appels d’une fonction récursive provoque de nombreux calculs redondants

Pour éviter les re-calculs, on met les valeurs calculées dans un tableau.
En plus de la fonction de départ f , on introduit la fonction fmemo et on remplace tous les appels de f par des
appels de fmemo .
Celle-ci vérifie d’abord si le résultat ne se trouve pas dans son tableau ; sinon elle appelle f et met le résultat dans
son tableau.
Exemple 7.1.1
7 Dans la fonction Fib, les appels récursifs se font à travers Fibmemo qui conserve les données déjà traitées
dans l’arbre des appels.

const undef = { une valeur constante jamais employée par ex -1 }

var FibTab : array [0..MAX] of integer ; { initialisé à undef }

function Fibmemo (n: integer ) : integer ;


{ Pré : 0 <= n <= MAX }
begin
if FibTab[n] = undef
then FibTab[n] := Fib(n) ;
Fibmémo := FibTab[n]
end ;

7.1.2 Avantages et inconvénient


 Avantages de la mémoïsation
• Technique facile à programmer.
• La transformation peut être automatisée.
• L’ordre de grandeur du temps d’exécution décroît (d’exponentiel à linéaire pour Fib) ou reste identique.
• Seuls les sous-problèmes utiles sont calculés

 Inconvénient
Il faut limiter le nombre de valeurs possibles des arguments pour avoir un tableau de taille raisonnable.

124 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique125

7.2 La récursivité ascendante


7.2.1 Principe
Pour éviter les test ( ... = undef ) on s’arrange pour que la valeur se trouve déjà dans la tableau au moment où
on en a besoin. Pour cela on calcule les appels en montant dans le graphe d’appels.

7.2.2 Avantages et inconvénients


 Avantages
• Petit gain de temps : élimination du test
• L’analyse du graphe d’appels permet souvent d’économiser de la mémoire.

 Inconvénients
• La forme du programme est complètement changée.
• Attention aux calculs inutiles !

 Calculs inutiles et gaspillage mémoire


Exemple 7.2.1
Calculer un logarithme entier blogk (n)c avec n ≥ 1, n ∈ N
Avec la méthode diviser pour régner
ilog(n) = 0 si 1 ≤ n ≤ k
ilog(n) = ilog(n div k) + 1 si n ≥ k
On a donc une complexité en T (n) = 1.T (n/k) + 1.n0 soit c = 1 = k 0 donc en O(log n)
Avec la récursivité ascendante, naïvement, on aurait tendance à calculer ilog pour tous les nombres
7
jusque n

for i := 1 to k-1 do ilogTab[i] := 0;


for i := k to n do ilogTab[i] := ilogTab[i div k] + 1

Ce programme est plus lent O(n) et demande plus de mémoire O(n) !


En effet, il calcule et retient des sous-problèmes inutiles (qui n’interviennent pas dans le résultat final)

Les sous-problèmes utiles sont ceux qui ont un chemin d’appels depuis le problème de départ.

31 décembre 2012 www.madit.be 125


1267.2 La récursivité ascendante BIHD3 - Meth Pgm

7.2.3 Économie de mémoire


• La taille du tableau dépend du nombre de valeurs possibles des paramètres. Il faut donc réduire ceux-ci. Les
paramètres qui ne changent pas (k pour ilog) peuvent être passés comme variable globale ou constante.
• Il n’est souvent pas nécessaire de garder tout le tableau, car il se peut que certains éléments ne soient plus
utilisés dans le futur. On peut les déterminer graphiquement :
1. On choisit un ordre de parcours
2. On trace une ligne séparant les éléments du tableau déjà calculés de ceux qui ne le sont pas encore
3. Les éléments calculés à conserver sont ceux qui sont le départ d’une flèche e qui traverse la ligne,
autrement dit qui seront utilisés par la suite.

7.2.4 Fibonacci en récursif ascendant


La passe ascendante appliquée à Fibonacci donne un tableau complet

var F: array [0..MAX] of integer;


F[0] := 0;
F[1] := 1;
for i:=2 to n do F[i] := F[i-2] + F[i-1];
Fib := F[n]

Ce qui donne un temps et une mémoire en O(n) comme la version memoïsée.


Or le graphe d’appel montre qu’il ne faut garder que les deux derniers éléments en mémoire. Donc posons un
nouveau tableau avec comme invariant
7 F [i mod 2] = F ib(i)
Ce qui donne toujours un temps en O(n) mais une (petite) mémoire constante :

var F: array [0..1] of integer;


F[0] := 0;
F[1] := 1;
for i:=2 to n do F[i mod 2] := F[i-2] + F[i-1];
Fib := F[n mod 2]

126 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique127

7.2.5 Combinaisons
Le nombre de combinaisons (ensembles) de m éléments choisis dans un ensemble de taille n (avec m ≤ n) est
noté ici Cnm . Certains le notent m

n ou inversent m et n.
Précondition : 0 ≤ m ≤ n
Postcondition : Cnm = ] { s ⊆ { 1..n } | ] = m }
Diviser pour régner nous permet de trouver une définition récursive :

1. Cnm = 0 si m > n ou m < 0 car il est impossible de prendre un nombre négatif d’objets ainsi que d’en
prendre plus de n.
2. Cn0 = 1 car seul l’ensemble vide ne contient aucun élément. Cn
3. Cnn = 1 car seul l’ensemble plein contient tous les objets.
m−1
4. Cnm = Cn−1m
+ Cn−1
car, si on ajoute un élément supplémentaire 0 n0 dans un ensemble de n − 1 éléments,
m
(a) Toutes les anciennes combinaisons Cn−1 restent valables (ce sont les cas où 0 n0 n’est pas pris).
m−1
(b) Les combinaisons qui contiennent 0 n0 sont au nombre de Cn−1 (car on prend n − 1 anciens).

Le temps d’exécution est :


O(1) pour les trois cas de base,
T (n) = 2T (n − 1) + O(1) = O(2n ) pour le cas récursif !
On peut constater des recalculs en dessinant le graphe d’appels où les mêmes appels sont fusionnés. Un noeud
avec plusieurs pères est un appel recalculé plusieurs fois.

C62 = 15

7
C52 = 10 C51 =5

C42 = 6 C41 = 4 C40 = 1

C32 = 3 C31 = 3 C30 = 1

C22 = 1 C21 = 2 C20 = 1

C11 = 1 C10 = 1

31 décembre 2012 www.madit.be 127


1287.2 La récursivité ascendante BIHD3 - Meth Pgm

On présente ce graphe dans le tableau de mémoïsation.


Par exemple, pour C41 on a besoin de garder l’information dans la zone en jaune

C62 = 15

C52 = 10 C51 = 5

C42 = 6 C41 = 4 C40 = 1

C32 = 3 C31 = 3 C30 = 1

C22 = 1 C21 = 2 C20 = 1

C11 = 1 C10 = 1

Ensuite, pour C52 . . .

C62 = 15

7
C52 = 10 C51 = 5

C42 = 6 C41 = 4 C40 = 1

C32 = 3 C31 = 3 C30 = 1

C22 = 1 C21 = 2 C20 = 1

C11 = 1 C10 = 1

Donc, garder seulement une diagonale de m + 1 éléments en mémoire suffit !


On peut même réduire encore la taille car on peut prouver que

m n−m
Cn = Cn

Par exemple C64 = C66−2 = C62 .


On choisi le plus petit m. Si m = 4, il est intéressant de prendre m = 2.
On aura donc toujours un m ≤ n/2.

128 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique129

Il vient donc le code suivant

function combinaisons (m,n : integer) : integer ;


var
C : array [0..MAX] of integer ;
j, i : integer ;
begin
{ choisir le plus petit m }
if m > n div 2 then m := n-m;
{ initialiser le tableau avec les cas de base }
for j := 0 to m do C[j] := 1 ;
{ double boucle pour le calcul }
for i := 1 to n-m do
{ i = index de la diagonale, donc n-m diagonales à traiter }
for j := 1 to m do
{ NB: C[1] reste toujours égal à 1 (cas de base) ! }
C[j] := C[j] + C[j-1]
combinaisons := C[m] ;
end ;

Remarques

1. L’invariant de la boucle for imbriquée (donc celle en j) est


k
k < j ⇒ C[k] = Ci+k+1
k
k ≥ j ⇒ C[k] = Ci+k

2. Le temps d’exécution est O(n ∗ m) ≤ O(n2 )

3. La place mémoire est linéaire en O(m) (en mémoisation simple, sans réduction elle est de O(n2 )).

4. Ceci est juste une illustration de la programmation dynamique ! On peut faire plus rapide (linéaire) et plus
7
économe en mémoire (constante) en se ramenant à la relation factorielle :
 
m n m!
Cn = =
m n!(m − n!)

31 décembre 2012 www.madit.be 129


1307.3 La programmation dynamique BIHD3 - Meth Pgm

7.3 La programmation dynamique


7.3.1 Principes
La programmation dynamique combine

• "diviser pour régner",


• puis la récursivité ascendante
• avec mise en mémoire.

On l’applique surtout sur des problèmes d’optimisation : trouver une solution faisable de coût minimal (ou de
gain maximal).

1. Trouver la récursion par diviser pour régner

(a) Dans le cas d’un problème d’optimisation :


i. trouver les solutions sur une partie plus petite (montrer que le problème de départ peut se
calculer à partir de solutions de sous-problèmes).
ii. trouver les contraintes sur chaque solution et montrer la faisabilité
iii. évaluer le coût de chaque solution

(b) Pour simplifier on ne renvoie que le coût optimal

(c) Limiter les paramètres pour réduire la taille du tableau.

2. Récursivité ascendante

(a) de la définition récursive, on déduit le graphe des dépendances.


7 (b) Si plusieurs chemins partent du même appel, il y a des recalculs donc on met en mémoire. Sinon, la
récursivité simple suffit.

(c) On choisit un ordre de calcul compatible avec les dépendances.

(d) On minimise l’emploi de mémoire en supprimant la mémoire qui ne sera plus utilisée, autrement dit
on ne garde que les départs d’une flèche qui franchit la frontière de calcul.

(e) On reconstruit la solution : chaque choix est mémorisé dans un tableau. A la fin, on reconstruit une
solution optimale grâce à ces choix.

130 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique131

7.3.2 Sac à dos discret


Un voleur pénètre par effraction dans un magasin. Il vous demande comment remplir son sac à dos pour maximiser
la valeur du contenu ?

 Données
• Charge utile (ou capacité) du sac : C ∈ N(nombre naturel)
• Il y a n articles dans le magasin. Pour chaque article du magasin i ∈ 1..n on a :
— sa valeur vi ∈ N (nombre naturel)
— son poids ti ∈ N+ (nombre naturel positif, sinon une infinité d’articles de poids nul peut être emporté)

 Résultat
Donner un tableau qi ∈ N (quantités emportées) qui maximise la valeur V du contenu :
n
X
V = qi ∗ vi
i=1

sous la contrainte que le poids total ne dépasser pas la capacité du sac :


n
X
qi ∗ ti ≤ C
i=1

 Formalisation récursive
Quelles sont les variables qui peuvent être rendues plus petites ?
– un plus petit sac à dos
– un magasin avec moins d’articles
Les données immutables sont placées en variables globales : le poids et la valeur 7
On pose gain(S, c) le gain maximum pour
– un ensemble S ⊆ { 1, . . . , n }
– une capacité c ∈ [0 . . . C]
Ces ensembles sont biens fondés, donc OK

 Cas de base
— Si le sac n’a pas de capacité, on ne peut rien y mettre : gain(S, 0) = 0
— Si le magasin est vide, on ne peut rien emporter : gain(ø, c) = 0

 Diviser : un élément et le reste


Soit un article k ∈ S : S 6= ø
Si l’article est trop lourd :
c < tk ⇒ gain(S, c) = gain(S\ { k } , c)

Sinon, on choisi la solution optimale entre prendre ou laisser :

c ≥ tk ⇒ gain(S, c) = max { gain(S, c − tk ) , gain(S\ { k } , c) }

31 décembre 2012 www.madit.be 131


1327.3 La programmation dynamique BIHD3 - Meth Pgm

 Choix de l’article et graphe des appels


On choisit de considérer les articles non plus comme un ensemble mais en les numérotant.
Pour diminuer la taille du tableau, donc on pose g(k, c) = gain({ 1 . . . k } , c)
cas de base → g(0, c) = 0
si c < tk → g(k, c) = g(k − 1, c)
sinon → g(k, c) = max { g(k, c − tk ) , g(k − 1, c) }

Il vient le tableau des appels :


– en ligne (vers la droite) : le même k et c augmente
– en colonne (vers le haut) : la même c mais k augmente

c 1 2 3 ... C
n g(n, 1) g(n, 2) g(n, 3) ... g(n, C)
n−1 g(n − 1, 1) g(n − 1, 2) g(n − 1, 3) ... g(n − 1, C)
..
.
3 g(3, 1) g(3, 2) g(3, 3) ... g(3, C)
2 g(2, 1) g(2, 2) g(2, 3) ... g(2, C)
1 g(1, 1) g(1, 2) g(1, 3) ... g(1, C)

Les couleurs montrent les dépendances de calcul, les valeurs vertes permettent de calculer les valeurs jaunes.

 Conséquences
L’implémentation récursive implique des recalculs si il y a deux façons différentes de diviser la même quantité.

7 Organisation des calculs :


– De gauche à droite (ligne par ligne car par rapport aux colonnes, les lignes sont de longueurs différentes).
– Une ligne suffit en mémoire.

var gain : array[0..C] of integer ;

 Invariants
• boucle extérieure (lignes) for k:= 0 to n do ...
0≤k≤n
gain j pour le k courant : ∀j ⇒ 0 ≤ j ≤ C : gain[j] = g(k, j)

• boucle intérieure (cellule) for j:= 0 to C do ...


0<k≤n
0≤j≤C
partie traitée : ∀i : 0 ≤ i < j ⇒ gain[i] = g(k, i)
partie non traitée : ∀i : j ≤ i ≤ C ⇒ gain[i] = g(k − 1, i)

 Temps d’exécution et mémoire


L’application des techniques de programmation dynamique dans ce cas précis donnent donc

• Espace mémoire : tableau de taille C → O(C).


• Temps d’exécution : n passages → O(nC).

Ce qui est coûteux dans les cas où C est grand.

132 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique133

 Améliorations
Si il y a un article i et un article j tels que i 6= j et

∀q : q ∗ ti ≤ tj ∧ q ∗ vi ≥ vj

Alors j ne sera jamais employé (car j est moins cher et plus lourd que i).
En généralisant cette constatation, si

 q ∗ t ≤ q ∗ t (plus léger)
i i j j
∃ qi , qj :
 q ∗ v ≥ q ∗ v (plus cher)
i i j j

alors j sera employé moins de qj fois, car sinon on le remplace par i.


L’idée est donc de calculer le "rapport qualité/prix" vi /ti .
Soit m l’article de meilleur rapport, tous les autres ont une borne qj telle que qj ≤ tm
car tj ∗ tm ≤ tm ∗ tj
et tj ∗ vm ≥ tm ∗ vj
par application des relations de la généralisation et car m est le meilleur rapport.
De même
qj ≤ vm

Soit d’abord un remplissage du maitre choix :


X
Cm = qj ∗ tj
j6=m

Le nombre d’article de type m est donc 


C − Cm
tm

7
Si C > Cm , alors on rempli le tableau pour boucher le trou. Soit on calcule pour chaque article (sauf le meilleur)
le nombre max de fois que l’on va l’employer.

31 décembre 2012 www.madit.be 133


1347.3 La programmation dynamique BIHD3 - Meth Pgm

7.3.3 Multiplication de matrices


Deux matrices se multiplient "ligne par colonne"
Si A = (aij )i=1..n,j=1..m est de taille n × m
et B = (bjk )j=1..m,k=1..p est de taille m × p
Pm
ALORS A × B = ( j=1 aij × bjk )i=1..n,k=1..p est de taille n × p
et est calculé en n ∗ m ∗ p multiplications (et n ∗ m − 1 ∗ p additions, mais elles sont plus rapides que le produit).

 Multiplier une chaine de matrices

M1 × M2 × M3 × · · · × Mn | Mi : pi−1 × pi

La multiplication matricielle n’est pas commutative, mais elle est associative : on peut choisir de mettre les
parenthèses comme on veut !
La question ici est de minimiser ce nombre de multiplications.
Exemple 7.3.1
Les matrices à multiplier, dans l’ordre :
A : 10 × 1
B : 1 × 10
C : 10 × 10
D : 10 × 1
E : 1 × 10

Par (((AB)C)D)E nous avons 1300 multiplications

7 AB = 10 × 1 × 10 = 100
.C = 10 × 10 × 10 = 1000
.D = 10 × 10 × 1 = 100
.E = 10 × 1 × 10 = 100
X
= 1300

En associant autrement : A(((BC)D)E) nous n’avons plus que 220 multiplications


BC = 1 × 10 × 10 = 100
.D = 1 × 10 × 1 = 10
.E = 1 × 1 × 10 = 10
A. = 10 × 1 × 10 = 100
X
= 220

 Spécification
Posons m(i, j) le nombre minimum de multiplications nécessaires pour calculer Mi Mi+1 . . . Mj
Notre objectif de départ s’écrit alors m(1, n).
La mesure bien fondée est j − i.
Cas de base : m(i, i) = 0.
Cas récursif : pour j > i on a :
m(i, j) = min { m(i, k) + m(k + 1, j) + pi−1 ∗ pk ∗ pj }
i≤k<j

Diviser pour régner tel quel est trop lent !

134 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique135

 Implémentation
Initialiser en variable globale un tableau des dimensions pour la suite de matrices reçues en entrées M1 (i, j) . . . Mn (i, j)

var p : array[0..MAX] of integer;

p[0] := M[0].i;
for k := 1 to n do p[k] := M[k].j ;

Pour notre exemple :

p[0] p[1] p[2] p[3] p[4] p[5]


10 1 10 10 1 10

La méthode d’appel utilise un tableau de dimensions n × n indicé [i, j] où seule la partie diagonale supérieure
est utilisée (les cas j ≥ i).
Son variant v = i − j est le nombre de multiplications de matrices.

{ initialisation de la diagonale aux cas de base }


for i := 1 to n do m[i,i] := 0;
{ pour chaque case de j>i recherche du minimum }
for v := 1 to n-1 do
for i := 1 to n-v do
m[i,i+v] := best(i,i+v) ;
{ le cas optimal est en m[1,n] }
result := m[1,n]

La méthode best utilise toutes les valeurs calculées précédemment dans le tableau. Il n’est pas possible de
diminuer le nombre de valeurs en mémoire. Elle s’exécute en O(n)

best(i, j : integer) : integer ;


var
m, m2, k : integer ;
begin
m := maxint ;
for k := i to j-1 do
7
begin
m2 := m[i,k] + m[k+1,j] + p[i-1] * p[k] * p[j] ;
if m2 < m then m := m2
end ;
best := m
end;

Soit m[i,j] pour notre exemple. Les couples de même couleur représentent les comparaisons succesives de m[i,k]
+ m[k+1,j] lors du dernier appel à best

i↓ A B C D E
j: 1 2 3 4 5
A 1 0 100 200 120 220
B 2 - 0 100 110 120
C 3 - - 0 100 200
D 4 - - - 0 100
E 5 - - - - 0

 Temps d’exécution
La double boucle for du programme d’appel exécute n ∗ n itérations d’appel à best (en O(n)).
Soit un temps d’exécution en O(n3 ) et un espace mémoire en O(n2 )
Diviser pour régner, pour énumérer tous les parenthèsages possibles aurait demandé un temps exponentiel de
O(2n ) (le nombre de partitions de n) .

31 décembre 2012 www.madit.be 135


1367.3 La programmation dynamique BIHD3 - Meth Pgm

136 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique137

7.4 Exercices
7.4.1 Questions du TP 5

1 Programmation dynamique
Exercice 1 (Récompense pour une reine (Examen juin 2008) )
Vous avez une reine que vous devez déplacer sur un échiquier nxn depuis le coin inférieur gauche de
coordonnées (1,1) jusqu’au coin supérieur droit (n,n). Vous pouvez la déplacer à droite, ou vers le haut,
ou en diagonale vers la droite et vers le haut, d’autant de cases que vous le voulez.
Vous ne pouvez pas ni descendre ni aller à gauche. Aprés chaque déplacement, vous recevez le nombre
entier positif inscrit dans la case d’arrivée.
– On demande de donner le nombre de trajets possibles
– On demande de maximiser la somme des points récoltés et de donner le trajet correspondant.

Exercice 2 (Problèmes du voyageur de commerce ou facteur)


On considère un voyageur de commerce qui doit visiter les entreprises x1 , . . . , xn avant de rentrer chez lui
ou un facteur qui doit aller vider les boîtes aux lettres x1 , . . . , xn avant de ramener le courrier à la poste.
Dans les deux cas, on note le point de départ (le domicile du voyageur ou le bureau de poste) x0 et di,j ,
pour 0 ≤ i, j ≤ n, la distance (ou le temps de parcours) de xi à xj . Le but est de trouver un parcours
minimal partant de x0 , visitant tous les autres lieux, et revenant à x0 .

Il faut bien sûr généraliser ce problème avant de pouvoir le résoudre par récurrence, puis utiliser une
approche ascendante. On peut faire cela en calculant des valeurs T ∗ (i, S) où 0 ≤ i ≤ n et S est un sous-
ensemble de {1, . . . , n} d’incluant pas i. Chaque T ∗ (i, S) représente le temps minimal pour, en partant
de xi , visiter tous les lieux xj tels que j ∈ S puis aller en x0 . Les valeurs T ∗ (i, ∅) peuvent être calculées
directement ; pour les valeurs T ∗ (i, S) où S n’est pas vide, on peut utiliser une récurrence.

Implémenter une fonction qui résout ce problème présente plusieurs défis : outre la conception d’une
définition récursive pour les T ∗ (i, S), il faut également construire une représentation en Turbo-Pascal pour
les ensemles S, ainsi qu’ajouter de l’information pour que, au final, la fonction puisse afficher les détails
7
des parcours de longueur minimale.

Exercice 3 (Multiplication de matrices)


Si A est une matrice m X n et B, une matrice n X p, calculer le produit AB nécessite mnp multiplications.
Calculer le produit d’une séquence de matrices A1 . . . An peut donc nécessiter un nombre très important
d’opérations. Toutefois, comme la multiplication matricielle est associative, cela peut se faire de plusieurs
façons différentes. Par exemple, ABC peut être calculé comme (AB)C ou comme A(BC). Si A est de
taille 10 X 100, B est de taille 100 X 5 et C est de taille 5 X 50, le premier calcul, (AB)C donne lieu à
7500 multiplications, alors que le second, A(BC), en demande 75000, dix fois plus.

Il est donc intéressant de trouver la manière la plus économique de découper la séquence A1 . . . An pour
minimiser le nombre de multiplications. Cela peut se faire de manière récursive : une parenthésation opti-
male de A1 . . . An découpe la séquence en A1 . . . Ai et Ai+1 . . . An et parenthèse ces deux sous-séquences
de manière optimale (le principe d’optimalité est vérifié). Et cette méthode peut être améliorée grâce à la
programmation dynamique.

31 décembre 2012 www.madit.be 137


1387.4 Exercices BIHD3 - Meth Pgm

Exercice 4 (Avance, petit robot)


On considère une grille carrée de dimensions taille × taille (où taille est une constante relativement
petite). Sur cette grille se déplace un petit robot. Ce petit robot avance d’une case à la fois horizontalement
ou verticalement. On trouve, également sur la grille, des récompenses et des obstacles (un obstacle se place
sur une et une seule case de la grille). Il est évident que le petit robot ne peut pas passer sur une case où
se trouve un obstacle.
La question principale est la suivante : “Etant donnés un point de départ et un point d’arrivée, trouver
le 1 trajet le plus court qui maximise la récompense pour le petit robot entre ces deux points.”

Exercice 5 (La plus longue sous-suite commune : Exercice guidé)


Dans plusieurs domaines d’application (en génétique par exemple), on trouve des problèmes qui se ra-
mènent à chercher la plus longue sous-séquence commune de deux séquences d’objets (comparaison d’ADN
par exemple). Dans le cas des séquences de lettres (ou mots) habracadabra et chandelier, la plus longue
sous-séquence commune est cadr, qui se trouve bien à la fois dans habraCADabRa et dans ChAnDelieR.

1.1 Généralisation du problème et formulation récursive


Soit a = a1 . . . an et b = b1 . . . bm deux séquences (d’entiers ou de caractères par exemple). Avant de
rechercher la plus longue sous-séquence commune, on va étudier la longueur de cette plus longue sous-
séquence commune.

1. Il est naturel de penser que la plus longue sous-séquence commune à a et b doit pouvoir se construire
à partir des plus longues sous-séquences communes à certains préfixes de a et préfixes de b. On pose donc
la notation Li,j , avec 0 ≤ i ≤ n et 0 ≤ j ≤ m, pour désigner la longueur de la plus longue sous-séquence
commune aux préfixes a1 . . . ai et b1 . . . bj . Pour quels i et j peut-on donner la valeur de Li,j directement ?
A quoi correspond la solution du problème principal ?

2. Il reste à traiter le cas récursif. Quels i et j sont concernés ? Et comment peut-on calculer Li,j à
partir des "valeurs plus petites" dans ces cas-là ?
Indice. On peut séparer les cas récursifs en deux sous-cas, selon que ai = bj ou non par exemple, c’est-à-
dire selon que les dernières lettres des préfixes considérés sont égales ou non. Si elles le sont, on obtient

7 une réponse assez facilement. Sinon, la plus longue sous-séquence commune ne contient pas ai et bj en
même temps ; cela permet de se ramener à deux sous-cas.

3. Construire une fonction récursive qui calcule les Li,j puis une autre qui indique la longueur de la
plus longue sous-séquence commune aux tableaux a[1..n] et b[1..m].

4. Transformer les fonctions de l’exercice précédent en fonctions qui donnent non seulement la longueur
de la plus longue sous-séquence commune, mais également une liste chaînée contenant les valeurs de cette
sous-séquence.

1.2 Organisation ascendante


5. Repérer quels sont les calculs qui sont répétés lors de l’exécution de fonctions récursives du point
précédent.

6. Construire une fonction qui remplit le (une partie du) tableau long avec les valeurs Li,j de "bas en
haut".

Comme indiqué plus haut, pour pouvoir reconstruire la plus longue sous-séquence commune, il faut
plus d’information : les Li,j ne suffisent pas. Plutôt que de créer un tableau dans lequel on stockerait
toutes les sous-séquences communes des préfixes a et b, on peut se contenter d’informations nécessitant
moins d’espace-mémoire.

7. Il y a trois sous-cas au cas récursif de calcul de Li,j . Il suffit de se souvenir de quel sous-cas a été
utilisé pour pouvoir reconstruire la sous-séquence commune la plus longue. Vérifier que c’est bien le cas.
1. Il est clair que celui-ci n’est pas nécessairement unique ... mais, on désire en connaître un seul.

138 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique139

Par la suite, on numérote les sous-cas 1, 2 et 3 et on note Ci,j l’identifiant du sous-cas utilisé.

8. Modifier la fonction précédente remplir_long pour qu’elle remplisse non seulement long mais aussi
cas, une variable globale déclarée par

9. Finalement, construire une fonction qui résout le problème initial en affichant non seulement la
longueur mais aussi les éléments de la plus longue sous-séquence commune.

31 décembre 2012 www.madit.be 139


1407.4 Exercices BIHD3 - Meth Pgm

7.4.2 Codes C de solutions


 Multiplication de matrices

1 #include <stdio.h>
2 #include <limits.h>
3 #define nm 10
4
5 #define mini(a,b) a<b? a:b
6
7 int NBCALL1=0;
8 int NBCALL2=0;
9 int t[nm+1];
10
11 int ML[nm+1][nm+1];
12 int MAT[nm+1][nm+1];
13 int PRNTSE[nm+1][nm+1];
14
15 // Fonction récursive naïve calculant le nombre de multiplication
16 int Cout_mul (int i, int j) {
17 int m3;
18 NBCALL1++;
19 if (i==j) return 0;
20 else {
21 int M =INT_MAX;
22 for (int k =i; k<j; k++) {
23 m3 = Cout_mul(i,k)+ Cout_mul(k+1,j) + t[i]*t[k+1]*t[j+1];
24 if( M>m3) M=m3;
25 }
26 return M;
27 }
28 }
29
30
31 // Fonction récursive avec mémoïsation
32 int Cout_mul2 (int i, int j) {
7 33
34
int m1;
NBCALL2++;
35 if (i==j) return 0;
36 else {
37 int M = INT_MAX;
38
39 for (int k =i; k<j; k++) {
40 if (ML[i][k]==0 ) ML[i][k] = Cout_mul2(i,k);
41 if (ML[k+1][j]==0) ML[k+1][j] = Cout_mul2(k+1,j);
42 m1 = ML[i][k]+ML[k+1][j] + t[i]*t[k+1]*t[j+1];
43 if( M>m1) M=m1;
44 }
45 ML[i][j]=M;
46 return M;
47 }
48 }
49
50 // Fonction itérative
51 int Cout_mul3 (int n) {
52 int m1 ;
53 int i,j,k,d;
54 for (i=1; i<=n ; i++) PRNTSE[i][i] =0;
55 for (i=1; i<=n ; i++) MAT[i][i] =0;
56 /**** remplissage des éléments de MAT diagonale par diagonale ***/
57 for (d=2; d<=n; d++) // d : numéro d'une diagonale
58 for (i=1; i<=n-d+1; i++) { // i : numéro de ligne pour un élément d'une diagonale d
59 j=i+d-1; // j : numéro de colonne pour un élément d'une diagonale d
60 MAT[i][j]=INT_MAX;
61 for (k=i; k<j;k++) {
62 m1= MAT[i][k] + MAT[k+1][j] + t[i]*t[k+1]*t[j+1];
63 if (m1<MAT[i][j]) {
64 MAT[i][j]= m1;
65 PRNTSE[i][j]=k;
66 }
67 }
68 }

140 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique141

69 return MAT[1][n];
70 }
71
72 // Fonction affichage
73 void affichage(int i, int j) {
74 if (i==j) printf(" %d ", i );
75 else {
76 printf("(");
77 affichage(i,PRNTSE[i][j]);
78 printf(" * ");
79 affichage(PRNTSE[i][j]+1,j);
80 printf(")");
81 }
82 }
83
84 void main() {
85 int i,j;int n=6;
86 t[1]= 10; t[2] =100 ;t[3] =5 ;t[4] =50 ;t[5] = 10;t[6] =5 ;t[7] =18 ;
87 /***************************************/
88 printf(" \n FN REC naive : le nombre de multiplication est %d ", Cout_mul(1,n));
89 printf(" \n le nombre d'appel est %d \n\n\n", NBCALL1);
90 /*****************************************/
91 for( i=1 ; i<=n;i++)for( j = 0 ; j<=n;j++) ML[i][j]=0;
92 printf(" \n FN REC avec MEMO : le nombre de multiplication est %d ", Cout_mul2(1,n));
93 printf(" \n le nombre d'appel est %d \n\n\n", NBCALL2);
94 for (i=1; i<=n ; i++) {
95 printf(" \n");
96 for (j=1; j<=n ; j++) printf(" %d ", ML[i][j]);
97 }
98 /*************************************/
99 printf(" \n \n \n FN ITER : le nombre de multiplication est %d \n\n\n", Cout_mul3(n));
100 for (i=1; i<=n ; i++) {
101 printf(" \n");
102 for (j=1; j<=n ; j++) printf(" %d ", MAT[i][j]);
103 }
104 printf(" \n\n");
105 for (i=1; i<=n ; i++) {
printf(" \n");
106
107
108 }
for (j=1; j<=n ; j++) printf(" %d ", PRNTSE[i][j]); 7
109 printf(" \n\n\n\n\n\n");
110 affichage(1,n);
111 }

31 décembre 2012 www.madit.be 141


1427.4 Exercices BIHD3 - Meth Pgm

 la plus longue sous-suite commune

1 #include <stdio.h>
2 #include <stdlib.h>
3 #define maxi(a,b) a>b? a:b
4
5 int a[15],b[15]; int c[15][15];int cas[15][15];
6
7 struct cell { int val; struct cell * suiv; };
8 typedef struct cell CELL;
9 typedef struct cell * liste;
10
11
12 void ajout(int val, liste *l) {
13 liste ele=(liste) malloc(sizeof(cell));
14 ele->val = val;
15 ele->suiv = *l;
16 *l=ele;
17 }
18
19
20 void concat(liste l1, liste *l){
21 if (l1 != NULL) {
22 concat(l1->suiv, l);
23 liste ele=(liste) malloc(sizeof(cell));
24 ele->val = l1->val;
25 ele->suiv = *l;
26 *l=ele;
27 }
28 }
29
30 void afficher(liste l) {
31 if(l!=NULL) {
32 printf("%d ", l->val);
33 afficher(l->suiv);
34 }
7 35
36
}

37 // Question 3 : Fonction récursive naïve calculant la longueur de la la plus longue sous-suite←-


commune entre a et b
38 int Long_PLSSC_rec (int i, int j) {
39 if (i==0 ||j==0) return 0;
40 else {
41 if (a[i]==b[j]) return 1+ Long_PLSSC_rec(i-1,j-1);
42 else {
43 int x = Long_PLSSC_rec(i-1,j);
44 int y = Long_PLSSC_rec(i,j-1);
45 return maxi( x,y);
46 }
47 }
48 }
49
50 /*************Question 4 : Calcul avec sauvegrade des éléments de la PLSC *******/
51 int Long_PLSSC_rec2 (int i, int j, liste *l) {
52 liste l1 =NULL; liste l2= NULL;
53 if (i==0 ||j==0) return 0;
54 else
55 if (a[i]==b[j]) {
56 ajout(a[i],l);
57 return 1+ Long_PLSSC_rec2(i-1,j-1,l);
58 }
59 else {
60 int x = Long_PLSSC_rec2(i-1,j,&l1);
61 int y = Long_PLSSC_rec2(i,j-1,&l2);
62 int z = maxi( x,y);
63 if (z==x ) concat(l1, l) ;
64 else concat(l2, l) ;
65 return z;
66 }
67 }
68
69 /*************Question 5 : *******/

142 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique143

70 void remplir(void) {
71 int i,j;
72 for(i=0; i<15; i++)
73 for( j=0; j<15; j++)
74 c[i][j] = 0;
75 for(i=1; i<15; i++)
76 for( j=1; j<15; j++)
77 if (a[i]==b[j]) c[i][j]=c[i-1][j-1] +1;
78 else c[i][j] = maxi(c[i-1][j],c[i][j-1]);
79 }
80
81 /*************Question 6 : *******/
82 void remplir2() {
83 int i,j;
84 for(i=0; i<15; i++)
85 for(j=0; j<15; j++)
86 { c[i][j] = 0;cas[i][j]=0; }
87 for(i=1; i<15; i++)
88 for( j=1; j<15; j++)
89 if (a[i]==b[j]) {
90 c[i][j]=c[i-1][j-1] +1;
91 cas[i][j]=1;
92 }
93 else {
94 c[i][j] =maxi(c[i-1][j],c[i][j-1]);
95 if (c[i][j]==c[i-1][j]) cas[i][j]=2;
96 else cas[i][j]=3;
97 }
98 }
99
100 void affiche2( ) {
101 int i=15; int j=15;
102 while ((i>=1) && (j>=1))
103 if(cas[i][j]==1) {
104 printf("%d ", a[i]);
105 i--;j--;
106 }
else
107
108
109
if(cas[i][j]==2) i--;
else j--;
7
110 }
111
112 void main() {
113 int i,j;
114 liste l =NULL;
115 a[1]=8;a[2]=1;a[3]=2;a[4]=18;a[5]=1;a[6]=3;a[7]=1;a[8]=4;a[9]=1;a[10]=2;a[11]=18;a[12]=1;a←-
[13]=1;a[14]=2;
116 b[1]=3;b[2]=8;b[3]=1;b[4]=15;b[5]=4 ;b[6]=5 ;b[7]=12 ;b[8]=9 ;b[9]=5 ;b[10]=18 ;b[11]=1;b←-
[12]=1;b[13]=1;b[14]=1;
117 printf("\n\n\n FN REC naive: La longueur de la PLSSC est %d \n", Long_PLSSC_rec(14,14));
118 printf("\n\n\n FN REC naive avec sauvegarde: La longueur de la PLSSC est %d \n", ←-
Long_PLSSC_rec2(14,14,&l));
119 printf("\n la PLSSC est : ");
120 afficher(l);
121 printf("\n\n\n\n");
122 remplir();
123 for( i=1; i<15;i++) {
124 printf("\n");
125 for( j=1; j<15;j++) printf(" %d ", c[i][j]);
126 }
127 printf("\n\n\n\n");
128 printf("\n\n\n\n");
129 remplir2( );
130 for( i=1; i<15;i++) {
131 printf("\n");
132 for( j=1; j<15;j++) printf(" %d ", cas[i][j]);
133 }
134 printf("\n\n\n\n");
135 affiche2();
136 }

31 décembre 2012 www.madit.be 143


1447.4 Exercices BIHD3 - Meth Pgm

 Récompense pour une reine (question d’examen 2008)

1 #include <stdio.h>
2 #define n 5
3 #define mini(a,b) a<b ? a : b
4 int NBCALL1=0;
5 int NBCALL2=0;
6 int NBCALL3=0;
7 int NBCALL4=0;
8 int t[n+1][n+1];
9
10 // Fonction récursive naïve calculant le nombre de trajets (déplacement d'un seul pas)
11 int NBT_rec (int i, int j) {
12 NBCALL1++;
13 if (i==1 ||j==1) return 1;
14 return NBT_rec(i-1,j)+NBT_rec(i-1,j-1)+NBT_rec(i,j-1);
15 }
16
17 // Fonction récursive avec mémoïsation calculant le nombre de trajets (déplacement d'un seul ←-
pas)
18 int NBT_Memo (int i, int j) { // un seul pas à la fois
19 int x, y, z;
20 NBCALL2++;
21 if ((i==1) ||(j==1)) return 1;
22 else
23 if (t[i-1][j] == 0) t[i-1][j]= NBT_Memo(i-1,j);
24 x=t[i-1][j];
25 if (t[i-1][j-1] == 0) t[i-1][j-1]= NBT_Memo(i-1,j-1);
26 y=t[i-1][j-1];
27 if (t[i][j-1] == 0) t[i][j-1]= NBT_Memo(i,j-1);
28 z=t[i][j-1];
29 t[i][j]=x+y+z;
30 return t[i][j];
31 }
32 }
33

7 34 // Fonction récursive naïve calculant le nombre de trajets (déplacement de plsuieurs pas à la ←-


fois possible)
35 int NBT_pp_rec(int i, int j) {// plusieurs pas possibles
36 NBCALL3++;
37 if ((i==1) && (j==1)) return 1;
38 else {
39 int s1,k; s1=0; int s2=mini(i,j);
40 k=j-1; while( k>=1 ) { s1+=NBT_pp_rec(i,k); k--; }
41 k=i-1; while( k>=1 ) { s1+=NBT_pp_rec(k,j); k--; }
42 k=1; while( k<s2) { s1+=NBT_pp_rec(i-k,j-k); k++;}
43 return s1;
44 }
45 }
46
47 // Fonction récursive avec mémoïsation calculant le nombre de trajets (déplacement de ←-
plsuieurs pas à la fois possible)
48 int NBT_pp_memo(int i, int j) { // plusieurs pas possibles
49 NBCALL4++;
50 if ((i==1) && (j==1)) return t[i][j];
51 else {
52 int s1,k; s1=0;
53 int s2=mini(i,j);
54 k=j-1;
55 while( k>=1 ){
56 if (t[i][k] ==0) s1+=NBT_pp_memo(i,k);
57 else s1+=t[i][k];
58 k--;
59 }
60 k=i-1;
61 while( k>=1 ){
62 if (t[k][j] ==0) s1+=NBT_pp_memo(k,j);
63 else s1+=t[k][j];
64 k--;
65 }
66 k=1;
67 while( k<s2 ) {

144 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 7. Programmation dynamique145

68 if (t[i-k][j-k] ==0) s1+=NBT_pp_memo(i-k,j-k);


69 else s1+=t[i-k][j-k];
70 k++;
71 }
72 t[i][j]=s1;
73 return s1;
74 }
75 }
76
77 void main() {
78 int i,j;
79 /**********/
80 printf("\n FN REC naive (un seul pas) : Le nombre de trajet pour n= %d est %d \n",n, ←-
NBT_rec(n,n));
81 printf("\n \n FN REC naive (un seul pas): Le nombre d'appels est %d \n \n",NBCALL1);
82 /*****************/
83 for( i=1; i<=n; i++)
84 for ( j=1; j<=n; j++)
85 t[i][j]=0;
86 for( i=1; i<=n; i++) {
87 t[i][1]=1;
88 t[1][i]=1;
89 }
90 for(i=1; i<=n; i++) {
91 printf(" \n " );
92 for (j=1; j<=n; j++) printf(" %d",t[i][j]);
93 }
94 printf("\n\n\n FN REC avec MEMO (un seul pas): Le nombre de trajet pour n= %d est %d \n",n←-
, NBT_Memo(n,n));
95 for(i=1; i<=n; i++) {
96 printf(" \n " );
97 for (j=1; j<=n; j++) printf(" %d",t[i][j]);
98 }
99 printf("\n \n FN REC avec MEMO (un seul pas): Le nombre d'appels est %d \n \n",NBCALL2);
100 /************************************/
101 printf("\n \n FN REC naive (plusieurs pas possibles): Le nombre de trajet pour n= %d est %←-
d \n",n, NBT_pp_rec(n,n));
printf("\n \n FN REC naive (plusieurs pas possibles): Le nombre d'appel est %d \n \n",←-
102

103
NBCALL3);
/********************************************************/
7
104 for(i=1; i<=n; i++)
105 for (j=1; j<=n; j++)
106 t[i][j]=0;
107 t[1][1]=1;
108 printf("\n \n \n FN memo (plusieurs pas ): Le nombre de trajet pour n= %d est %d \n",n, ←-
NBT_pp_memo(n,n));
109 printf("\n \n FN REC avec MEMO ( plusieurs pas) : Le nombre d'appels est %d \n \n",NBCALL4←-
);
110 printf(" \n\n\n\n\n " );
111 for(i=1; i<=n; i++) {
112 printf(" \n " );
113 for (j=1; j<=n; j++) printf(" %d",t[i][j]);
114 }
115 printf(" \n\n\n\n\n " );
116 }

31 décembre 2012 www.madit.be 145


1467.4 Exercices BIHD3 - Meth Pgm

146 www.madit.be 31 décembre 2012


8
Algorithmes gloutons

Sommaire
8.1 Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.2 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

147
1488.1 Principes BIHD3 - Meth Pgm

8.1 Principes
De manière intuitive, prendre la plus grande quantité possible d’un maitre-choix en trouvant localement un choix
qu’on peut toujours faire dans un optimum.
Ne marche pas très souvent (< 5% des cas).

 Les exemples
8.2.1 Sac à dos continu borné* : Prendre d’abord les articles de meilleur rapport valeur/poids.
8.2.2 Pleins* : Faire le plein le plus tard possible.
8.2.4 Salle de spectacle* : Prendre l’activité qui se termine le plus tôt.
8.2.5 Codes de Huffman* : Fusionner les sous-arbres de moindre fréquences.
8.2.7 Voyageur de commerce : Commencer par la ville la plus proche.

 Correct ?
Pour les problèmes marqués d’une * la solution est optimale. Sinon il donne une solution faisable, mais pas
optimale.

 Preuve
Le schéma d’une preuve gloutonne a deux étapes.
On prouve que :

1. Il y a un optimum qui contient le premier choix fait. Pour cela :

• on suppose un optimum,
• et on montre que "notre" solution a la même valeur d’objectif

2. L’optimum pour le reste peut se trouver récursivement


3. En le combinant avec le 1er choix, on trouve l’optimum.
8 C’est une preuve de type C1 (trouver un élément du résultat, puis trouver le reste du résultat)
qui donnera donc une récursion terminale équivalente à une boucle.

8.2 Exemples
8.2.1 Sac à dos continu ou borné
A partir de l’exemple du sac à dos discret 7.3.2 page 131

 Continu
Dans le cas où les articles sont vendus au poids ou au mètre, les qi peuvent donc être fractionnaires.
⇒ la solution est triviale : prendre uniquement l’article de meilleur rapport ⇔ qm = C/tm

 Borné
Si une limite est imposée sur la quantité présente dans le magasin : qi ≤ Qi .
⇒ quand le stock du maitre choix est épuisé, prendre les articles selon les rapports décroissants jusqu’à remplir le
sac (ou vider le magasin).

148 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons149

8.2.2 Les pleins d’essence de Donald


Voir le livre de référence [Cor04, exercice 16.2.4]

 Objectif
Donald a planifié son itinéraire de vacances, le long duquel il a relevé le kilométrage des stations d’essence.
Il veut s’arrêter le moins possible, sans tomber en panne sèche.

 Données
• Une capacité de réservoir CM ≥ 0. Par facilité on suppose une consommation constante ; cette capacité est
donc exprimée en kilomètres.
• Le contenu du réservoir au départ C0 avec 0 ≤ C0 ≤ CM .
• La distance à parcourir L ≥ 0
• La position des n stations Si . . . Sn le long de la route avec
◦ 0 ≤ Si < Si+1 < · · · < L
◦ S0 = 0

Res
CM
C0

... km
S0 S1 S2 S3 S4 L

 Analyse
On attend
8
• Soit un tableau q contenant les quantités achetées qi à chaque station i avec 0 ≤ qi ≤ CM
• Soit de signaler qu’il n’y a pas de solution faisable.

La fonction C(l) donne le contenu du réservoir en fonction du kilométrage :


X
C(l) = C0 + qi − l
Si <l

Si l est la position d’une station, il s’agit du contenu à l’entrée de la station.


Le contenu du réservoir en sortant de la station k est donc :

Ck = C(Sk ) + qk

Les arrêts A sont alors les stations où on achète de l’essence

A = { i ∈ 1..n | qi > 0 }

Le problème est de minimiser le nombre d’arrêts |A| sans tomber en panne avant de rallier l’arrivée :

∀ l, 0 ≤ l < L ⇒ C(l) ∈ [0..CM ]

ce qui constitue un invariant (une contrainte) le long du trajet mais pas dans la planification.

31 décembre 2012 www.madit.be 149


1508.2 Exemples BIHD3 - Meth Pgm

 Recherche de caractéristiques gloutonnes


Rechercher d’une solution optimale gloutonne S 0 et en calculer l’impact au niveau optimisation et faisabilité par
rapport à une solution S !
impact(S 0 ) ≤ impact(S)

(1) Si on s’arrête, autant faire le plein


Soit q un optimum.

CM
CM
C0
q

... km
S0 S1 S2 S3 S4 L

q 0 modifie q en faisant le plein chaque fois que q s’arrête :

qi > 0 ⇒ qi0 = CM − C 0 (Si )

Alors q 0 est aussi optimale.

res
CM
C0 q0
q

8
... km
S0 S1 S2 S3 S4 L

Preuve :
A0 = A donc la valeur de l’objectif est la même.
q 0 reste faisable (on ne tombe pas en panne) car CM ≥ C 0 (l) ≥ C(l) ≥ 0.

150 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons151

(2) Si on a de quoi atteindre la prochaine station, pas d’arrêt


Si C(l) ≥ (Sk+1 − Sk ), alors soit q” une solution où q”k = 0 (on ne s’arrête pas en k) et q”k+1 = qk + qk+1 (on
achète à la station suivante).
Etant donné le choix de la première station, on résoud récursivement avec une station en moins.

res
CM
C0 q”
q

? ?
... km
S0 S1 S2 S3 S4 L

Preuve
La fonction C”(l) est identique à C(l) sauf entre Sk et Sk+1 , où C”(l) = C(l) − qk , mais elle satisfait toujours la
contrainte.
Le variant de la récursion : le nombre de stations diminue.
La récursion est terminale car il est inutile de revenir en arrière. On peut donc l’implémenter avec une boucle.

 Implémentation

C := C0 ;
{ étude des choix à chaque station S[i] }
for i := 1 to n do begin
{ de quoi arriver à cette station ? Valable surtout pour la première ! }
if C < (S[i] - S[i-1])
then panneEssence
{ si oui, dois-je y faire le plein ? }
else if (S[i+1] - S[i-1]) > C
then begin { je fais le plein }
q[i] := CM - (C - (S[i] - S[i-1])) ;
C := CM
8
end
else begin { non ; mais mise à jour des variables }
q[i] := 0 ;
C := C - (S[i] - S[i-1]) ; { ce que j'ai consommé }
end

 Complexité
Temps d’exécution : O(n)
Mémoire : O(1) si on néglige les données S[1..n],q[1..n] que tous les autres algo doivent aussi avoir !

31 décembre 2012 www.madit.be 151


1528.2 Exemples BIHD3 - Meth Pgm

8.2.3 Les pleins d’essence de Picsou


 Objectif
Ce n’est plus Donald mais Picsou qui planifie son itinéraire de vacances.
Il veut que le carburant lui coute le moins cher possible, sans tomber en panne sèche.

 Données
On ajoute le prix du carburant à chaque station pi > 0

 Analyse
Le coût à minimiser est
n
X
pi ∗ qi
i=1

Noter que cet objectif encourage à arriver avec un réservoir vide.

 Choix glouton
Remplir le plus possible à la station m la moins chère :

• On arrive à vide (sauf si C0 permet d’atteindre m)


• et on y fait le plein, sauf si on peut atteindre la destination finale (calcul).

Pour résoudre les trajets avant et après m on fait deux appels récursifs.

 Temps
Retrouver la station la moins chère est linéaire, ce qui mène a un coût total quadratique O(n2 ).

 Solution plus rapide

8 Un arbre rouge-noir trié sur le kilométrage des stations et augmenté par le prix minimum des sous-arbres permet
de retrouver la station de prix minimum entre Si et Sj en temps O(n log n).

arn = ^node;
node = record
sta : int ; { position de la station, en km depuis le départ }
prix : real ; { prix au km, pour rester cohérent}
g : arn ;
d : arn ;
prix_min_g : real ; { prix min à gauche }
prix_min_d : real ; { prix min à droite }
red : boolean
end ;

L’implémentation des rotations sera plus complexe !

152 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons153

8.2.4 Ordonnancement d’activités


Voir le livre de référence [Cor04, § 16.1]
On suppose un responsable qui doit rentabiliser sa salle de spectacles en acceptant le plus possible d’activités
possible.

 Données
Une liste d’activités candidates ki (1 ≤ i ≤ n) avec pour chacune :
– si : temps de début
– fi : temps de fin (avec si ≤ fi )

k2
k7 k6
k3
k1 k4
k5

 Analyse
Le résultat doit produire un ensemble A ⊂ { 1 . . . n } de taille maximale d’activités compatibles (soit chronologi-
quement disjointes)
⇔ ∀ i, j ∈ A : si ≥ fi ∨ sj ≥ fi

 Choix glouton
Faisable ?
Une solution optimale contient l’activité k qui (commence et) se termine le plus tôt.
A partir d’une solution optimale A et m sa première activité, on a A0G = A\ { m } ∪ { k }
⇒ A0G reste optimale.
Après avoir choisi k, on résoud récursivement pour les activités compatibles avec k.
8
Le variant est le nombre d’activités qui diminue (les incompatibles disparaissent)

 Algorithme
• Trier les activités par temps de fin (en O(n log n)).
• A := { }
• f := −1
• Pour chaque activité i prise dans cet ordre (O(n))

si ≥ f ⇒ A := A ∪ { i } ∧ f := fi

On obtient donc un temps d’exécution total de l’ordre de O(n log n).

31 décembre 2012 www.madit.be 153


1548.2 Exemples BIHD3 - Meth Pgm

8.2.5 Codes de Huffman


Voir le livre de référence [Cor04, § 16.3]

 Codage de caractères
Fixe : p.ex. ISO-Latin-1 fixe 8 bits pour chaque caractère.
Morse : les caractères plus fréquents (par ex. ’E’) ont un code plus court.
Le problème du morse est de séparer deux caractères :

Car c’est un code préfixé, l’un est le début de l’autre !

• ¯

e t

i a n m

8 s u r w d k g o

h v f l p j b x c y z q

On cherche donc un code sans préfixe !

 Calcul du code optimal


Données :

• Un alphabet C, c’est-à-dire un ensemble de caractères c.


• Pour chacun, une fréquence f [c] dépendant du langage

Résultat : Un code pour chaque caractère tel que

1. aucun code n’est préfixe d’un autre


2. et la longueur du texte Lt est minimum (d(c) est la longueur du code de c) :
X
Lt = d(c) ∗ f (c)
c∈C

154 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons155

Exemple 8.2.1
C = { a, b, c, d, e, f } et f = 45, 13, 12, 16, 9, 5 (soit un total de 100%).
codage ISO-Latin-1 : 8 ∗ (f )100 = 800 bits.
calcul du nombre de bits pour un codage fixe :

bits = dlog2 |C|e

donc codage fixe sur 3 bits : 3 ∗ (f )100 = 300 bits (dlog2 |C|e = 3).
codage optimal = 224 bits (en multipliant chaque longueur de code par sa fréquence)

a b c d e f
0 101 100 110 1111 1110

Soit la représentation binaire suivante

0 1

a 0 1

0 1 0 1

c b d 0 1

f e

 Lemmes

Lemme 8.1
8
Un code est sans préfixe ssi un caractère n’apparaît que sur une feuille de l’arbre.

Lemme 8.2
Dans un code sans préfixe optimal un noeud a 0 ou 2 fils. En effet, dans le cas contraire, on peut "raccourcir la
branche".

31 décembre 2012 www.madit.be 155


1568.2 Exemples BIHD3 - Meth Pgm

 Commencer par les moins fréquents


Si x, y sont les deux caractères les moins fréquents alors il existe une solution optimale où x et y ont deux codes
les plus longs et diffèrent seulement par le dernier bit.
Preuve : Soit b, c deux codes les plus longs qui ne diffèrent que par le dernier bit.

Soit pour x (idem pour y)

X X
cout(T ) − cout(T 0 ) = f [c] ∗ d(c) − f [c] ∗ d0 (c)
c c
= f [x] ∗ d(x) + f [b] ∗ d(b) − f [x] ∗ d0 (x) − f [b] ∗ d0 (b)
or d0 (x) = d(b)
= (f [b] − f [x]) ∗ (d(b) − d(x))
= (≥ 0) ∗ (≥ 0) (car x moins fréquent et b plus profond)

⇒ faisable car une permutation de feuille ne crée pas de préfixe


⇒ optimal car fréquences moins élevées = plus long

 Récursion
Après avoir regroupé les deux caractères les moins fréquents, peut-on se ramener à un problèmes similaire ?
8 Soit T une solution, x, y deux caractères frères de T .
Alors T est optimal ⇔ T \setx, y ∪ { z } est optimal pour le problème
avec C 0 = C\ { x, y } ∪ { z }
où z est un nouveau caractère de frésuence f 0 [z] = f [x] + f [y]
de façon que la meilleure solution pour le reliquat demeure la solution optimale.

X
cout(T ) = f [c] ∗ d(c)
c∈C
!
X
= f [c] ∗ d(c) + f [x] + f [y]
c∈C 0
= f [x] ∗ d(x) + f [y] ∗ d(y) + f [x] + f [y]
car f 0 [z] = f [x] + f [y]
et d(z) + 1 = d(x) = d(y)
= cout(T 0 ) + (f [x] + f [y])

156 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons157

 Algorithme
Tant qu’il reste au moins deux caractères :

1. Retirer les deux caractères les moins fréquents : x et y.


2. Insérer l’arbre z := cons(f(x)+f(y), x, y)
Exemple 8.2.2
Illustration

 Implémentation et temps d’exécution


8
Comment trouver efficacement les deux caractères les moins fréquents ?
Une solution est le TA file de priorité du chapitre 5, en particulier l’implémentation en tas qui permet une complexité
en O(log n) pour l’ajout et le retrait et en temps constant pour la recherche du minimum.
La complexité d’une telle implémentation sera donc de O(n log n)), car on passe n fois dans la boucle.

31 décembre 2012 www.madit.be 157


1588.2 Exemples BIHD3 - Meth Pgm

8.2.6 Exposants
Voir le livre de référence [Cor04, exercice 16.2.7] et les slides [Sch12, s. 305-306].

158 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons159

8.2.7 Le voyageur de commerce


L’idée d’algorithmes gloutons donne parfois des heuristiques (i.e. des solutions bonnes et souvent rapides mais
non optimales).

 Problème
Un représentant de commerce doit visiter n clients puis rentrer chez lui, par le plus court chemin possible.

 Données
Un ensemble de "villes" V = 0, . . . , n et leurs distances d : V × V → R+ .

 Résultat
Une permutation p des villes telle que
n−1
X
d(pi , pi+1 ) + d(pn , p0 )
i=0

est minimal.
Exemple 8.2.3
Considérons les villes suivantes chacune munie de sa coordonnée (x, y), le tout sous la distance euclidienne.

c(1, 7) d(15, 7)

b(4, 3) e(15, 4)

a(0, 0) f (8, 0)
8
Le parcours optimal est [a, b, f, e, d, c] pour un coût (distance) de 48, 39.

 Heuristique gloutonne : de proche en proche


Partant de son domicile, le représentant va toujours à la ville la plus proche non encore visitée.
Nous avons deux ensembles : VillesVisitées et VillesAVisiter. Le variant est la taille du second ensemble.
Le calcul des distances inter-villes se réalise en O(n) et le calcul de la distance minimale en O(n). Comme on
passe n fois dans la boucle, cela donne un temps en O(n2 ).
Si nous calculons toutes les distances inter-villes avant, même temps de calcul mais plus de mémoire nécessaire !

31 décembre 2012 www.madit.be 159


1608.2 Exemples BIHD3 - Meth Pgm

Exemple 8.2.4
Pour notre exemple précédent, cet heuristique va donner le parcours suivant

c(1, 7) d(15, 7)

b(4, 3) e(15, 4)

a(0, 0) f (8, 0)

De proche en proche, le parcours obtenu [a, b, c, d, e, f ] à un coût (distance) de 50, 0, soit supérieur au
coût optimal, car le dernier trajet est souvent long pour revenir au point de départ.
Noter que si on partait de b, cette solution serait aussi la solution optimale !

 Heuristique gloutonne : le plus proche avant/arrière


On agrandit aussi le trajet "en arrière" si c’est moins coûteux.
Exemple 8.2.5
Pour notre exemple précédent, après avoir trouvé [a, b] on cherche le plus proche à partir des extrémités a
et b.
Au final on obtient le même résultat.

 Heuristique gloutonne : par arcs


On prend les arcs dans l’ordre de longueur croissante, à condition que cela ne crée pas de cycle (interne) ni de
carrefour (degré du sommet > 2). L’algorithme de Kruskal (voir cours de Graphes) ne suffit donc pas (pas de
contrainte de carrefours, pas de cycle du tout).

8 Si les arcs sont dans un tas, on trouve une complexité de O(n2 log n) car il y a ≈ n2 arrêtes.

160 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 8. Algorithmes gloutons161

31 décembre 2012 www.madit.be 161


1628.3 Exercices BIHD3 - Meth Pgm

8.3 Exercices
8.3.1 Questions du TP 7

1 Algorithmes gloutons
Exercice 1 (Les pièces de monnaie)
On dispose des pièces de monnaie correspondant aux valeurs {a1 , a2 , ..., an }

Pour chaque valeur le nombre de pièces est non borné. Etant donnée une somme S entière, on veut
trouver une façon de rendre la somme S avec un nombre de pièces minimum.

De manière générale, l’algorithme glouton ne donne pas de solution optimale pour ce problème. on peut
par contre montrer que qu’il la donne pour certains ensembles de pièces disponibles. Il s’agit ici de montrer
que pour le système monétaire européen {50,20,10,5,2,1}, l’algorithme donne une solution optimale.

Exercice 2 (Le voleur intelligent)


Un cambrioleur entre par effraction dans une maison et désire emporter quelques-uns des objets de valeur
qui s’y trouvent. Il n’est capable de porter que x kilos : il lui faudra donc choisir entre les différents objets,
suivant leur valeur (il veut bien entendu amasser le plus gros magot possible...). On suppose dans un pre-
mier temps que les objets sont des matières fractionnelles (on peut en prendre n’importe quelle quantité,
c’est le cas d’un liquide ou d’une poudre). Il y a n matières différentes, numérotées de 1 à n, la i-ème ayant
une valeur pi par kilo. La quantité disponible de cette matière est qi . On suppose que toutes les valeurs
sont différentes deux à deux.

Question Proposer un algorithme qui renvoie la valeur optimale du butin du voleur. Ce choix est-il
unique ? Programmer une fonction qui implémente cet algorithme (on pourra supposer que le tableau p
est trié).

On suppose maintenant que les objets sont non fractionnables (c’est le cas d’une chaise ou d’un télé-
viseur). Le i-ème objet a une valeur pi et pèse un poids qi .

Question Proposer une méthode dérivée de la question 1 pour résoudre le nouveau problème. Donne-
8 t-elle un choix optimal ?

Exercice 3 (Empaquetage)
On dispose d’un ensemble d’objets E= {O1 , O2 , ..., On } de poids respectifs P = {P1 , P2 , ..., Pn } et des
boites de capacité C.

Le problème consiste à placer les objets dans les boites en respectant leurs capacités et en utilisant le
moins possible de boites.

Question Soit E= {O1 , O2 , ..., O8 } , P = {9, 8, 7, 6, 5, 4, 3, 2, 1} et C= 11

Proposer un algorithme glouton qui résoud ce problème.

Question Soit E= {O1 , O2 , ..., O6 } , P = {6, 5, 5, 3, 3, 2} et C= 12

En utilisant l’algorithme de la question 1, la solution obtenue est-elle optimale ?

162 www.madit.be 31 décembre 2012


9
Générer et Tester

Sommaire
9.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.2 Génération . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.3 Améliorations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
9.4 Branch and Bound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168

163
1649.1 Principe BIHD3 - Meth Pgm

9.1 Principe
 Idée
Essayer toutes les solutions. Pour un problème d’optimisation, on garde la meilleure.

 Méthode
On divise la spécification du problème en deux parties :

1. la structure d’une solution, qu’on va générer


(a) On la découpe en choix, pour lesquelles on a des alternatives
(b) Un ensemble d’alternatives est une solution partielle ou un état

2. Les contraintes, qu’on va tester.

 Exemple des N reines


On veut trouver toutes les façons de placer N reines sur un échiquier N × N sans qu’elles ne puissent se prendre,
c’est-à-dire qu’elles ne peuvent être sur la même ligne, colonne, ou diagonale.
Structure d’une solution :

1. tableau des positions des reines : donne N 2N solutions


2. chaque reine étant identifiée par sa colonne, on choisit sa ligne : donne N N solutions ; on ne doit plus
vérifier la contrainte sur les colonnes

9.2 Génération
1. Un programme récursif permettra de générer toutes les solutions
2. chaque niveau de récursion reçoit une solution partielle (état) et essaie toutes les alternatives d’un nouveau
choix : La pile de récursion contient l’information utile pour continuer la génération. Son exécution peut
être représentée par un arbre d’exploration. La récursion classique l’explore en profondeur (DFS).
3. Lorsque la solution courante est complète, il teste les contraintes
4. Pour un problème d’optimisation, on évalue la valeur de l’objectif et on mémorise la solution courante si
c’est la meilleure jusqu’ici.

9  Exemple des N reines


Structure d’une solution partielle : pour les k premières colonnes, on donne dans quelle ligne se trouve la reine de
cette colonne.
On la représente par une liste en ordre inverse. L’appel initial se fait avec la liste vide (nil ).
Choix suivant : la ligne de la reine dans la colonne k + 1. Les alternatives sont les nombres de 1 à N .

procedure sols(l: liste)


begin
if length(l) < N
then for i := 1 to N do sols(cons(i,l))
else if test(l) then printQueen(l)
end

164 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 9. Générer et Tester165

9.3 Améliorations
 Elagage
On a intérêt à évaluer les contraintes dès que possible sur une solution partielle, soit pour éliminer toutes les
solutions descendantes.

 Propagation
Trouver des conséquences plus simples des contraintes permet de les évaluer plus tôt.

 Domaine
En particulier, on peut retenir les alternatives encore possibles pour un choix. S’il n’y en a plus qu’une, on la prend.
Intuitivement c’est ce que l’on fait manuellement quand on place les reines sur la grille.
Pour les 4 reines, supposons qu’on choisisse pour chaque colonne de gauche à droite. La première solution générée
est 1,1,1,1, ensuite 1,1,1,2, etc. Mais on aurait pu backtracker dès 1,1 (même ligne).

31 décembre 2012 www.madit.be 165


1669.4 Branch and Bound BIHD3 - Meth Pgm

9.4 Branch and Bound


9.4.1 Principe
Amélioration : on utilise des bornes (bound) sur le coût de la solution optimale parmi celles qu’on construira
dans le sous-arbre d’exploration.
Pour une minimisation, lorsque la borne inférieure d’une branche est supérieure à une solution déjà trouvée, ce
n’est plus la peine de l’explorer : on l’élague (pruning).
On a donc intérêt à trouver rapidement une bonne solution.

9.4.2 Le voyageur de commerce


Lors du générer, on représente l’exploration par des noeuds de choix : inclure ou non l’arc (i, j) .

(a, b)?

oui non

(a, c)? (a, c)?

oui non oui non

 Borne inférieure
• Arc inclus ⇒ coût connu.
• Chaque noeud doit avoir un arc entrant et un sortant : on complète avec les arc minimaux

9 • La demi-somme de ces arcs est une borne inférieure

Exemple 9.4.1
Pour trouver un chemin de la ville A vers la ville B la distance à vol d’oiseau peut constituer une borne
qui limite l’exploration afin de ne pas calculer la distance de toutes les villes du monde.

 Algorithme
• Un choix est dit "ouvert" si on ne l’a pas exploré mais bien son père ;
• Heuristique : explorer le choix ouvert de moindre borne inférieure
• On élague lorsqu’une borne inférieure dépasse la meilleure valeur trouvée.

166 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 9. Générer et Tester167

31 décembre 2012 www.madit.be 167


1689.5 Exercices BIHD3 - Meth Pgm

9.5 Exercices
9.5.1 Questions du TP 6

1 Algorithmes Générer et Tester


Exercice 1 (Permutations)
Le problème de permutations consiste à trouver toutes les permutations possibles d’une suite 1.2...n (n ≥ 0)
1. Modéliser le problème, i.e. définir plus précisément la notion d’“Etat” et toutes les autres notions
que vous jugerez utiles (au niveau “abstrait”).
2. Elargir le problème de manière à pouvoir le résoudre via un algorithme du type “Generate and Test”
et spécifier une fonction “abstraite” correspondant à ce problème élargi.
3. Construire l’algorithme “abstrait” correspondant à cette fonction. Toute primitive mentionnée dans
celui-ci doit être définie.
On demande de fournir l’organigramme résultant.
4. Donner l’algorithme concret qui découle des deux étapes précédentes.
5. Implémenter un petit programme permettant de rentrer au clavier un entier n d’afficher toutes les
permutations possibles.

Exercice 2 (Avance, petit robot)


On considère une grille carrée de dimensions taille × taille (où taille est une constante relativement
petite). Sur cette grille se déplace un petit robot. Ce petit robot avance d’une case à la fois horizontalement
ou verticalement. On trouve, également sur la grille, des obstacles (un obstacle se place sur une et une
seule case de la grille). Il est évident que le petit robot ne peut pas passer sur une case où se trouve un
obstacle.
La question principale est la suivante : “Etant donnés un point de départ et un point d’arrivée, trouver
le 1 trajet le plus court pour le petit robot entre ces deux points.”
On demande donc de réaliser pas à pas les étapes suivantes.
1. Modéliser le problème, i.e. définir plus précisément les notions de “grille”, “obstacle”, “mouvement”...
et toutes les autres notions que vous jugerez utiles (au niveau “abstrait”).
2. Spécifier une fonction “abstraite” permettant de résoudre le problème énoncé ci-dessus.
3. Elargir le problème de manière à pouvoir le résoudre via un algorithme du type “Generate and Test”
(version descendante de la solution) et spécifier une fonction “abstraite” correspondant à ce problème
élargi.
4. Construire l’algorithme “abstrait” correspondant à cette fonction. Toute primitive mentionnée dans
celui-ci doit être définie.
On demande de fournir l’organigramme résultant.

9 5. Définir précisément les représentations C des différents types d’objets manipulés (i.e. donner des
conventions de représentations) et traduire les primitives “abstraites” en termes des représentations
adoptées. Justifier (expliquer) le choix effectué pour les représentations.
6. Donner l’algorithme concret qui découle des deux étapes précédentes.
7. Implémenter un petit programme permettant de rentrer au clavier une “liste” d’obstacles et de, pour
cette liste d’obstacles, permettre le calcul de plusieurs trajets du petit robot. Pour chaque trajet, le
programme demandera un point de départ et un point d’arrivée et puis affichera successivement les
différents états de la grille le long du trajet trouvé.
Il est évident que toute fonction intermédiaire doit être spécifiée.
On demande de ne pas trop s’attarder sur les fonctions d’affichage : un simple quadrillage avec des
caractères est largement suffisant.

Exercice 3 (Problème des n-reines)


le but du problème des n-reines est de placer n dames d’un jeu d’échecs sur un échiquier de nxn cases
sans que les dames ne puissent se menacer mutuellement, conformément aux règles du jeu d’échecs (la
couleur des pièces étant ignorée). Par conséquent, deux dames ne devraient jamais partager la même
rangée, colonne ou diagonale. Simple mais non trivial ! On vous demande donc de résoudre ce problème à
l’aide d’un algorithme de type générer et tester, on demande donc de réaliser pas à pas les étapes réalisées
dans l’exercice précédent.
1. Il est clair que celui-ci n’est pas nécessairement unique ... mais, on désire en connaître un seul.

168 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm 9. Générer et Tester169

9.5.2 Codes C de solutions


Voir fichiers annexes

31 décembre 2012 www.madit.be 169


1709.5 Exercices BIHD3 - Meth Pgm

170 www.madit.be 31 décembre 2012


Bibliographie

[Cor04] Thomas Cormen. Introduction à l’algorithmique, cours et exercices. DUNOD, 2004. 81, 82, 85, 149, 153,
154, 158
[Des11] Marc Dessoy. Méthodes de programmation, partie 1 (notes personnelles du cours de bac 2). www.madit.be,
2011. 5, 31, 32, 33, 45, 119, 120, 121
[Sch12] Pierre-Yves Schobbens. Méthodes de programmation partie 2, slides du cours de bac 3. Université de
Namur, 2012. 5, 33, 54, 122, 158

171
172BIBLIOGRAPHIE BIHD3 - Meth Pgm

172 www.madit.be 31 décembre 2012


Table des matières

Préambule 5
Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Mise en garde . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

I Concepts communs 7
1 La récursion 9
1.1 Récursion bien fondée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.1.1 Relation bien fondée et cas de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.1.2 Preuves par induction générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.1.3 Récursion croisée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.1.4 Exemple de procédure récursive : le labyrinthe . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 Récursion terminale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.1 Terminale ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.2 Exemple avec un accumulateur : la fonction ’factorielle’ . . . . . . . . . . . . . . . . . . . 12
1.2.3 L’accumulateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.4 Deux accumulateurs (la suite de Fibonacci) . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2.5 Récursivité terminale et les langages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.3 Structures de données récursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.1 Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.2 Arbres binaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.3 Arbres binaires triés (ABT) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.4 Arbres ordonnés (AO) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4 Exercices de TP - procédures récursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.4.1 Fonctions récursives terminales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.4.2 Recherche du minimum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.4.3 Algorithme du tri fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

173
174BIBLIOGRAPHIE BIHD3 - Meth Pgm

1.4.4 Listes chaînées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21


1.4.5 Valeurs communes à deux listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.4.6 Évaluation binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.4.7 Multiplication binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.4.8 Suites équilibrées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

2 Preuves et temps d’exécution 29


2.1 Preuves de programmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.1.1 Règles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.1.2 Prouver la correction partielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.1.3 Preuves par la méthode progressive "sp" . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.1.4 Preuves par la méthode régressive "wp" . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.1.5 Preuve d’appel de procédures, de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.2 Temps d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.2.1 Ordre de grandeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.2.2 Temps d’exécution des algorithmes itératifs . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.2.3 Temps d’exécution des algorithmes récursifs . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.3 Espace en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.4.1 Primalité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.4.2 Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.4.3 Calcul de l’index du max . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.4.4 Tri par sélection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.4.5 Tri par fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
2.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.5.1 Exercices du TP2 - temps d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.5.2 Solutions du TP2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.5.3 Notes du TP2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

II Représentation des données 51


3 Les types abstraits (TA) 53
3.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.2 Avantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.3 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.4 Donner les propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.4.1 Par modèles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.4.2 Par axiomes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.5 Complétude . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.5.1 Méthode des constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.5.2 Méthode des observateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.6 Quelques types abstraits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

174 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm BIBLIOGRAPHIE175

3.6.1 TA Pile (LIFO) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58


3.6.2 TA Ensemble fini . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.6.3 TA Dictionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.7 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.7.1 Exercices du TP4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.7.2 Solutions du TP4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

4 Implémentation des ensembles 65


4.1 Tableau de Booléens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.1.1 Fonctions et procédures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.2 Liste avec doublons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.2.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.2.2 Fonctions et procédures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.2.3 Temps de calcul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.2.4 Place en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.3 Liste sans doubles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.3 Temps d’exécution (Liste chaînée) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.3.4 Place en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.4 Listes triées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.4.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.4.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.4.3 Temps d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.4.4 Place en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.5 Tables de hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.5.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.5.2 Implémentation par un ensemble des indices . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.5.3 Implémentation par une table d’adressage ouvert . . . . . . . . . . . . . . . . . . . . . . 75
4.5.4 Implémentation en agrandissant la table de hachage . . . . . . . . . . . . . . . . . . . . . 75
4.6 Arbres binaires de recherche (ABR) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.6.1 Définition d’un AB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.6.2 Définition d’un ABR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.6.3 Fonctions et Procédures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
4.6.4 Temps d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.7 Arbres rouges/noirs (ARN) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.7.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.7.2 Invariants de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.7.3 Hauteur d’un ARN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.7.4 Fonctions de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.7.5 Rotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.7.6 Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

31 décembre 2012 www.madit.be 175


176BIBLIOGRAPHIE BIHD3 - Meth Pgm

4.7.7 Supprimer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.7.8 Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.8 B-Arbres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.8.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.8.2 Procédures et fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
4.9 Exercices de TP - implémentation des ensembles . . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.9.1 Arbres binaires ABR et ARN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.9.2 Code C complet pour un arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

5 TA File de priorité 107


5.1 Spécification par modèle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.2 Implémentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.2.1 Possibilités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.2.2 Tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.2.3 Implémentation du Tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.3 Le tri par Tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111

III Conception d’algorithmes 113


6 Diviser pour régner 117
6.1 Idée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.1.1 Cas de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.1.2 Choix possibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.2 Exemples : quelques tris . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.2.1 La Spécification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.2.2 Solution D1 : Tri par INSERTION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.2.3 Solution D2 : Tri par FUSION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.2.4 Solution C1 : Tri par SELECTION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.2.5 Solution C2 : QUICKSORT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.2.6 Temps d’exécution minimal d’un tri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.3 Exemples : multiplications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.3.1 Multiplication binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.3.2 Puissances rapides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.3.3 Fibonacci par puissances rapides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

7 Programmation dynamique 123


7.1 Memoïsation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.1.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.1.2 Avantages et inconvénient . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.2 La récursivité ascendante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
7.2.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
7.2.2 Avantages et inconvénients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

176 www.madit.be 31 décembre 2012


BIHD3 - Meth Pgm BIBLIOGRAPHIE177

7.2.3 Économie de mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126


7.2.4 Fibonacci en récursif ascendant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
7.2.5 Combinaisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
7.3 La programmation dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
7.3.1 Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
7.3.2 Sac à dos discret . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
7.3.3 Multiplication de matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.4.1 Questions du TP 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.4.2 Codes C de solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

8 Algorithmes gloutons 147


8.1 Principes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.2 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.2.1 Sac à dos continu ou borné . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.2.2 Les pleins d’essence de Donald . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
8.2.3 Les pleins d’essence de Picsou . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
8.2.4 Ordonnancement d’activités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
8.2.5 Codes de Huffman . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
8.2.6 Exposants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
8.2.7 Le voyageur de commerce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
8.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.3.1 Questions du TP 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

9 Générer et Tester 163


9.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.2 Génération . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.3 Améliorations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
9.4 Branch and Bound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.4.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.4.2 Le voyageur de commerce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
9.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
9.5.1 Questions du TP 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
9.5.2 Codes C de solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

31 décembre 2012 www.madit.be 177