Vous êtes sur la page 1sur 53

Memento INFO 104 Semestre 2 Année 2018

Professeur Maurice TCHUENTE

Section 2 : Les Boucles

Recherche séquentielle dans une structure qui peut être vide


Trouve := faux ; < Se mettre en position initiale>
Jusqu’à <Condition de sortie : trouve ou position_hors_limites> faire
début
Prendre él _cour ; On est placé avant l’élément suivant ;
Si él_cour = val alors trouve := vrai sinon <Passer à la position suivante>
fin ;
<Traitement final > ;

Recherche séquentielle dans une structure non vide


Trouve := faux ; <Prendre le premier élément >
Répéter
Si él_cour = val alors trouve := vrai sinon <Passer à la position suivante>
<Prendre élément suivant>
Jusqu’à <Condition de sortie : trouve ou position_hors_limites>
<Traitement final > 

Algorithmes sur les vecteurs

Recherche séquentielle de val dans un vecteur V[1..n]


trouve := faux ; i := 1 ; { Initialisation de la boucle }
jusqu’à trouve ou (i > n) faire si V[i] = val alors trouve := vrai sinon i := i+1.

Recherche séquentielle avec sentinelle de val dans un vecteur V[1..n]


V[0] := val ; i := n ; { Initialisation de la boucle }
jusqu’à V[i] := val faire i := i-1 ;
trouve := i > 0.

Recherche dichotomique de val dans un vecteur V[1..n].


i := min ; j := max ;
jusqu’à i = j faire début m := (i + j) div 2 ; {i  m < j}
si val  V[m] alors j := m sinon i := m+1
fin
trouve := V[i] = val.

Segmentation d’un vecteur V[min..max] par rapport à X = V[max]


Idée : utiliser deux tamis g (gauche) et d (droite) qui :
 encadrent X : V[r ]  X pour r < g, V[s ]  X pour s  d
 se rapprochent (g se déplace vers la droite et d vers la gauche) jusqu’à coïncider
g := min ; d := max ; V[r ]  X pour r < g, V[s ]  X pour s  d
Jusqu’à g = d faire début Jusqu’à V[g]  X ou g = d faire g := g+1 ;
Jusqu’à V[d]  X ou g = d faire d := d-1 ;
Echanger (V[g],V[d]) V[r ]  X si r < g, V[s ]  X si s  d
fin ;
Echanger (V[g],V[max]).

Drapeau tricolore
Mettre un vecteur V[1..n] de trois couleurs dans l’ordre vert, rouge et jaune.
Idée : utiliser trois pointeurs i, j, k et les faire progresser de sorte que :
V[r] = vert pour r < i, V[r] = rouge pour i  r < j et V[r] = jaune pour k < r
i := 1 : j := 1 ; k := n ;
Jusqu’à j > k faire si V[k] = jaune alors k := k-1 sinon
si V[k] = rouge alors début Echanger (V[k], V[j]) ; j :=j+1 fin sinon
V[k] = vert début x:=V[i]; V[i]:=V[k] : V[k]:=V[j] ;  V[k]:=x ; i:=i+1 ; j:=j+1 fin

Sélection du maximum de V[1..i] et placement en position i


Max := V[1 ; Pos := 1 ;
Pour j := 1 à i faire si V[j >Max alors debut Pos := j ; Max :=  V[j fin ;
Echanger (V[Pos, V[i)

Tri d’un vecteur V[1..n] par sélection et placement du maximum


Pour i := n à 2 faire <Sélection du maximum de V[1..i] et placement en position i>
Commentaire : Tri non stable à cause de Echanger (V[Pos, V[i) qui déplace V[i.
Nombre de comparaisons-échanges : O(n2) indépendant de V.

Tri d’un vecteur V[1..n] par sélection via comparaisons–échanges d’éléments consécutifs
Idée : Soit Pos la position du terme de gauche de la dernière comparaison-échange après un
balayage. V[Pos..n contient les plus gros éléments de V . Il suffit de trier V[1..Pos.
Balayage de V[1..i] par comparaisons-échanges avec calcul de Pos :
Pos := 1 ;
Pour j := 1 à i faire si V[j >Max alors debut Pos := j ; Echanger(V[j ; V[j+1) fin ;
Tri 
i := n ;
Jusqu’à i=1 faire debut < Balayer V[1..i] par comparaisons-échanges avec calcul de Pos> ;
i := Pos
fin ;
Commentaire :
Algorithme stable et plus rapide que le précédent, nombre de comparaisons-échanges =
nombre d’inversions = O(n2) en moyenne, donc même complexité que le tri par sélection.

Tri par insertion


Idée : procéder par récurrence en insérant V[i] dans le vecteur trié V[1..i-1], pour i = 2, … , n.
Insertion de V[i] dans le vecteur trié V[1..i-1]
Idée : pousser les V[j] vers la droite jusqu’à rencontrer un élément inférieur ou égal à V[i]. On
est donc ramené à chercher dans V[1..i-1] un élément V[j] inférieur ou égal à V[i].
Recherche et insertion avec sentinelle :
V[0] := V[i] ; j := i-1 ;
Jusqu’à V[j]  V[0] faire j := j-1 ;
V[j+1] := V[0] ;
Algorithme
Pour i := 2 à n faire < Insérer V[i] dans V[1..i-1]>
Fusion de deux sous-vecteurs triés V[a..b] et V[b+1..c] en un sous-vecteur trié V[a..c].
Idée : fusionner dans W puis recopier dans V.
i := a ; j := b+1 ; k := a ;
Jusqu’à i > a ou j > c faire si V[i] < V[j] alors debut w[k] := V[i] ; i := i+1 ; k := k+1 fin
sinon debut w[k] := V[j] ; j := j+1 ; k := k+1fin
Pour i := a à c faire V[i] := W[i] ;

Tri récursif par fusions successives


Idée : Pour trier V[i..j], on ne fait rien si n  m, sinon on procède en trois étapes :
Découper V[i..j] en V[i..milieu] et V[milieu+1..j], avec milieu = (i+j) div 2
Trier V[i..milieu] et V[milieu+1..j]
Fusionner les sous-vecteurs triés V[i..milieu] et V[milieu+1..j]
Analyse des performances : T(n) = 2T(n/2) + O(n). On montre que T(n)=O(n log2 n)
Défaut de cet algorithme : utilisation du vecteur intermédiaire W.
Exercice : Donner une version non récurrente de cet algorithme (programmation dynamique)

Tri par segmentation de V[min..max]


Idée : Si min = max on ne fait rien, sinon :
 Mettre V[max] = M en position k, V[i] ≤ M pour i < k, et M ≤ V[j] pour k < j
 Trier ensuite V[min..M-1] et V[M+1..max] car V[M] est à sa place définitive
Performance : O(n log n) en moyenne  et O(n2) au pire.

Tri d’un vecteur d’éléments de type énuméré V[1..n] (exemple des caractères)
 Calcul du vecteur des fréquences FREQ[a..z] des lettres de V[1..n].
 Calcul du vecteur des fréquences cumulées FREQ_CUMUL[a..z]
 Placement des éléments triés, dans le vecteur V[1..n]
Question : Cet algorithme est-il stable ? Comment procéder pour minimiser les mouvements
lorsque les éléments à trier sont volumineux et coûtent cher lorsqu’on veut les échanger ?

Tri par Tas (Tas : arbre binaire complet représenté par un vecteur)
Un tas est un vecteur V1..n dont les éléments sont dessinés sous forme d’arbre binaire
complet ainsi qu’il suit :
 V1 est à la racine
 V2i s’il existe, est le fils gauche de Vi, et V2i+1 s’il existe, est le fils droit de
Tasi. De plus tout fils est plus petit que son père. Le plus grand élément se
trouve donc à la racine.
Dans un tas de profondeur h, on a 1 nœud à la racine (niveau 1), 2 nœuds de profondeur
2, 4 nœuds de profondeur 3, …, 2h-1 nœuds de profondeur, les autres nœuds étant de
profondeur h. En conséquence, 1 + 2 + … + 2h-1 = 2h – 1 < n  1 + 2 + … + 2h = 2h+1-1. D’où
log2 (2h – 1) < log2 n  log2(2h+1-1) < h+1. Donc h = O(log2 n)
Dans un tas, le plus gros élément est V1. D’où l’algorithme suivant pour trier V1..n :
 Transformer V1..n en tas ; i := n ;
 Jusqu’à i = 1 faire
début Echanger (V1 et Vi) ; Place V1 en bonne position i, mais perturbe la
racine du tas V1..i-1 ; i := i-1 ;
<Faire descendre V1 dans V1..i en l’échangeant successivement avec
le plus grand de ses fils si l’un d’eux est plus grand; Descente_Tas (V, 1, i)>
fin ;
Transformation de V[1..n] en tas
Idée : Partir de V[1..n] et considérer des tas de plus en plus grands à partir du bas.
Pour i := n div 2 à 1 faire Descente_Tas (V, i, n) ; Fait descendre Vi dans V1..n
Performance : O(n log2 n) au pire car on a au plus n descentes de longueurs log2 n.
Procédure Descente_Tas (V, i, max) qui fait descendre V[i] dans V[i..max]
Idée : insérer V[i] dans un chemin qu’on découvre au fur et à mesure.
trouve := faux ; j := i ; { Initialisation de la boucle }
jusqu’à trouve ou (2j > max) faire si V[j]  <tous ses fils> alors trouve := vrai
sinon <Echanger V[j] et son plus grand fils et mettre à jour j> ;

Recherche de Mot[1..m] dans Texte[1..n] 


trouve := faux ; i := 1 ; { Initialisation de la boucle }
Jusqu’à trouve ou (i > n-m+1) faire
Si Mot[1..m] = Texte[i..i+m-1] alors trouve := vrai sinon i := i+1
Comparaison de Mot[1..m] et Texte[i..i+m-1]
différents := faux ; j := 1 ; { Initialisation de la boucle }
Jusqu’à différents ou (j > m) faire
Si Mot[j] ≠ Texte[i+j-1] alors différents := vrai sinon j := j+1
Critique : On n’exploite pas la structure de Mot[1..m].

Programmation par automates pour le traitement de c1c2 … cm#


Idée : Considérer l’indice j de parcours du mot comme un grade. Au grade i, si on lit
Mot[i+1] alors on est promu au grade i+1, sinon on se comporte comme une personne du
grade (moins élevé) k, tel que Mot[1..k] est le plus grand préfixe de Mot[1..m] qui est aussi
suffixe : l’échec au grade abab, ramène au grade ab, l’échec au grade abc ramène au grade .
Remarque : C’est un cas particulier de la programmation par automates.
 Ensemble des états : entiers
 Ensemble des entrées : caractères
 Nouvel état à partir de e après lecture de c : Transit[e, c]
 Actions pour un état e et un caractère c : Action [e, c]
e := <état initial> ; c := <premier caractère> ; Trouvé := faux ;
Jusqu’à (c = ‘#’ >) ou Trouvé faire
début Action [e, c] ; e := Transit [e, c] ; c := <caractère suivant> fin ;
N.B. La variable Troué, est mise à jour dans les actions.

Tri par arbre (le faire à la main en bricolant)


Idée : créer un arbre binaire de recherche et le parcourir en ordre infixé
 <Lecture des éléments et insertion dans un arbre A>
 <Impression de A dans l’ordre infixé>
Performance : O(n) au pire, O(n log2 n) au mieux et O(n log2 n) en moyenne
Inconvénient : on peut tomber sur le pire cas et frustrer l’utilisateur.

Tri de fichier
Idée : Faire des fusions successives pour réduire à chaque fois de moitié le nombre de
monotonies.
Répéter
<Extraire les monotonies de f et les placer alternativement dans f1 et f2> ;
<Fusionner deux à deux les monotonies de f1, f2 et mettre le résultat dans f > ;
Jusqu’à f1 vide ou f2 vide faire
Performance : T(n) = O(n) + 2T(n/2) + O(n) = O(n) + 2T(n/2) = O(n log n)
Extraction des monotonies de f avec placement alternativement dans f1 et f2
On utilise une fenêtre (val_prec, val_cour, i, Terminé) où :
 Val_prec et val_cour désignent la valeur précédente et la valeur courante
 i est un indice de fichier
 Terminé est une variable booléenne qui indique si le traitement est terminé
val_prec := - ; Lire (f, val_cour) ; i := 1 ; Terminé := faux ;
Répéter L’élément précédent val_prec a été inséré dans fi
Si val_prec > val_cour alors modifier i ;
Ecrire val_cour dans fi ;
Si eof (f) alors Terminé := vrai
sinon début val_prec := val_cour ; Lire (f, val_cour) fin  
Jusqu’à Terminé ;
Fusion deux à deux les monotonies de f1, f2 non vides et placement du résultat dans f
val_prec1 := - ; Lire (f1, val_cour1) ; val_prec2 := - ; Lire (f2, val_cour2) ;
Terminé := faux ;
Répéter
Si (val_prec1  val_cour1) et (val_prec2  val_cour2) alors
<Ecrire dans f le plus petit des val_cour et décaler la fenêtre correspondante>
Sinon si (val_prec1 > val_cour1) et (val_prec2  val_cour2) alors
<Ecrire val_cour2 dans f et décaler la fenêtre de f2>
Sinon si (val_prec1  val_cour1) et (val_prec2 > val_cour2) alors
<Ecrire val_cour1 dans f et décaler la fenêtre de f1>
Sinon (val_prec1 > val_cour1) et (val_prec2 > val_cour2) monotonies épuisées
début val_prec1 := - ; val_prec2 := - début de deux nouvelles monotonies fin
Jusqu’à Terminé ;
Décalage de la fenêtre de f1
Si eof (f1) alors début Recopier le reste de f2 ; Terminé := vrai fin
sinon début val_prec1 := val_cour1 ; Lire (f1, val_cour1) fin ;
Décalage de la fenêtre de f2
Si eof (f2) alors début Recopier le reste de f1 ; Terminé := vrai fin
sinon début val_prec2 := val_cour2 ; Lire (f1, val_cour2) fin ;

Algorithmes sur les listes chaînées

Les pointeurs ou adresses d’enregistrements


Un pointeur est l’adresse du début de la zone occupée par un enregistrement. La
définition d’un pointeur comporte donc le type T de l’enregistrement sur lequel il pointe.
Sinon, en arrivant dans la zone, on ne saurait pas quelle quantité de bits prendre pour
obtenir l’enregistrement situé à cette adresse.

Déclaration pour une liste d’entiers.


Type
Cellule = record
info : entierCellule ;
suivant : Cellule
end ;
Pointeur = ^T (ou Pointeur :=T) ;
Liste = Pointeur ;
Var L : liste ;
Opérations de base sur les listes chaînées
Vider la liste L :
L := nil ;

Insertion après la cellule P, d’une cellule contenant x :


New(Q) ;
Q.suivant := P.suivant ;
P.suivant := Q ;
Q.info := x ;

Insertion avant la cellule P, d’une cellule contenant x :


New(Q) ;
Q.suivant := P.suivant ;
P.suivant := Q ;
Q.info := P.info ;
P.info := x ;

Suppression de la première cellule (liste L supposée non vide)


L := L.suivant ;

Suppression de la cellule qui suit P


P.suivant := P.suivant.suivant ;

Suppression de la cellule P (qui n’est pas la dernière)


Recopier dans P le contenu de la cellule suivante puis supprimer la cellule suivante
P.info := P.suivant.info ;
P.suivant := P.suivant.suivant ;

Commentaire
Pour pouvoir se retrouver toujours avec le cas facile d’une suppression portant sur un
élément qui n’est pas le dernier, il suffit d’avoir une cellule sentinelle en fin de liste.

Insertion d’une cellule contenant x en tête (avant la première cellule) dans une
liste chaînée L
New(P) ;
P.suivant := L ;
L := P ;
P.info := x ;
Section 3 : Les Piles

Définition
Une pile (en anglais stack) est un type de données abstrait basé sur le modèle de
données liste, dans laquelle on peut insérer ou supprimer un élément. La suppression
obéit au principe « dernier inséré, premier extrait » (en anglais LIFO pour Last In, First
Out). Autrement dit, l’élément le plus récent dans la pile est celui qui est extrait.

Exemple  de pile :


Organisation des plateaux dans un restaurant en self-service est une pile. Un nouveau
plateau est ajouté au-dessus de la pile. Pour extraire un plateau, on prend celui qui est
au-dessus de la pile et correspond donc au plateau le plus récent (dernier plateau
ajouté). Les plateaux sont donc extraits dans l’ordre inverse de leur ajout dans la pile.

Propriété
Dans une pile les opérations d’insertion et de suppression sont toutes les deux
effectuées à une même extrémité de la liste appelée sommet de la pile.

Spécification formelle d’une pile


D’un point de vue formel (c’est-à -dire sans les détails d’implémentation), une pile P est
un type de données abstrait muni des cinq opérations de base (ou primitives) suivantes :
 Initialiser (P) : Vide la pile P.
 EstVide (P) : Renvoie vrai si la pile P est vide et faux sinon.
 EstPleine (P) : Renvoie vrai si la pile P est pleine et faux sinon.
 Empiler (P, x) : ajoute l’élément x au sommet de la pile P.
 Dépiler (P, x) : Supprime l’élément en sommet de pile, si la pile est non vide.

Spécifications détaillées des opérations de base sur une pile


Initialiser (P) : Vide la pile P.
Clear (P) en anglais.
{Pré-condition : aucune}
Procédure Initialiser (var P : Pile) ;
début
<Opérations qui vident la pile>
fin ;
{Post-condition : P est vide}

EstVide (P) : Renvoie vrai si la pile P est vide et faux sinon.


IsEmpty (P) en anglais.
{Pré-condition : pile initialisée}
Fonction EstVide (valeur P : Pile) : booléen ;
début
Si <la pile est vide> alors EstVide := vrai sinon EstVide := faux
fin ;
{Post-condition : délivre vrai si et seulement si la pile est vide}

EstPleine (P) : Renvoie vrai si la pile P est pleine et faux sinon.


{Pré-condition : pile initialisée}
Fonction EstPleine (P : Pile) : booléen ;
début
Si <la pile est pleine> alors EstPleine := vrai sinon EstPleine := faux
fin ;
{Post-condition : délivre vrai si la pile est pleine et faux sinon}

Empiler (P, x) : ajoute l’élément x au sommet de la pile P.


Push (P, x) en anglais.

Solution 1 :
{Pré-condition : pile P initialisée et de contenu C = (x1, … , xn) avec xn au sommet}
Fonction Empiler (var P : Pile ; valeur x : élément) : booléen ;
début
Si EstPleine(P) alors Empiler := faux
sinon début
ajouter x au sommet de la pile ;
Empiler := vrai
fin
fin ;
{Post-condition : si la pile est pleine au départ alors son contenu est inchangé et
la valeur retournée est faux, sinon son nouveau contenu est (x1, … , xn, x) et la
valeur retournée est vrai}

Solution 2 :
{Pré-condition : pile P initialisée, non pleine et de contenu (x1, … , xn)}
Procédure Empiler (var P : Pile ; valeur x : élément) ;
début
Ajouter x au sommet de la pile
fin ;
{Post-condition : le nouveau contenu de P est (x1, … , xn, x)}

Dépiler (P, x) : Supprime l’élément en sommet de pile, si la pile est non vide.
Pop (P, x) en anglais.

Solution 1 :
{Pré-condition : pile initialisée et de contenu (x1, … , xn) avec xn au sommet }
Fonction Dépiler (var P : Pile ; var x : élément) : booléen ;
début
Si EstVide(P) alors Dépiler := faux
sinon début
x := <élément en sommet de pile> x ;
Dépiler := vrai
fin 
fin ; 
{Post-condition : x = xn et le nouveau contenu de la pile est (x1, … , xn-1)}

Solution 2 :
{Pré-condition : pile initialisée, non vide et de contenu (x1, … , xn) avec xn au
sommet }
Fonction Dépiler (var P : Pile) : élément ;
début
Supprimer l’élément x en sommet de pile ;
Dépiler := x
fin ; 
{Post-condition : renvoie x1 et le nouveau contenu de la pile est (x1, … , xn-1)}

On peut aussi pour des raisons de commodité, définir la primitive Sommet qui
donne l’élément au sommet de la pile sans l’extraire

Sommet (P, x) 


Top (P, x) en anglais

Solution 1
{Pré-condition : Pile initialisée }
Fonction Sommet (valeur P : Pile ; var x : élément) : booléen ;
début
Si EstVide(P) alors Sommet := faux
sinon début
x := <élément en sommet de pile> ;
Sommet := vrai
fin ;
{Post-condition : met dans x la valeur au sommet de la pile sans l’extraire
et renvoie vrai si la pile n’est pas vide ; renvoie faux si la pile est vide}

Solution 2
{Pré-condition : Pile initialisée et non vide (x1, … , xn) avec xn au sommet }
Fonction Sommet (valeur P) : élément ;
début
Sommet := xn 
fin ;
{Post-condition : renvoie xn sans le supprimer de la pile}

Implémentation contigue c-à-d à base d’un vecteur :


LongMax est une constante entière représentant la taille maximum de la pile.

Déclarations
Type Pile = record
tête : entier ;
V : array[1..LongMax] of élément
end ;
Var P : Pile ;

Initialiser (P) :
P. tête := 0
EstVide (P) :
Si P.tête = 0 alors EstVide := vrai sinon EstVide := faux ;
{En condensé ceci peut s’écrire EstVide := P.tête = 0}

EstPleine (P) :
Si P.tête = LongMax alors EstPleine := vrai sinon EstPleine:= faux ;
{ En condensé ceci peut s’écrire EstVide := P.tête = LongMax}

Empiler (P, x) :
Solution 1 :
Avec P faire
si EstPleine(P)  alors Empiler := faux
sinon début
tête := tête + 1 ;
Vtête := x ;
Empiler := vrai
fin ;

Solution 2 :
Avec P faire
début
tête := tête + 1 ;
Vtête := x ;
Empiler := vrai
fin ;

Dépiler (P, x) :
Solution 1 :
Avec P faire
si EstVide (P) alors Dépiler := faux
sinon début
x := Vtête  ;
tête := tête - 1 ;
Dépiler := vrai
fin ;

Solution 2 :
Avec P faire
début
x := Vtête  ;
tête := tête - 1 ;
Dépiler := vrai
fin ;
Sommet (P, x) :

Solution 1 :
Avec P faire
Si EstVide(P) alors Sommet := faux
sinon début
x := VSommet ;
Sommet := vrai
fin ;

Solution 2 :
Avec P faire
début
x := VSommet ;
Sommet := vrai
fin ;

Représentation avec des pointeurs

Déclarations
Type Pile = Cellule ;
Cellule = record
info : élément ;
suivant : Cellule
end ;
Var P : Pile ;

On adopte une représentation en liste chaînée dans laquelle la pile le premier élément
est le sommet de la pile.

Initialiser (P) :
P := nil ;

Empiler (P, x) : ajoute en tête de liste une cellule contenant x.


New(Q) ; Q.info := x ; Q.suivant := P ; P := Q ;

Dépiler (P, x) :
si P = nil alors Dépiler := faux P = nil peut être remplacé par EstVide(P)
sinon début
x := P.info  ;
P := P.suivant 
Dépiler := vrai
fin ;

Remarque : Lorsqu’on veut juste supprimer l’élément en sommet de pile, on peut


concevoir une procédure Dépiler avec uniquement le paramètre P.
EstVide (P) :
EstVide := P = nil ;

EstPleine (P) : Ne se produit jamais


EstPleine := faux ;

Sommet (P, x) :


Si P = nil alors Sommet := faux
sinon début
x := P.info ;
Sommet := vrai
fin ;

Application des piles


Reconnaissance des palindromes
Reconnaissance d’un mot de la forme w#wt$ où w est un mot sur un alphabet  ne
contenant pas le caractère #, et wt est le mirroir de w (obtenu en prenant les caractères
de w dans l’ordre inverse). Des exemples de tels mots sont : abac#caba$ et
daaacca#accaaad$. Pour reconnaître un tel mot donné caractère par caractère de gauche
à droite, on procède en deux phases :
Phase 1 : on parcourt les caractères et on les stocke jusqu’à la rencontre du
caractère #.
Phase 2 : après le caractère #, on parcourt le mot toujours de gauche à droite,
en extrayant de la structure de la phase 1, le caractère le plus
récent, s’il est égal au caractère courant. C’est en quelque sorte un
processus de simplification illustré dans le schéma ci-dessous :

cca#acc$
cca#acc$
cca#acc$

cca#acc$

A la lecture du $, si la structure de données est vide alors le mot appartient à la


famille, sinon le mot n’appartient pas à la famille. La structure de données adaptée
pour cet algorithme est donc une pile. En effet, dans la partie gauche, le dernier
caractère arrivé est le premier à être simplifié. Il suffit aussi de remarquer que la
partie gauche évolue toujours à l’extrémité droite.

Calcul d’une expression arithmétique post-fixée


Une expression arithmétique est dite post-fixée si les opérateurs viennent après les
opérandes. Par exemple : a+b s’écrit ab+, c-d s’écrit cd- et a+b*c-d s’écrit abc*+d-

Pour calculer une telle expression terminée par $ en la lisant de gauche à droite, on
procède ainsi qu’il suit :

Lire (élément) ;
Jusqu’à élément = $ faire
début
Si l’élément lu est un opérande alors le stocker
sinon début
Extraire les opérandes nécessaires à cet opérateur ;
Effectuer l’opération ;
Stocker le résultat de l’opération
fin ;
Lire (élément) ;
fin ;

Dans cet algorithme, un opérateur utilise les opérandes immédiatement à sa


gauche. Par exemple dans l’expression abc*+ qui correspond à a+b*c, la
multiplication utilise b et c. Une fois stockés, les opérandes a, b et c sont donc
extraits dans l’ordre inverse de leur insertion. La structure de données adaptée
pour cet algorithme est une pile.

Parcours d’un labyrinthe (exploration d’un graphe en profondeur)


Pour parcourir un labyrinthe on peut procéder ainsi qu’il suit :
1. Partir d’un nœud initial u0, marquer u0 comme visité et prendre u0 comme nœud
courant. u désigne le nœud courant à un instant donné, et les sommets visités
sont marqués
2. Initialiser C à u0 C désigne à tout instant le chemin (u0, … , un ,u) de u0 au nœud
courant u
u := u0 ;
Répéter
Si u a un voisin non marqué v alors
début
C := (C, v) ;
Marquer v ;
u := v
fin
sinon faire marche arrière C = (C’,u) devient C’ par suppression de u 
Jusqu’à C = 

Analyse
La structure de données pour représenter le chemin C = (u0, … , un), correspond à une
liste à laquelle :
1. l’insertion d’un nœud u, aboutit à la liste (u0, … , un, u). L’insertion se fait donc à
l’extrémité droite ;
2. la suppression d’un noeud par marche arrière dans la liste (u0, … , un, u), conduit
à la liste (u0, … , un). La suppression se fait donc à l’extrémité droite ;

Les opérations d’insertion et de suppression dans la liste C se font à la même extrémité


de la liste. La structure de données adaptée pour C est donc une pile. Nous aboutissons à
l’algorithme suivant :

Initialiser (P) ;
Marquer le nœud initial u0 ;
Empiler (P, u0) ;
u := u0 ; u désigne le nœud courant 
Répéter u désigne le nœud courant 
Si u a un voisin v non marqué alors
début
Empiler (P, v) ;
Marquer (v) ;
u := v
fin
sinon Dépiler (P)
Jusqu’à EstVide (P)

Calcul d’une expression infixée


On suppose que l’expression est une file e terminée par ‘#’. Pour le calcul de a + b * c ou
a * b + c, on applique d’abord celui des deux opérateurs ‘+’ , ‘*’ qui est le plus prioritaire.
Dans un balayage de gauche à droite, il faut donc commencer par stocker le premier
opérateur et faire le choix uniquement après la comparaison des priorités, à l’arrivée du
deuxième opérateur.
. Pour le calcul de a + b * c d, on applique d’abord celui des trois opérateurs ‘+’ , ‘*’, ,
qui est le plus prioritaire. Dans un balayage de gauche à droite, il faut donc commencer
par stocker les 3 opérateurs et faire le choix uniquement après la comparaison des
priorités, à l’arrivée du troisième opérateur. En effet ‘x’ est prioritaire sur ‘+’ mais il faut
attendre de voir le suivant avant de l’appliquer. Dans tous les cas ‘x’ sera effectué avant
‘+’. Les opérateurs prioritaires sont stocks en denier et exécutés les premiers. Ils sont
donc gérés comme une pile

On utilise une pile et chaque opérateur op a deux priorités :


1. une priorité intérieure Priorité_int (op) lorsqu’il est dans la pile
2. une priorité extérieure Priorité_ext (op) lorsqu’il est hors de la pile
Lorsqu’un nouvel opérateur est lu on a deux cas :
 sa priorité extérieure est supérieure à la priorité intérieure de l’opérateur en
sommet de pile : on l’empile
 sa priorité extérieure est inférieure à la priorité intérieure de l’opérateur en
sommet de pile : on dépile l’opérateur en sommet de pile pour le mettre dans
l’expression et on recommence la comparaison avec le nouvel opérateur en
sommet de pile
Ceci impose d’initialiser la pile avec un opérateur sentinelle de priorité intérieure - et
de considérer ‘#’ comme un opérateur de priorité -.

On utilise une deuxième pile pour stocker les opérandes après lecture. L’exécution d’une
opération se fait en dépilant les opérandes, en appliquant l’opérateur correspondant et
en empilant le résultat.

La parenthèse ouvrante est systématiquement empilée. Ceci revient à dire que sa


priorité extérieure est égale à +. Lorsque la parenthèse fermante est lue, on dépile tous
les opérateurs en sommet de pile jusqu’à rencontrer la parenthèse fermante qui lui
correspond, puis on dépile cette dernière. Ceci revient à dire que sa priorité extérieure
est égale à -.

Lorsqu’on a épuisé l’expression, on se retrouve avec l’opérateur ‘#’. On dépile tous les
opérateurs de la pile en exécutant au fur et à mesure l’opération correspondante

Exercice :
1. Concevoir la table des priorités en donnant pour chaque couple où chaque
composante est un opérateur ou une parenthèse, un exemple qui permet de
définir la valeur de la table pour ce couple. On obtiendra un tableau de booléens
tel que TAB u,v = vrai si et seulement si l’opérateur v à l’extérieur de la pile, est
plus fort que l’opérateur u à l’intérieur de la pile. Par exemple, pour le couple (-,
+) on considère la sous-expression a – b + c et on voit qu’elle correspond à (a-b) +
c ; ceci veut dire que si ‘-‘ est dans la pile et ‘+’ en dehors de la pile, alors c’est la
soustraction qui s’effectue en premier. Autrement dit, pour cet exemple on doit
produire a b – c +, donc TAB -, + = faux, puisque ‘+’ en dehors de la pile n’est pas
plus fort que ‘-‘ à l’intérieur de la pile.
2. Déterminer les vecteurs Priorité_int et Priorité_ext qui permettent d’obtenir TAB
de la manière suivante :
TABu,v : vrai si et seulement si Priorité_extv  Priorité_intu
3. Ecrire avec des notations algorithmiques, la solution exposée plus haut.

Représentation condensée de deux piles


Montrer comment représenter deux piles avec un seul vecteur.
Solution : placer les deux piles qux deux exrémités du vecteur, evec des orientations
opposées (cf. figure 2)

Equivalences entre structures de stockage


Théorème
Une bande de lecture/écriture peut être fabriquée à partir de deux piles.

Démonstration
La bande (a1, a2, … , an, q, bm, am-1, … , b1) où la tête scanne bm, est réalisée avec les deux
piles (a1, a2, … , an) avec am en tête, (bm, bm-1, … , b1) avec bm en tête.

Modification de l’élément bm, situé sous la tête de lecture/écriture :


On doit obtenir (a1, a2, … , an) et (bm-1, … , b1).
Modifier le sommet de la pile de droite

Mouvement de la tête vers la gauche


On doit obtenir (a1, a2, … , an-1) et (an, bm, bm-1, … , b1).
4. Empiler le sommet de la pile de gauche sur la pile de droite
5. Dépiler sur la pile de gauche

Mouvement de la tête vers la droite


On doit obtenir (a1, a2, … , an-1, bm,) et (bm-1, bm-1, … , b1).
1. Empiler le sommet de la pile de droite sur la pile de gauche
2. Dépiler sur la pile de droite.
Section 4 : Files d’attente ou Queues

Définition : Une file d’attente ou queue (en anglais queue) est un type de données
abstrait basé sur le modèle de données liste, dans laquelle on peut insérer ou supprimer
un élément. La suppression obéit au principe « premier inséré, premier extrait » (en
anglais FIFO pour First In, First Out). Autrement dit, l’élément le plus ancien dans la file
d’attente est celui qui est extrait.

Dessin avec des flèches Insérer et Supprimer avec


- tableau (2) avec des éléments au milieu
- liste chaînée avec
o insertion en fin de liste et suppression en début de liste (marche bien)
o insertion en début de liste et suppression en fin de liste (dire
l’inconvénient de cette alternative)

Exemple  de file d’attente ou queue


Une file d’usagers pour l’accès à un service administratif (lorsqu’il n’y a ni indiscipline, ni
passe-droit) est une file d’attente ou queue. Un nouvel arrivant se place en fin de file.
Pour servir un nouvel usager, le guichetier invite la personne en première position dans
la file. Les usagers sont donc servis selon la politique ‘’premier arrivé, premier servi’’.

Exemple de séquences d’opérations sur une file d’attente

Propriétés
Dans une file d’attente :
 les opérations d’insertion sont effectuées à l’extrémité de la liste appelée
queue ou fin de liste et les opérations d’extraction sont effectuées à
l’extrémité de la liste appelée tête ;
 les éléments sont extraits dans le même que pour leur insertion

Spécification d’une file d’attente


D’un point de vue abstrait (c’est-à -dire sans les détails liés à un contexte
particulier), une file d’attente Q est un type de données abstrait muni des cinq
opérations de base (ou primitives) suivantes :
Initialiser : crée une file vide
EstVide : dit si oui ou non la file est vide
EstPleine : dit si oui ou non la file est pleine
Enfiler : insère un élément dans la file
Défiler : suprime l’élément en tête de file

Initialiser (Q) : Vide la file d’attente Q.


Clear (Q) en anglais.
{Pré-condition : aucune}
Procédure Initialiser (var Q : File d’attente) ;
début
<Opérations qui vident la file d’attente>
fin ;
{Post-condition : Q est vide}

EstVide (Q) : Renvoie vrai si la file d’attente Q est vide et faux sinon.
IsEmpty (Q) en anglais.
{Pré-condition : file d’attente initialisée}
Fonction EstVide (valeur Q : file d’attente) : booléen ;
début
Si File d’attente Q vide alors EstVide := vrai sinon EstVide := faux
fin ;
{Post-condition : délivre vrai si et seulement si la file d’attente est vide}

EstPleine (Q) : Renvoie vrai si la file d’attente Q est pleine et faux sinon.
{Pré-condition : file d’attente initialisée}
Fonction EstPleine (Q : File d’attente) : booléen ;
début
Si file d’attente pleine alors EstPleine := vrai sinon EstPleine := faux
fin ;
{Post-condition : délivre vrai si la file d’attente est pleine et faux sinon}

Enfiler (Q, x) : ajoute l’élément x à la file d’attente Q.


AddQueue (Q, x) en anglais.

Solution 1 :
{Pré-condition : File d’attente Q initialisée et de contenu C = (x1, … , xn)}
Fonction Enfiler (var Q : File d’attente ; valeur x : élément) : booléen ;
début
Si EstPleine(Q) alors Enfiler := faux
sinon début
ajouter x à la fin la file d’attente ;
Enfiler := vrai
fin
fin ;
{Post-condition : si la file d’attente est pleine au départ alors son contenu est
inchangé et la valeur retournée est faux, sinon son nouveau contenu est (x1, … , xn,
x) et la valeur retournée est vrai}

Solution 2 :
{Pré-condition : file d’attente Q initialisée, non pleine et de contenu (x1, … , xn)}
Procédure Enfiler (var Q : File d’attente ; valeur x : élément) ;
début
Ajouter x à la file d’attente
fin ;
{Post-condition : le nouveau contenu de Q est (x1, … , xn, x)}

Défiler (Q, x) : Supprime l’élément en sommet de file d’attente, si la file d’attente


est non vide.
DeleteQueue (Q, x) en anglais.
Solution 1 :
{Pré-condition : file d’attente initialisée et a le contenu (x1, … , xn)}
Fonction Défiler (var Q : File d’attente ; var x : élément) : booléen ;
début
Si EstVide(Q) alors Défiler := faux
sinon début
extraire la tête de file d’attente et l’affecter à x ;
Défiler := vrai
fin 
fin ; 
{Post-condition : x = x1 et le nouveau contenu de la file d’attente est (x2, … , xn)}

Solution 2 :
{Pré-condition : file d’attente initialisée, non vide et de contenu (x1, … , xn)}
Fonction Défiler (var Q : File d’attente) : élément ;
début
Supprimer l’élément x en tête de file d’attente ;
Défiler := x
fin ; 
{Post-condition : renvoie x1 et le nouveau contenu de la file d’attente est (x2, … ,
xn)}

On peut aussi pour des raisons de commodité, définir la primitive Premier qui
donne l’élément en tête de file d’attente sans l’extraire

Premier (Q, x) 


Head (P, x) en anglais

Solution 1
{Pré-condition : File d’attente initialisée }
Fonction Premier (valeur Q : File d’attente ; var x : élément) : booléen ;
début
Si EstVide(Q) alors Premier := faux
sinon début
x := élément en tête de file d’attente ;
Premier := vrai
fin ;
{Post-condition : met dans x la valeur en tête de file sans l’extraire et
renvoie vrai si la file n’est pas vide ; renvoie faux si la file est vide}

Solution 2
{Pré-condition : File d’attente initialisée et non vide, de contenu (x1, … ,
xn)}
Fonction Tête (valeur Q) : élément ;
début
Tête := x1 
fin ;
{Post-condition : renvoie x1 sans le supprimer de la file d’attente}

Implémentation contigüe :
La représentation utilise :
 un entier n ; la file d’attente contiendra au plus n éléments
 un tableau V0..n destiné à contenir les éléments de la file d’attente
 un entier tête qui indique l’élément en tête (élément le plus ancien) dans la file
d’attente
 un entier queue (ou sentinelle) qui indique l’emplacement où sera inséré le
prochain élément dans la file d’attente ; la position queue ou sentinelle ne
contient donc pas d’élément de la file d’attente

Dans la suite nous préférons ‘sentinelle’ à ‘file d’attente’ pour la désignation du second
indice. C’est plus conforme à la sémantique et évite aussi la confusion avec la structure
globale. Par ailleurs, las calculs d’indice se feront modulo n+1 ; autrement dit, n+1 = 0,
n+2 = 1, …

Déclarations
Const n = … ;
Type Queue = record
V : array[0..n] of élément ;
tête, sentinelle : 0 .. n
end ;

Var Q : Queue ;

Initialiser (Q) :
tête := 0 ; sentinelle := 0 ;

EstVide (Q) :
Avec Q faire
Si tête = sentinelle alors EstVide := vrai sinon EstVide := faux ;

EstPleine (Q) :
Avec Q faire
Si sentinelle = tête + n alors EstPleine := vrai sinon EstPleine:= faux ;

Enfiler (Q, x) :
Solution 1 :
Avec Q faire
si EstPleine(Q)  alors Empiler := faux
sinon début
Vsentinelle := x ;
sentinelle := sentinelle + 1 ;
Enfiler := vrai
fin ;
Solution 2 :
Avec Q faire
début
Vsentinelle := x ;
sentinelle := sentinelle + 1 ;
Enfiler := vrai
fin ;

Défiler (P, x) :
Solution 1 :
Avec Q faire
si EstVide (Q) alors Défiler := faux
sinon début
x := Vtête  ;
tête := tête + 1 ;
Défiler := vrai
fin ;

Solution 2 :
Avec Q faire
début
x := Vtête  ;
tête := tête + 1 ;
Défiler := vrai
fin ;

Premier (Q, x) :


Solution 1 :
Avec Q faire
Si EstVide(P) alors Premier := faux
sinon début
x := Vtête ;
Premier := vrai
fin ;

Solution 2 :
Avec P faire
début
x := VTête ;
Premier := vrai
fin ;

Représentation avec des pointeurs

Déclarations
Type Queue = record
tête : Cellule ;
dernier : Cellule
end ;
On peut utiliser soit une sentinelle comme pour le cas contibu, soit un pointeur sur le
dernier element comme nous procédons ici
Cellule = record
info : élément ;
suivant : Cellule
end ;
Var Q : Queue ;

Initialiser (Q) :
tête := nil ; dernier := nil ;

EstVide (Q) :
EstVide := Q = nil ;

EstPleine (Q) : Ne se produit jamais


EstPleine := faux ;

Enfiler (Q, x) : ajoute en fin de liste une cellule contenant x.


New(C) ; C.info := x ; dernier.suivant := C ; dernier := C ; dernier.suivant :=
nil ;

Défiler (Q, x) :
si EstVide(Q) alors Défiler := faux
sinon début
x := Q.info  ;
Q := P
Q.suivant 
Défiler := vrai
fin ;

Remarque : Lorsqu’on veut juste supprimer l’élément en tête de queue, on peut


concevoir une procédure Défiler avec uniquement le paramètre Q.

Premier (Q, x) :


Avec Q faire
Si Q = nil alors Premier := faux
sinon début
x := tête.info ;
Premier := vrai
fin ;

Exercice
1) Montrer qu’il qu’on peut implémenter une file pouvant contenir n+1 éléments avec
les déclarations suivante, où taille désigne la taille de la queue, et prend ses valeurs
entre 0 et = n+1 (on précisera les procédures correspondant aux primitives de base) :
Const n = … ;
Type Queue = record
V : array[0..n] of élément ;
tête : 0 .. n ;
taille : entier ;
end ;
Var Q : Queue ;

1) Montrer qu’il est difficile d’implémenter une file pouvant contenir n+1 éléments avec
les déclarations suivante, où premier est le premier élément de la file, c-à -d le plus
ancien, et dernier est l’élément le plus récent (on précisera la(es) primitive(s) qui
pose(nt) problème) :
Const n = … ;
Type Queue = record
V : array[0..n] of élément ;
tête : 0 .. n ;
dernier : entier ;
end ;
Var Q : Queue ;

Applications des Queues

Application 1 : Parcours en profondeur d’un graphe


L'algorithme de parcours en largeur d’un graphe (Breadth First Search en anglais)
consiste à parcourir ce graphe de la manière suivante : on commence par explorer un
nœud source u0, puis tous ses voisins (nœuds à distance 1 de u0), puis tous les voisins
de ses voisins no encore explorés (nœuds à distance 2) etc.

Pour mettre en œuvre cet algorithme on considère une structure de données dans
laquelle un nœud est inséré lorsqu’il est exploré et un nœud extrait lorsque vient le
moment d’explorer ses voisins. On voit alors les nœuds sont extraits dans le même ordre
que pour leur insertion. L’algorithme est le suivant :

Initialiser (Q) ;
Marquer le nœud initial u0 ;
Enfiler (Q, u0) ;
Répéter
Défiler (Q, u) ; u désigne le nœud courant 
Pour tout voisin non marqué v de u faire
début
Marquer (v) ;
Enfiler (Q, v)
fin
Jusqu’à EstVide (Q)
Exercice
Montrer qu’on peut simuler
1. une machine à une pile de mémoire avec une machine à une file de mémoire, et
donner le nombre d’opérations requises pour effectuer les opérations
élémentaires
 une machine à deux piles de mémoire avec une machine à une seule file de mémoire,
et donner le nombre d’opérations requises pour effectuer les opérations
élémentaires
 une machine à autant de piles de mémoire qu’on veut avec une machine seule file de
mémoire, et donner le nombre d’opérations requises pour effectuer les opérations
élémentaires
 un machine avec une mémoire sous forme de ruban de lecture-écriture (machine de
Turing) avec une machine à deux piles de mémoire, et donner le nombre
d’opérations requises pour effectuer les opérations élémentaires
 un machine avec une mémoire sous forme de pile avec une machine à deux files de
mémoire, et donner le nombre d’opérations requises pour effectuer les opérations
élémentaires

N.B. Les automates finis sont des machines à mémoire finie.

Equivalences entre structures de stockage


Théorème
Une structure de stockage basée sur deux piles peut être fabriquée à partir d’une unique file
d’attente.

Démonstration
La configuration de deux piles (a1, a2, … , an) avec an en tête, et (b1, b2, … , bm) avec bm en
tête, est représentée par l’unique file d’attente (a1, a2, … , an, #1, b1, b2, … , bm ,#2) avec a1 en
tête et #2 en queue.

Initialiser Pile 1

Initialiser Pile 2

Pile 1 vide

Pile 2 vide

Dépiler au sommet de la pile 1


On doit obtenir (a1, a2, … , an-1, #1, b1, b2, … , bm ,#2).
Recopier par insertion en queue et suppression en tête sauf l’élément avant #1, et jusqu’à la
rencontre de #2. Pour cela on utilise une fenêtre de taille 2 (val_prec, val_cour) et on insère
val_prec, sauf pour le couple (an, #1) caractérisé par #1.

Dépiler au sommet de la pile 2

Empiler au sommet de la pile 1

Empiler au sommet de la pile 2


Théorème
Une file d’attente peut être réalisée avec un ruban de lecture/écriture.

Démonstration
Une file d’attente F peut appliquer sur son contenu c = (a1, a2, … , an) avec a1 en tête peut
supporter les trois opérations suivantes :

Tête (F) : fonction qui produit en sortie la valeur de tête a1.

Tail (F) : supprime la valeur de tête, ce qui réalise la transition suivante :


(a1, a2, … , an)  (a2, … , an)

Append (F, x) : ajoute x en queue, ce qui réalise la transition suivante :


(a1, a2, … , an)  (a1, … , an, x)

Idée :
Pour réaliser F avec un ruban de lecture/écriture on fait la correspondance suivante

Configuration de la file Configuration du ruban


a1 a2 … an # a1 a2 … an $

Autrement dit, pour une configuration de file c = (a1, a2, … , an), le contenu de ruban est (#,
a1, a2, … , an, $), avec la tête de lecture/écriture sur # (position en gras au début du ruban).

Tail (F) : On veut donc réaliser sur la bande l’opération suivante :


(#, a1, a2, … , an, $)  (#, a2, … , an, $)
Il suffit de procéder sur la bande ainsi qu’il suit :
2. Effacer #
3. Aller à droite d’un cran
4. Ecrire #

Append (F, x) : On veut donc réaliser sur la bande l’opération suivante :
(#, a1, a2, … , an, $)  (#, a1, a2, … , an, x, $)
Il suffit de procéder sur la bande ainsi qu’il suit :
1. Aller à droite jusqu’à la rencontre de $
2. Ecrire x
3. Aller à droite d’un cran
4. Ecrire $
5. Revenir à gauche jusqu’à la rencontre de #

Nous avons établi le résultat suivant :

Théorème
Les trois classes de machines à ruban de lecture/écriture, à deux piles et à file sont
équivalentes en puissance de calcul

Théorème
Ces trois classes snt universelles, autrement dit elles peuvent effctuer tout calcul
automatique.

Démonstration (intuitive)
La machine à ruban de lecture/écriture, ou machine de Turing modélise le calcul tel que
pratiqué par l’homme avec :
- le ruban qui est le cahier
- le dispositif de lecture/écriture qui est
o la main du cô té de la pointe sur le ruban
o le cerveau de l’autre cô té

Exercice
Montrer que :
1. Toute machine à deux piles peut être réalisée par une machine à ruban
2. Toute machine à ruban peut être réalisée par une machine à file
3. Toute machine à file peut être réalisée par une machine à deux 2 piles

Exercice
Montrer comment représenter deux piles avec un seul vecteur

Exercice
Montrer comment implémenter une liste avec insertion et suppression aux deux
extrémités avec :
- un vecteur
- une structure chaînée
N.B. Dans chaque cas on écrire les 4 procédures d’insertion/extraction

Solution avec un vecteur :


Prendre V0..n-1 avec deux pointeurs g(gauche) et d(droite) indiquant des cellules
sentinelles.
Les indices sont manipulés modulo n
Les éléments sont stockés dans les positions g+1, g+2, … , d-1
Commentaire : on interdit les configurations où g = d

Liste vide :
g+1 = d

Liste pleine :
d+1 = g

Opérations (liste supposée non pleine):


Insertion de x à gauche : Vg := x ; g := g-1 modulo  ;
Insertion de x à droite : Vd := x ; d := d+1 modulo n

Solution avec une liste doublement chaînée :


Liste avec deux pointeurs g(gauche) et d(droite) indiquant des cellules sentinelles.
On interdit les configurations où g = d
La liste est doublement chaînée avec un suivant droit et un suivant gauche
Liste vide :
g.suivant_droit = d ou d.suivant_gauche = g

Liste pleine :
Non applicable

Insertion :
Insertion de x à gauche : Insertion à la droite de la sentinelle g
Insertion de x à droite : Insertion à la gauche de la sentinelle d

Suppression :
Suppression en tête gauche : Suppression du voisin droit de g
Suppression en tête droite : Suppression du gauche de g

Exercice
On considère une liste de caractères doublement chaînée.
1) Donner les déclarations nécessaires
2) Donner les instructions (ou le dessin avec numéros sur les actions) pour insérer une cellule
qui deviendra voisine droite de la cellule pointée par P
3) Donner les instructions (ou le dessin avec numéros sur les actions) pour la suppression de
la cellule pointée par P.

Exercice
On considère une agence bancaire où :
 Il y a 10 catégories de clients (étudiant, moto-taximan, fonctionnaire, enseignant, …)
 Ces catégories ont des priorités distinctes 1, 2, … , 10
 Chaque catégorie a un guichet dédié
 Lorsqu’une file est vide, on sert le client qui suit dans la file non vide la plus
prioritaire
 Il n’y a pas de borne sur la longueur d’une file d’attente
1) Proposer une structure de données pour gérer les files d’attente
2) Expliquer de manière claire et précise comment s’implémentent les opérations suivantes :
3) Tester si toutes les files dont vides
4) Insérer un nouveau client de priorité i
5) Sélectionner le prochain client à servir au guichet i

Exercice
Montrer comment implémenter une liste avec insertion et extraction aux deux
extrémités avec :
- un vecteur
- une structure chaînée
N.B. Dans chaque cas on écrire les 4 procédures d’insertion/extraction

Section 5 : Files de Priorité


Définition : Une file de priorité (en anglais priority queue) est un type de données
abstrait dans laquelle chaque élément a un attribut appartenant à un ensemble
totalement ordonné, et dont les opérations de base sont les suivantes : Initialiser,
EstVide, EstPleine, Insérer, ExtraireMax, ElémenMax.,

Initialiser (S) : Vide la file de priorité S.


Clear (S) en anglais.
{Pré-condition : aucune}
Procédure Initialiser (var S : File de priorité) ;
début
<Opérations qui vident la file de priorité >
fin ;
{Post-condition : S est vide}

EstVide (S) : Renvoie vrai si la file de priorité S est vide et faux sinon.
IsEmpty (S) en anglais.
{Pré-condition : file de priorité initialisée}
Fonction EstVide (valeur S : file de priorité) : booléen ;
début
Si file de priorité S vide alors EstVide := vrai sinon EstVide := faux
fin ;
{Post-condition : délivre vrai si et seulement si la file de priorité est vide}

EstPleine (S) : Renvoie vrai si la file de priorité S est pleine et faux sinon.
{Pré-condition : file de priorité initialisée}
Fonction EstPleine (S : Queue) : booléen ;
début
Si file de priorité pleine alors EstPleine := vrai sinon EstPleine := faux
fin ;
{Post-condition : délivre vrai si la file de priorité est pleine et faux sinon}

Insérer (S, x) : ajoute l’élément x à la file de priorité S.


AddPriorityQueue (S, x) en anglais.

Solution 1 :
{Pré-condition : file de priorité S initialisée et de contenu S = (x1, … , xn)}
Fonction Insérer (var S : file de priorité ; valeur x : élément) : booléen ;
début
Si EstPleine(S) alors Insérer := faux
sinon début
ajouter x à la fin la queue ;
Insérer := vrai
fin
fin ;
{Post-condition : si la file de priorité est pleine au départ alors son contenu est
inchangé et la valeur retournée est faux, sinon son nouveau contenu est (x’1, … ,
x’n, x’n+1) obtenu après ajout et tri par ordre croissant et la valeur retournée est
vrai}
Solution 2 :
{Pré-condition : queue Q initialisée, non pleine et de contenu (x1, … , xn)}
Procédure Insérer (var S : file de priorité ; valeur x : élément) ;
début
Ajouter x à la file de priorité
fin ;
{Post-condition : le nouveau contenu de Q est (x’1, … , x’n, x’n+1) obtenu par
ajout de x suivi du tri par ordre croissant}

ExtractMax (S, x) : Supprime l’élément de plus grande valeur d’attribut, si la file


est
non vide et met sa veleur dans x.
DeleteMax (S, x) en anglais.

Solution 1 :
{Pré-condition : file de priorité initialisée et a le contenu (x1, … , xn)}
Fonction ExtractMax (var S : file de priorité ; var x : élément) : booléen ;
début
Si EstVide(S) alors ExtractMax := faux
sinon début
extraire l’élément de plus grande valeur
d’attribut et l’affecter à x ;
ExtractMax := vrai
fin 
fin ; 
{Post-condition : x = x1 et le nouveau contenu de la file est (x2, … , xn)}

Solution 2 :
{Pré-condition : file de priorité initialisée, non vide et de contenu (x1, … , xn)}
Fonction ExtractMax (var S : file de priorité) : élément ;
début
Supprimer l’élément x1 {de plus pgrande valeur d’attribut} ;
ExtractMax := x
fin ; 
{Post-condition : renvoie x1 et le nouveau contenu de la queue est (x2, … , xn)}

On peut aussi pour des raisons de commodité, définir la primitive ElémentMax


qui donne l’élément de valeur d’attribut maximum de la file sans l’extraire

ElémentMax (S) 
MaxElement (P) en anglais

Solution 1
{Pré-condition : file de priorité initialisée }
Fonction (valeur S : file de priorité ; var x : élément) : booléen ;
début
Si EstVide(S) alors ElémentMin := faux
sinon début
x := élément de valeur d’attribut
maximum ;
Premier := vrai
fin ;
{Post-condition : met dans x la valeur de valeur d’attribut maximum sans
l’extraire et renvoie vrai si la file de priorité n’est pas vide ; renvoie faux si
la file de priorité est vide}

Solution 2
{Pré-condition : file de priorité initialisée et non vide, de contenu (x1, … ,
xn)}
Fonction ElémentMax (valeur S) : élément ;
début
ElémentMin := x1 
fin ;
{Post-condition : renvoie x1 sans le supprimer de la file de priorité }

Implémentations :
Une file de priorité peut être représentée par l’une des structures suivantes :
 vecteur non trié (avec ou sans sentinelle)
 vecteur trié (avec ou sans sentinelle)
 liste chaînée non triée (avec ou sans sentinelle)
 liste chaîne triée (avec ou sans sentinelle)tas

Les temps d’exécution des opérations de base sont alors donnés dans le tableau ci-
dessous :

Implémentation Insertion Suppression


Liste chaînée non triée de taille n O(1) O(n)
Liste chaînée triée O(n) O(1)
Vecteur non trié O(1) O(n)
Vecteur trié O(n) O(1)
Tas O(log2 n) O(log2 n)

Les Tas
Un tas est un vecteur Tas1..n dont les éléments sont dessinés sous forme d’arbre
binaire ainsi qu’il suit :
 Tas1 est à la racine
 Tas2i s’il existe est le fils gauche de Tasi et Tas2i+1 s’il existe est le fils droit
de Tasi.

Propriété
Dans un tas de profondeur h on a 1 nœud à la racine (niveau 1), 2 nœuds de niveau 2, 4
nœuds de niveau 3, et plus généralement 2k-1 nœuds de niveau k, k < h, et au plus 2h
nœuds de niveauh . En conséquence, un tas d’ordre n avec k niveaux numérotés, 0, 1, … ,
k-1 vérifie
1 + 2 + … + 2h-1 < n  1 + 2 + … + 2h
Donc h = O(log2 n)).

Extraction dans un tas.


On procède en trois étapes
1. Extraire V1
2. Placer Vn en position 1
3. Faire descendre V1 dans le tas en l’échangeant au fur et à mesure avec le plus grand de ses fils si
l’un de ses fils est plus grand que lui

Comment faire descendre dans un tas (reconstitution d’un tas perturbé à la racile)
Declaration de procedure
Procedure Descente_Tas (var V : tableau ; n : integer) ;
Fait descendre V1 dans le tas d’ordre n, à partir de la position 1
var child : integer ;
termine := faux ;
jusqu’à termine ou c’est termine lorsque Vi  Vchild ou i n’a pas de fils
begin
child := 2*i ; child est la position potentielle du plus grand fils
if child < n then if Vchild + 1 > Vchild then child := child + 1 ;
child est la position potentielle du plus grand fils
if child  n then if Vi < Vchild then  Vi est plus petit que son fils Vchild 
begin
Swap (V, i, child) ;
i := child
end
else termine := true Vi  Vchild
else termine := true i n’a pas de fils
end ;

Comment faire remonter dans un tas (insertion de Vi dans le tas V 1..i-1)
Declaration de procedure
Procedure Remontée_Tas_Rec (var V : tableau ; i : integer) ;
Fait remonter Vi dans le tas V 1..i-1
var termine := faux ; on termine si i = 0 ou Vi plus petit que son pere Vi div 2 
begin
jusqu’à termine faire
if i > 1 then if Vi div 2 < Vi then Vi plus grand que son père Vi div 2 
begin
Swap (V, i, i div 2) ;
i := i div 2
end
else termine := vrai  Vi est plus petit que son pere V i div 2 
else termine := vrai i = 0

end ;

Solution avec sentinelle : On cherche un ancêtre inférieur ou égal à lui


Declaration de procedure
Procedure Remontée_Tas(var V  : tableau ; i : integer) ;
Fait remonter Vi dans le tas V 1..i-1 jusqu’à trouver un anc^tre inférieur ou égal à lui
begin
V0 := Vi on termine avec Vi  Vi div 2
jusqu’à Vi  Vi div 2 faire
begin
Swap (V, i, i div 2) ;
i := i div 2
end
end ;

Appel de procedure
Remontee_Tas (V, i)

3 - Tri par Tas (Heap Sort) de V1 : n


On remarque que les nœuds ayant des contraintes vont de l’indice 1 à l’indice n div 2. En effet, les autres
n’ont pas de fils et ne posent donc aucun problème.

3.a Construction du tas.


On procède de bas en haut. Pour chaque nœud, il faut transformer en tas, un arbre dont tous les sous-
arbres sont déjà des tas. C’est donc la procédure Descente_tas qu’il faut appliquer.

Procedure Construite_Tas (var V : tableau ; n : integer) ;


begin
pour i := n div 2 à 1 faire Descente_Tas(V, i)
end

dans la d euxième phase, on extrait successivement l’élément maximum et on le met à sa place

Procedure Tri_par_Tas
 Déclarations
debut
Construite_Tas (V , n) ;
pour i := n à 2 faire
debut
Swap (V1 , Vi) ; ceci place Vi à sa place définitive
Descente_Tas (V, i-1) ceci transforme V1..i-1 en tas
end 
end ;
Exercice
Donner les implémentations des opérations de base pour la représentation en tas.

Autre application des listes circulaires : organisation des lignes d’un texte pour des
recherches.
Section 6 : Ensembles et Partitions

Ensembles
En informatique un ensemble (en anglais set) sur un domaine universel U, est un type de
données abstrait qui manipule des éléments de base ‘souvent appelés atomes), muni des
primitives suivantes :
- Lookup(x,S1) (Recherche de x dans S1) : délivre vrai si x  S1, et faux sinon
- S1 := S1 x : ajoute x à l’ensemble S1
- S1 := S1 - x : supprime x de l’ensemble S1
- S := S1  S2 : réalise l’union de S1, S2 et met le résultat dans S
- S := S1  S2 : réalise l’intersection de S1, S2 et met le résultat dans S
- S := S1 - S2 : réalise la différence de S1, S2 (éléments appartenant à S1 mais
pas à S2) et met le résultat dans S

Représentations prédéfinies dans les langages


Lorsque U est un type énuméré, les langages de programmation offrent généralement
une représentation utilisable via la déclaration suivante :
Type Ensemble_sur_U = set of U

Exemples :
1) Type Jour = (lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche)
Jours = set of jour
2) Type Article = (pain, beurre, fruit, sucre, thé, café, croissant)
Commande = set of Article

Pour la représentation d’un ensemble S sur U par liste chaînée, on peut avoir :
 une liste chaînée d’éléments de U sans doublons
 une liste chaînée d’éléments de U avec possibilité de doublons
 une liste chaînée d’éléments de U sans doublons et triée

Pour la représentation d’une sous-ensemble S (de taille n) de U par un vecteur, nous


pouvons avoir :
 un vecteur de taille max à éléments dans U, où max est la valeur maximale de n,
ce vecteur étant trié ou pas
 un vecteur booléen indicé par l’ensemble universel U

Dans la suite, l’évaluation des performances d’une représentation se fait en terme


d’espace mémoire et de temps moyen d’exécution des primitives. Le tableau ci-dessous
donne les performances des différentes représentations.

S1 de taille n, S2 de taille m, U de U est taille de l’ensemble universel


Lookup(x,S1 S1:=S1x S1:=S1-x S:= S1S2 S:=S1S2 S:=S1-S2  Espace mémoire
)
Liste O(n) O(n)1 O(n) O(nm) O(nm) O(nm) O(n) pour S1
Liste triée O(n)2 O(n)1 O(n) O(n+m) O(n+m) O(n+m O(n) pour S1
)
Vecteur3 O(n) O(n)1 O(n) O(nm) O(nm) O(nm) nbmax
Vect. trié O(log2 n)4 O(n) O(n) O(n+m) O(n+m) O(n+m nbmax
)
Vect Bool. O(1) O(1) O(1) O(U) O(U) O(U) O(U)
Liste5 O(L) O(1)6 O(n’) O(n’m’) O(n’m’) O(n’m’) O(n’)

1. Il faut commencer par effectuer une recherche afin d’éviter les doublons
2. Plus rapide que pour la liste non triée, lorsque x  S1.
3. Vecteur V1..nbmax indicé par des entiers et à éléments dans U
4. Recherche dichotomique
5. Liste chaînée avec doublons autorisés. Dans ce cas la taille n’=Lde la liste est
supérieure ou égale au cardinal n=S de l’ensemble.
6. Comme les doublons sont autorisés, on insère systématiquement en tête

Partitions
Une partition d’un ensemble fini E est une famille E1, E2, … , Em de parties de E telle que :
 aucune partie Ei n’est vide
 deux partie distinctes Ei et Ej sont disjointes
 la réunion des parties donne E.

Les opérations de base sur les objets de type abstrait partition sont les suivantes :
 Initialisation (P) : Crée une partition P où chaque élément est isolé
 Groupe(P, x) : donne le groupe de l’élément x de E dans la partition P
 Fusion (x, y) : produit à partir de P une nouvelle partition où les sous-ensembles
contenant x et y sont fusionnés

Dans toute la suite on suppose que E peut être représenté par un type énuméré

Représentation des partitions : solution 1


 un vecteur VE à éléments dans E
 pour x donné, Vx a une valeur qui caractérise son groupe dans la partition, et
peut être interprété comme la couleur, le chef, le représentant, …

Les temps d’exécution des opérations de base sont alors les suivantes :

Initialisatio Groupe Fusion


n
O(E) O(1) O(E)

Commentaire
Dans cette représentation, chaque indice x du vecteur V pointe vers le représentant Vx
de son groupe. C’est pourquoi Fusion(x, y) qui concerne uniquement les éléments des
sous-ensembles contenant x et y, nécessite le balayage de tout le vecteur V. Lorsque par
x’ et y’ sont les représentants de x et y, et que le représentant du nouvel sous-ensemble
ainsi créé par fusion est y’, ce balayage permet de faire Vz := y’ pour tous les éléments
tels que Vz = x’.

Représentation des partitions : solution 2


Idée : éviter le balayage de E lors d’une fusion
 un vecteur VE à éléments dans E
 Vx, lorsqu’il est différent de x, désigne alors le supérieur hiérarchique de x dans
son groupe. Ce supérieur hiérarchique est celui désigné lors d’une fusion
impliquant x alors que x était le représentant de son groupe.
 un élément x est représentant de son groupe si Vx = x.

Un sous-ensemble de la partition est alors représenté par une arborescence où chaque


élément pointe vers son supérieur hiérarchique. Une partition est représentée une
famille d’arborescences encore appelée forêt.

Les temps d’exécution des opérations de base sont alors les suivantes :

Initialisatio Groupe (x) Fusion (x, y)


n
O(E) O(h) h + h’ + 1
h (resp. h’) est la profondeur de x (resp. y) dans l’arborescence à laquelle il appartient.

Commentaire
Dans cette représentation, chaque indice x du vecteur V pointe non pas vers le
représentant de son groupe, mais vers son supérieur hiérarchique. La détermination du
représentant de x nécessite donc le parcours à partir de x, de l’arborescence à laquelle
appartient x, jusqu’à la rencontre de z tel que Vz = z, ainsi qu’il suit :
z := x ;
Répéter
z := Vz
Jusqu’à Vz = z ;

Ceci permet de trouver le représentant z de x, et prend un temps O(h) où h est la


profondeur de x.

L’opération Fusion (x, y) comporte deux étapes :


 calcul des représentants de z et z’ de x et y en temps h + h’ + O(1)
 Vmin(z,z’) := Vmax(z,z’) : le supérieur hiérarchique de min(z,z’) après fusion
est max(z,z’).

Commentaire :
Il se pose alors une question mathématique très intéressante : Quel est l’ordre de
grandeur de h dans une arborescence correspondant à une partition aléatoire d’un
ensemble de taille n ? La réponse à cette question est que h = O(log n). D’où le tableau
ci-dessous pour la performance moyenne :

Initialisatio Groupe (x) Fusion (x, y)


n
O(E) O(log2 n) O(log2 n)

Critique :
Dans cette solution, on n’exploite pas une information précieuse. En effet, lorsqu’on a
fusionné les groupes de x et y, avec par exemple z’ come nouveau représenta :
 on connaît le chemin de x vers ce nouveau représentant z’
 on connaît le chemin de y vers ce nouveau représentant z’
 sur ces chemins, tous les sommets ont pour nouveau représentant z’
La solution 3 ci-dessous exploite judicieusement cette information.

Représentation des partitions : solution 2 avec compression de chemin


 un vecteur VE à éléments dans E
 Vx désigne alors le supérieur hiérarchique de x dans son groupe
 un élément x est alors représentant de son groupe si Vx = x.

Nouvel algorithme de détermination de groupe : Dans cette solution une opération de


calcul de Groupe(x) d’un élément x amène à parcourir le chemin x= x1, … , xk = r qui va de
x à son représentant r dans l’arborescence. Connaissant maintenant r, on fait un
deuxième parcours de ce chemin en faisant pointer systématiquement tous les éléments
xi vers r. Cela permet de tasser l’arborescence en réduisant sa profondeur. La
conséquence est une réduction du nombre d’opérations nécessaires lorsque plus tard on
reviendra sur l’un de ces éléments.

Nouvel algorithme de fusion Fusion (x,y) : Dans cette solution les deux opérations de
calcul de groupes opération de calcul de groupe, Groupe(x) et Groupe exploitent l’astuce
exposée ci-dessus. Les performances sont données dans le tableau ci-dessous :

Initialisatio Groupe (x) Fusion (x, y)


n
O(E) 2h 2h + 2h’ + O(1)
h (resp. h’) est la hauteur de l’arbre auquel appartient l’élément x (resp. y)

Commentaire
Il se pose alors une question mathématique très intéressante : Quel est l’ordre de
grandeur de h dans une arborescence correspondant à une partition obtenue par tirage
aléatoire des opérations de recherche de groupe et de fusion, sur un ensemble E de taille
n ? La réponse à cette question est que h = O(log2 log2 n). Ceci est extraordinaire car
pour les valeurs usuelles de n, log2 log2 n est pratiquement constant. Par exemple
pour n = 232, log log n = 5.

Conclusion
On voit ainsi un exemple où un algorithme fait faire des économies considérables en
temps d’exécution. Une telle innovation pour un algorithme difficile qui constitue le
noyau d’une application provoque i la faillite des concurrents. Le secteur du numérique
est donc ‘’très violent’’.

Autres représentations des partitions


Une solution naturelle consiste à représenter une partition comme une suite de sous-
ensembles E1, E2, … , Em. On peut alors utiliser :
 un vecteur V dont chaque composante pointe vers un sous-ensemble, chaque
sous-ensemble ayant l’une des représentations vues précédemment pour les
ensembles (vecteur, liste chaînée)
 une liste chaînée dont chaque cellule pointe vers un sous-ensemble, chaque sous-
ensemble ayant l’une des représentations vues précédemment pour les
ensembles (vecteur, liste chaînée)
 une suite d’éléments (représentée par un vecteur ou une liste chaînée) où chaque
élément pointe vers le représentant de son groupe
 une suite d’indices de groupes, où chaque indice permet de retrouver le groupe
qu’il caractérise

Objection très sérieuse :


Ces représentations des partitions sont dangereuses car elles ne permettent pas de
garantir que les sous-ensembles sont disjoints et recouvrent E.

Application
On considère la voirie urbaine d’une commune. A la veille des élections municipales, le
maire souhaite reprofiler les routes pour redorer son blason auprès des populations, car
aucun quartier n’est relié à un autre par une voie pratiquable. Connaissant le coû t de
reprofilage de chaque tronçon, il vous demande de déterminer le budget minimum qu’il
doit prévoir pour faire en sorte que deux quartiers quelconques de la commune soient
reliées par un chemin dont tous les tronçons ont été reprofilés.

Le problème se modélise ainsi qu’il suit :


 Soit E = {1, … , n} l’ensemble des quartiers et R = {(x1,y1, c1), … , {(xm, ym, cm)}
l’ensemble des tronçons de route.
 Un ensemble de routes reprofilées crée une partition P de l’ensemble des
quartiers, où les quartiers pouvant être reliés entre eux par route reprofilée,
constituent un sous-ensemble dans la partition P.
 Une nouvelle route reprofilée entre deux quartiers q et q’, revient à fusionner les
sous-ensembles contenant ces quartiers
 Au départ chaque quartier est isolé
 Le maire souhaite avoir une partition réduite au seul sous-ensemble E.

Initialisation : P : = ({1}, … , {n}) ; {aucun tronçon n’est reprofilé}


Répéter
Reprofiler le tronçon le moins cher (x,y) tel que Groupe(x)  Groupe(y)
Jusqu’à P = (1, … , n)  ;

Cet algorithme peut être amélioré en triant les tronçons au départ :

Initialisation : P : = ({1}, … , {n}) ; {aucun tronçon n’est reprofilé}


Trier les tronçons dans l’ordre croissant des coû ts ;
i := 1 ; nb := 0 ;
Jusqu’à (nb = n-1) ou (i > m)  faire
début
Si Groupe(xi)  Groupe(yi) alors début
Reprofiler le tronçon (xi, yi) ;
nb := nb + 1
fin
i := i+1
fin;

Le temps d’exécution de ce nouvel algorithme est O(m log m) + O(mg) + O(nf) soit :
 O(m log m) pour le tri des tronçons au départ
 O(mg) pour le balayage des tronçons avec calcul des groupes (g : temps moyen de
calcul de groupe)
 O(nf) pour les fusions des groupes (f : temps moyen de fusion)

On a en définitive un temps O(m(log m + log n)) qui se décompose ainsi qu’il suit  :
 O(m log m) pour le tri des arêtes au départ
 O(m log n) pour les m recherches de groupes et fusions (une par arête)

Exercice 1
On désire représenter les fonctions d’un ensemble A vers un ensemble B, par la liste des
couples (x, f(x)), de manière à faire évoluer la fonction f au cours du temps.
1) Décrire les structures de données nécessaires
1) Donner les algorithmes pour effectuer les opérations suivantes : calcul de f(a) et
f(a) := b

Exercice 2 : Stockage des matrices creuses, c-à-d qui ont une immense majorité d’éléments
nuls. C’est notamment le cas des réseaux sociaux, d’ordre nxn, contenant m éléments non
nuls.
Indication : Utiliser :
 un vecteur Val de taille m pour stocker les éléments
 un vecteur IC de taille m pour stocker les indices de colonnes de ces éléments
 un vecteur IDL de taille n pour stocker les indices de début des lignes de la matrice.

Exemple :
0 a 0 0 b abcdef Val : éléments
0 0 0 0 0 251343 Indices de colonnes
c 0 0 0 0 10357 Débuts de ligne
0 0 d e 0
0 0 f 0 0

Tables de hachage pour la représentation des ensembles


 Il y a des cas où  :
 Les tailles n des ensembles sont très petites devant U. La représentation par
vecteur indicé par l’ensemble universel fait donc gaspiller de la place mémoire.
Exemple : sous-ensemble de jours de l’année, pour manipuler les jours fériés
 U est trop grand (par exemple l’ensemble des mots de longueur au plus 10,
pour représenter un dictionnaire) : N = 261 + 262 + … + 2610 , soit plus de 1014
 U ne peut pas être utilisé pour indicer un vecteur : l’ensemble des articles dans un
magasin

Le problème est alors d’éviter à la fois la lenteur des listes chaînées et le gaspillage de
mémoire lié aux vecteurs. On recourt aux tables de hachage, qui sont une combinaison
des vecteurs et des listes chaînées. On procède alors comme suit pour stocker un
élément x de E :
 rechercher le paquet auquel appartient x. A cet effet, on utilise une fonction h
pour trouver ce numéro de paquet h(x)
 Dans le paquet de numéro h(x), on procède à une recherche séquentielle.

On a donc une fonction h : E  0__m-1

Exercice 
Expliquer pourquoi un répertoire téléphonique est basé sur une table de hachage.

Construction la fonction de hachage h :


- h ne doit pas être trop compliqué à calculer : premier caractère, somme pondérée
- les listes doivent être équilibrées de manière à réduire le temps de recherche,
l’idéal étant un temps constant. Pour pouvoir espérer avoir un temps constant, il
fait que dans h : E  0, m-1, on ait m = O(E)

Technique de hachage : paramètres p, a, b et m.


 Coder x par un nombre de l’intervalle 0, p-1, où p est un grand nombre premier
 Choisir le nombre de paquets (ou alvéoles) m, de sorte que p > m et m = O(E)
 Poser ha,b(x) = ((ax+b) mod p) mod m, où a, b  1, p-1  .
 
Remarque
Pour toute fonction de hachage, on peut choisir une ensemble pour lequel cette fonction
marche mal. Il suffit de prendre un ensemble tel que tous ses éléments x donnent la
même valeur de h(x).

On contourne cette difficulté en choisissant au hasard, une fonction h : D  0, m-1,
dans un ensemble de fonctions H. Un tel ensemble est dit universel si pour toute clé c,
les h(c) pour h dans H sont uniformément répartis dans 0, m-1. On peut alors espérer
que
Puisque les h(c) sont répartis au hasard lorsque h varie et c est fixé,
les h(x) sont répartis au hasard lorsque x varie et h est fixé mais choisi
aléatoirement

En pratique, une classe universelle permet, en cas de déséquilibre observé, de


changer la fonction h avec un bon espoir d’obtenir cette deuxième tentative, un
hachage équilibré

Théorème
1. La classe de fonctions hp,a,b définie ci-dessus est universelle.
2. Si on a m = n2 alvéoles, alors la probabilité de collision avec un hachage universel
est inférieure à ½

Remarque
La deuxième assertion du théorème est très intéressante théoriquement car elle dit que
pour m = n2 alvéoles, on aura peu de collisions. Toutefois, cette valeur d’alvéoles n’est
pratiquable que pour de petites valeurs de n.

En pratique on adopte une stratégie à deux niveaux de hachage :


Un premier niveau classique qui crée des alvéoles de tailles n 1, n2, … , ni, …
Pour une alvéole de taille ni qui dépasse un certain seuil, on crée une fonction de
hachage hi avec mi = ni2 alvéoles, et qui aura donc peu de collisions ; cette fois mi
est raisonnable

Exercice:
Appliquer cela au répertoire téléphonique de la promotion

Section 7 : Les Graphes

Objectif :
- introduire les notions de base des graphes orientés et non orientés ;
- présenter quelques situations modélisables par des graphes ;
- donner quelques représentations dans un langage de type Pascal.

Notations et définitions de base.


Graphe orienté G = (V, A), où V est l’ensemble des sommets et A VxV est l’ensemble des
arcs. Un arc (u, v) se note aussi u v. u est l’origine et v est l’extrémité. On dit aussi que u
est prédécesseur de v et v est successeur de u. Une boucle est un arc u u dont l’origine
et l’extrémité coïncident.

Un graphe est dit étiqueté si les sommets et/ou les arcs portent des étiquettes. Deux
étiquettes peuvent être égales.

Un chemin (ou une marche) est une suite de sommets (u1 , u2 , … uk ) ou u1 u2  … 
uk, k 1, où les (ui, ui+1) sont des arcs. Un tel chemin est dit de longueur k-1. La longueur
d’un chemin est donc le nombre d’arcs. (u1) est un chemin de longueur 0. Un chemin est dit
élémentaire si tous ses sommets sont distincts.

Un circuit est un chemin u1 u2  …  uk, k 2, dont l’origine u1 et l’extrémité uk


coïncident. Un circuit est dit élémentaire si tous ses sommets sont distincts à l’exception
du premier et du dernier. Un circuit hamiltonien est un circuit élémentaire qui passe par
tous les sommets du graphe. Il est donc de longueur n.

Le demi-degré extérieur (sortant) de u est le nombre d’arcs d’origine u. Le demi-degré


intérieur (entrant) de u est le nombre d’arcs d’extrémité u.

Propriétés. Tout chemin de u à v contient un chemin élémentaire de u à v. Tout chemin de


longueur supérieure ou égale à V contient un circuit. Tout circuit contient un circuit
élémentaire.

Un graphe orienté G = (V,A) peut aussi se noter G = (V, ), où pour tout sommet u  V, (u)
= {v : (u,v)  A}.

Un graphe G = (V,A) est dit non orienté si pour tout arc (u,v)  A on a aussi (v, u)  A. On
peut alors noter G = (V,E) où V est l’ensemble des sommets et A  P2(V) est l’ensemble des
arêtes. Une arête se note {u,v}, u et v sont dits voisins. Une arête {u,v} désigne donc les
deux arcs de sens opposés (u,v) et (v,u).

Une chaîne d’un graphe non orienté, est une suite (u1 , u2 , … uk ) 1 telle que {ui, ui+1} est
une arête pour i = 1, … , k-1. Un tel chemin est dit de longueur k-1. La longueur désigne
donc le nombre d’arêtes. (u1) est une chaîne de longueur 0. Un cycle est une chaîne de
longueur k 3, dont l’origine et l’extrémité coïncident, et dont les arêtes sont distinctes.
Pour les graphes non orientés assimiler une arête à deux arcs opposés. Donc matrice
symétrique. Le degré de u dans un graphe non orienté est le nombre de voisins de u. A
partir d’un graphe orienté on obtient un graphe non orienté en ignorant les orientations.

Nous considérons sur les graphes les opérations de base suivantes :


 Test pour savoir si un couple (u,v) est un arc de G : v  (u) ?
 Calcul des voisins sortants (u) d’un sommet u
 Calcul des voisins entrants -1(u) d’un sommet u.
 Calcul des chemins de longueur 2.

Représentations d’un graphe sans labels sur les arcs

Représentation à plat
On considère G comme un ensemble d’arcs. On peut alors utiliser les représentations vues
précédemment pour les ensembles :
 une liste chaînée d’arcs
 une liste chaînée et triée d’arcs. L’ordre du tri qui s’impose est alors
lexicographique.
 Dans la représentation ci-dessus, on a en réalité une liste structurée en blocs, où
chaque bloc est constitué d’arêtes ayant la même origine
 On peut faire des économies dans la représentation précédente en évitant re
stocker plusieurs fois l’origine. Il suffit alors d’avoir :
o un vecteur qui stocke les autres extrémités
o un vecteur qui stocke les délimiteurs des blocs
C’est la représentation de Yale pour les matrices creuses.
 un vecteur de taille max, à éléments dans UxU puisque les éléments sont des
arcs), où max est la valeur maximale d’éléments. Ici, on aura max = U2.
 un vecteur trié de taille max, à éléments dans UxU puisque les éléments sont des
arcs), où max est la valeur maximale d’éléments. Ici, on aura max = U2.
 un vecteur booléen indicé par l’ensemble universel UxU. La composante (u,v)
vaudra 1 si et seulement si (u,v) est un arc de G. C’est la représentation
matricielle.

Représentation basée sur les sous-ensembles de voisins


Dans ces représentations, on identifie G = (V, ) avec l’ensemble des (u), pour u
sommet de V.
 un vecteur VV indicé par V et dont chaque élément d’indice u pointe vers (u).
Un sous-ensemble (u) est représenté à plat :
 soit sous forme de liste chaînée. On obtient un vecteur de listes d’adjacences qui
peut aussi être vu comme un hachage par l’origine
 soit sous forme de vecteur (on retrouve la représentation matricielle)
 une liste chaînée où chaque cellule est associée à un sommet u et pointe vers (u)
 Représentation de Yale (réseaux sociaux)

Propriété Soit G un graphe représenté par sa matrice d’adjacence définie par ai,j = 1 si
(i,j) est un arc, ai,j = 0 sinon. Pour tout entier k, la puissance booléenne M = Ak représente
alors l’existence d’un chemin de longueur k. Autrement dit :
mi,j = 1 si il existe dans G un chemin de longueur k allant de i à j, et mi,j = 0 sinon

Démonstration par récurrence


Evident pour les chemins de longueur 1, car correspond aux définitions.
Récurrence : Supposons le résultat vrai pour k-1 et montrons-le pour k  2. Mk = M.Mk-1. Donc
M(k)i,j = 1
si et seulement si
1rn mi,rm(k-1)r,j = 1
si et seulement si
Il existe r, 1rn, tel que mi,r = 1 et m(k-1)r,j = 1
si et seulement si
Il existe r, 1rn, tel que (i,r) arc de G, et il y a un chemin de G de longueur k-1 de r à j
si et seulement si
Il existe un chemin de longueur k entre i et j.

Pour évaluer les performances des diverses représentations, il faut remplir le tableau ci-
dessous pour un graphe ayant n sommets et m arcs, et d le degré moyen :

Mémoire v  (u) ? Calcul de (u) Calcul de -1(u) Chemins de


longueur 2 à partir
de u
Liste des O(m) 0(m) O(m) O(m) O(m2)
arcs
Matrice n2 O(1) O(n) O(n) O(n2)
Liste adj. O(n+m) O((u)) O(1) O(m) O((u)m)

Applications : Réseau routier, graphe de dépendance pour des calculs, graphe de
communication dans un réseau, graphe des appels de procédures (circuit = récursivité),
voyageur de commerce,

Questions classiques.
1. Chemins et composantes (fortement) connexes 
2. Arbres couvrants (minimaux par rapport à la somme des poids des arêtes, à la
somme des chemins, …) : bitumer un ensemble minimal d’axes de manière à
relier toutes les villes.
3. Parcours en profondeur, Parcours en largeur : pour explorer tous les nœuds.
4. Tri topologique pour les graphes orientés sans circuits : application à
l’ordonnancement des tâ ches.
5. Calcul de tous les (plus courts chemins) : atteignabilité,
6. Flots : circulation routière.
7. Couplage (maximal) équipages.
8. Voyageur de commerce

Quelques problèmes de théorie des graphes. Isomorphisme. Planarité (théorème de


Kuratovski). Coloriage et nombre chromatique. Cliques (maximales) : emplois du temps,
Jeux

Le chou, la chèvre et le loup. Un passeur disposant d’une barque doit faire traverser
une rivière à un chou, une chèvre et un loup. Outre le passeur, la barque peut contenir au
plus une des entités à faire traverser. D’autre part, pour des raisons de sécurité, le
passeur ne doit jamais laisser seuls, la chèvre et le chou, le loup et la chèvre. Comment le
passeur doit-il procéder ?

Le problème des philosophes Cinq philosophes chinois placés autour d’une table
disposent de 5 baguettes placées entre eux. Chacun a besoin de 2 baguettes pour manger
du riz. Montrer que la situation est bloquée si chacun d’un se saisit de la baguette à sa
gauche.
Modélisation : philosophes A, B, C, D, E. un arc (X,Y) si X requiert une ressource que
possède Y. L’interblocage est modélisé par l’existence d’un circuit dans ce graphe.

Exploration en profondeur.
Initialement aucun sommet n’est marqué.
Pilevide ;
Empiler le sommet initial s ;
Marquer s ; {marquage pré-ordre}
Jusqu’à Pilevide faire
debut
s := sommet de pile ;
si tous voisins de s marqués alors Depiler {éventuellement numérotation post-
ordre}
sinon debut u := voisin non marqué de s ;
marquer u ; {marquage pré-ordre}
Empiler(u)
fin
fin ;

Exploration en largeur.
Initialement aucun sommet n’est marqué.
Filevide ;
Insérer le sommet initial s en tête de file ;
Marquer s ;
Jusqu’à Filevide faire
debut
Insérer en queue tous les voisins non marqués de la après les avoir marqués ;
Supprimer la tête de file
fin ;

Composantes connexes et arbre couvrant.


V = {1, … , n} et E = {(x1,y1), … , {(xm, ym) }
Initialement : Arbre couvrant H = (V, E’), E’ =  ; Composantes connexes : {1}, … , {n}.
Pour i := 1 à m faire si CC(xi)  CC(yi) alors
début Fusionner (CC(xi) , CC(yi) ) ;
Insérer (xi,yi) dans E’
fin ;
Remarques : la représentation des composantes connexes sous forme de partitions est
donc particulièrement indiquée. Chaque ensemble de la partition est une arborescence.
La fusion peut se faire en pointant la racine du moins profond vers la racine du plus
profond. Le fait de profiter des parcours pour tasser les arborescences constitue une
optimisation.

Algorithmes Gloutons pour l’arbre minimum (on suppose que les arêtes ont des
poids)

Algorithme de Kruskal
a) Trier les arêtes dans l’ordre croissant. On supprime les ex-aequo soit avec l’ordre
lexicographique, soit avec des  ajouts tels que n < écart minimum entre poids
d’arêtes.
b) Appliquer l’algorithme précédent en traitant les arêtes dans l’ordre croissant des
poids
Prim
Initialisation : L’arbre H est réduit à un sommet quelconque r.
Jusqu’à ce que H ait n-1 arêtes faire
sélectionner l’arête {u, v} de poids min telle u  H et v  H et l’insérer dans H ;

Existence de chemins (Warshall).


Considérer la matrice booléenne d’adjacence A. Ak : existence d’un chemin de longueur k.
I + A + … + Ak , existence d’un chemin de longueur  k.

Trouver un cycle (resp. circuit) dans un graphe non orienté (resp. orienté).
a) Parcours en profondeur avec numérotation post-ordre ;
b) Existence de cycle si et seulement s’il existe arête (u, v) telle que num(u) <
num(v).
Tri topologique
a) Parcours en profondeur avec numérotation post-ordre ; {u1, … , un} ;
b) Ordre topologique : un, un-1, … , u1

Application : ordonnancement de tâ ches, les arcs représentant les contraintes de


précédence.

Distances minimales de Dijkstra (cf. idée de Prim)


Initialisation : S est réduit à un sommet r ; Dist (u) = arc(r,u).
Jusqu’à ce que tous les sommets soient atteints faire
debut Sélectionner u  S tel que Dist(u) minimum et insérer u dans S et enregister
Pred(u);
Mettre à jour Dist(v) et Pred (v) pour v  S {si Dist(v) > Dist(u) + arc(u,v)}
fin ;
Complexité : O(m log n)

Tous les plus courts chemins


Dijkstra : Applique l’algorithme précédent à partir de tous les sommets.
Complexité : O(mn log n).

Floyd (cf. Warshall)


Ak : longueurs des plus courts chemins ayant leurs sommets intermédiaires dans {1, … ,
k}.
Remarquer que ai,j k+1: = Min { ai,j k , ai,k+1 k + ak+1,j k }
Pour i := 1 à n faire
Pour j := 1 à n faire ai,j: = arc(i,j) ; {initialisation}
Pour k := 1 à n faire {calcul de Ak+1}
Pour i := 1 à n faire
Pour j := 1 à n faire {calcul de ai,jk+1}
debut d := ai,k+1 + ak+1,j ;
si d < ai,j alors ai,j: = d
fin ;

Graphes planaires : Aptitude à être dessiné sans croisement d’arêtes.


Théorème de Kuratowski  : Un graphe est planaire ssi il ne contient ni K3,3 ni K5.
Application : coloriage de cartes (pays = face).
Attention : Bien distinguer le test de planarité de l’algorithme de dessin.

Coloriage de graphes.
Definition : attribuer des couleurs pour que 2 sommets voisins n’aient pas la même couleur.
Graphe k-chromatique : coloriable avec k couleurs. Nb chromatique = Nb min de couleurs.
Application : organisation des examens. G = graphe des conflits (lorsque deux cours sont
suivis par le même étudiant. Graphe biparti ssi bichromatique. K-colorabilité = Problème
NP-Complet pour k > 2 (vérifiable en temps polynomial, solution polynomiale ssi P = NP).
Algorithme approché : colorier successivement des ensembles indépendants maximaux.

Conjecture de 4 couleurs 4-colorabilité des graphes planaires (énoncé en 1852, résolu par


Appel et Haken en 1977 avec le recours à l’ordinateur pour certains aspects énumératifs).
Exercices sur le cours INFO104
L1 Semestre 2 Année 2018

Exercice
Donner les automates qui permettent de réaliser les calculs suivants dans un texte
terminé par le caractère ‘#’ :
1. Nombre de caractères alphabétiques
2. Nombre d’occurrences de l’article ‘le’ (sans distinction majuscle/minuscule)
3. Nombre de mots

Exercice
Un SMS est une suite de mots (sans distinction majuscle/minuscule) séparés par un ou
plusieurs espaces et terminée par le mot ‘fin’. Les mots sont divisés en deux catégories :
- les mots facturés
- un mot de service : orange
Un texte contient une suite de SMS, terminée par un SMS sans mot facturé.
Donner l’automate qui imprime chaque télégramme en respectant les règles suivantes :
1. deux mots sont séparés par un seul espace.
2. Tout mot est de longueur au plus 5 et les mots trop longs sont tronqués et la
partie excédentaire de droite est ignorée.
3. Chaque télégramme est suivi du nombre de mots facturables.
N.B. Faire une analyse précise qui en explique le principe et les notations.

Exercice
Dire pourquoi la procédure ci-dessous de recherche de val dans V[1..n] n’est pas correcte.
i := 1 ;
jusqu’à (V[i] = val ou i > n) faire i := i+1 ;
trouve := (i  n)
Donner une astuce liée à la réécriture et au calcul de la condition de sortie (V[i] = val ) ou (i >
n), pour que malgré tout, cette procédure s’exécute correctement dans un langage donné.

Exercice
Donner un algorithme de recherche de gauche à droite (selon les indices croissants) de la
dernière occurrence de val dans V[1..n] :
1. Sans sentinelle
2. Avec sentinelle

Exercice
Donner l'algorithme qui imprime colonne par colonne, les éléments d'une matrice triangulaire
inférieure d’ordre n.

Exercice (on suppose que n est impair)


1. Donner un algorithme simple qui calcule le minimum et le maximum d’un vecteur
V[1..n].
2. Quel est le nombre de comparaisons effectuées ?
On considère maintenant un algorithme qui calcule le maximum et le minimum de V[1..n]
en traitant successivement les couples (V[2i], V[2i+1]) pour i = 1, 2, ...
3. Montrer que si on connaît le maximum et le minimum de V[1..2i-1], alors on peut
calculer en 3 opérations, le minimum et le maximum de V[1..2i+1]. Indication :
commencer par comparer V[2i] et V[2i+1].
4. Quel est le temps d’exécution de ce nouvel algorithme 9.3 ?

Exercice (Drapeau quadricolore)


On considère le tri d’un vecteur V[1..n] dont les éléments ont quatre valeurs possibles : vert,
rouge, jaune et bleu. Montrer qu’on peut adapter la segmentation pour trier en temps linéaire
ce vecteur, en utilisant quatre pointeurs dont chacun évolue toujours dans le même sens
(gauche vers droite ou droite vers gauche).
N.B. On précisera l’initialisation, l’invariant et le variant.

Exercice Tri par segmentation (Tri Rapide ou Quick Sort)


Idée : Procéder par récurrence pour trier un vecteur V[min..max] :
1. Si max-min  s (seuil qu’on se donne), alors utiliser une méthode simple, sinon :
a. Segmenter V[min..max] de sorte que 
V[max] = M soit en position k, V[i] ≤ M pour i < k, et M ≤ V[j] pour k < j.
b. Trier ensuite par récurrence V[min..k-1] et V[k+1..max]
Indication pour la segmentation de V[min..max] : on considère deux tamis :
i initialement en position min, et j initialement en position max.
On considère une itération où, à chaque phase,
2. le tamis j se déplace vers la gauche en laissant à sa droite les éléments supérieurs ou
égaux à M,
3. puis le tamis i est déplacé vers la droite en laissant à sa gauche les éléments inférieurs
ou égaux à M.
4. Lorsque les deux tamis sont bloqués, on échange V[i] et V[j].
A la fin de l’itération on a i = j, et on échange V[i] avec V[max].
1) Ecrire cet algorithme
2) Déduire un algorithme de tri
3) Donner un cas très simple où l’un des segments est vide (i = j = max)
4) Proposer un algorithme de segmentation comportant une seule boucle et où à chaque pas on
essaie autant que possible d’équilibrer les longueurs des segments V[min,i] et V[j,max].
Indication : Distinguer 4 cas : V[i]  V[max], V[i] > V[max], V[j]  V[max], ou V[j] <
V[max]
5) Simuler cet algorithme pour trouver la distribution des longueurs des segments obtenus.

Exercice
On considère une suite de messages. Chaque message est terminé par le mot ‘FIN’ et est
constitué d’une suite de mots (mot : suite de caractères non blancs) séparés par un ou
plusieurs espaces. Tout mot est suivi par au moins un espace. Il y a deux catégories de mots :
les mots facturables et les deux mots de service non facturés, ‘STOP’ et ‘FIN’. Le dernier
message ne contient aucun mot ou uniquement des ‘STOP’.
Donner l’automate qui permet d’imprimer la suite des messages en respectant les règles
suivantes :
 Les mots sont séparés par exactement un espace. Un mot ne peut dépasser 12
caractères : les mots trop longs sont tronqués à la longueurv12 sur la droite
 Les texte de chaque message est suivi par le nombre de mots facturés et le nombre de
mots tronqués.
Tiré de : M. Lucas, J.P. Peyrin et P.C. Scholl, Algorithmique et représentation des données,
Tome 1 : Files et automates d’états finis, Masson, 1985.

Exercice
Vérifier le tableau ci-dessous pour le temps d’exécution des opérations de base surles
ensembles. Pour les vecteurs on a soit un indiçage par l’ensemble universel U, soit un
vecteur à éléments dans U et de taille fixée par la taille maximum nbmax.
S, S1 de taille n, S2 de taille m, c le coû t du tri, N = U est taille de l’ensemble universel
Lookup Insert Delete Union Intersection Différence Mémoire
Liste O(n) O(n) O(n) O(nm) O(nm) O(nm) O(n)
Liste triée O(n) O(n) O(n) O(n+m) O(n+m) O(n+m) O(n)
Vect.(1) O(n) O(1) O(n) O(nm) O(nm) O(nm) nbmax
Vect. trié O(log n) O(n) O(n) O(n+m) O(n+m) O(n+m) nbmax
Vect Bool. O(1) O(1) O(1) O(U) O(U) O(U) O(U)
Liste (3) O(n) O(1) O(n) O(n+m) O(nm) O(nm) O(n)
Liste (3) : Liste avec doublons, n est alors plus grand que le cardinal de l’ensemble.
Exercice
Donner un algorithme qui dit si le vecteur V[1..n] précède le vecteur W[1..m] dans l’ordre
lexicographique.

Exercice
Donner un algorithme qui imprimer la liste des éléments distincts qui apparaissent dans
un vecteur V[1..n].

Exercice
Donner un algorithme qui prend en entrée un entier et imprime les bits de sa
représentation binaire.

Exercice
Donner un algorithme qui calcule le PGCD (plus grand commun diviseur) de deux
entiers strictement positifs a et b.
Indication : si a > b alors PGCD(a, b) = PGCD(a-b, b).

Exercice
On considère une matrice creuse M d’ordre nxn, c’est-à -dire qui contient très peu
d’éléments non nuls. Nous notons dans la suite ce nombre par Nb.
1) Donner deux exemples d’applications des matrices creuses
On considère une représentation (dite de Yale) sous la forme de trois vecteurs :
5. Un vecteur A[1..Ne] contenant les éléments non nuls de A de haut en bas et de
gauche à droite ;
6. Un vecteur IA[1..Nl+1] qui donne pour chaque ligne i de M, l’indice IA[i] dans A du
premier élément de la ligne i de M. On a IA[Nl+1] = 0 ;
7. Un vecteur JA[1..Ne] qui donne l’indice de colonne de chaque élément de IA.
2) Appliquer cette représentation à un exemple de votre choix
3) Dire comment on peut à partir de cette représentation, retrouver l’élément M(i,j) de
M.

Exercice
1) Donner un algorithme d’évaluation de P(x), pour un polynô me P(x) = a0 + a1x + …
anxn. par accumulation successive des monô mes aixi pour i = 0, … , n.
2) Quel est e nombre de multiplications et le nombre d’additions requis ?
3) Donner un autre algorithme basé sur le schéma (((a4x + a3)x + a2)x + a1)x + a0.
4) Quel est e nombre de multiplications et le nombre d’additions requis ?
5) Déduire un algorithme efficace pour le calcul de xa, où a est un entier de code binaire
an an-1 … a2 a1 a0.
6) Donner des exemples où les gains associés aux schémas des questions 3 et 5 est
considérable.

Exercice
1) Chercher sur internet l’algorithme de Knuth-Morris-Pratt pour la recherche d’une
chaîne V[1..n] dans un texte T[1..m], le comprendre, le réécrire avec vos propres notations et
l’expérimenter.
Une solution
Lorsqu’on échoue au ième test V[i] = T[k], c-à-d après i-1 succès, on reprend le test après avoir
été rétrogradé du grade i-1 au grade lpgps [i-1], correspondant à la longueur du plus grand
préfixe strict de V[1..i-1] qui est aussi suffixe strict de V[1..i-1]. On refait la comparaison
dans le nouveau grade. Autrement dit on compare V[lpgps [i-1]+1] et T[k].
Construction de l’automate (on suppose connu le tableau lpgps[i]
Pour tout caractère c faire si c = V[1] alors [0,c] := 1 sinon [0,c] := 0 ;
Pour i := 2 à n faire si c = V[i] alors [i-1,c] := i sinon [i,c] := [lpgps[i],c] ;
Calcul du tableau lpgps [0..n-1] (cf figure ci-dessous) :
lpgps[1] := 0 ; i := 2 ; j := 1 ;
j-1 = lpgps[i-1] et on espère calculer lpgps[i] en prolongeant lpgps[i-1 grâce à (V[i],V[j])
Jusqu’à i > n faire calcul de lpgps[i] sachant que j-1 = lpgps[i-1]
Si V[i] = V[j] alors début lpgps[i] := j ; j := j+1 ; i := i+1  fin
Sinon si j > 1 alors on a échoué et j > 1 j := lpgps[j-1]+1 
Sinon on a échoué à j = 1début lpgps[i] := 0 ; i := i+1 fin ;
2) Montrer que, si on échoue au ième test V[i] = T[k], c-à-d après i-1 succès, on peut considérer
que T[k] a été traité et passer directement, pour le traitement du caractère suivant T[k+1], au
grade g tel que g est la longueur du plus long préfixe de V[1]V[2]…V[i-1]T[k] qui est aussi
suffixe de V[1]V[2]…V[i-1]T[k].
Réponse : il suffit e faire le dessin
3) Dire pourquoi cette propriété n’est pas utilisable en pratique
Réponse : Pour l’utiliser il faut calculer les lpgps non pas pour les V[1..i], i = 1, … , n-1, mais
pour toutes les chaînes de longueur au plus n-1 sur l’alphabet.

Exercice Reconnaissance de mot via un automate non déterministe


Un automate est di non déterministe si pour un état i et une entrée c, on à plusieurs
choix possibles pour l’état suivant [i,c].

Définition : un automate non déterministe reconnaît un mot si, parmi les états
atteignables à partir de l’état initial avec ce mot, il y en a un qui est un état de
satisfaction. Autrement dit, un mot ‘’reconnu’’ peut pour certains choix de transition,
aboutir à un état qui n’est pas de satisfaction.

Exemple : Automate non déterministe pour V = ababaab


Un automate non déterministe n’est donc pas une machine directement utilisable. C’est
plutô t une spécification de la solution à un problème. On l’utilise ainsi qu’il suit :
1. Construire un automate non déterministe (spécification de la solution)
2. Transformation automatique de l’automate non déterministe en automate
déterministe équivalent.
C’est un bel exemple d’approche la production de logiciels. On conçoit un environnement
de modélisation dans lequel :
1. L’utilisateur peut spécifier (ou modéliser) la solution
2. Il y a un outil de transformation de la spécification en programme
Pour effectuer la transformation de la phase 2, on calcule tous les états que l’automate
peut atteindre, et on regarde si parmi es états il y en a un qui est de satisfaction.
Exemple : Application à V : ababaab.
Remarque : on peut maintenant construire un automate qui reconnaît plusieurs mots.

Exercice
Donner un automate qui reconnaît les mots abab et abba dans un texte.

Exercice
On considère la recherche d’une chaîne V[1..n] dans un texte T[1..m] dont les caractères
sont différents deux à deux : T[i]  T[j] pour i  j. déduire des approches par automates, un
algorithme très simple qui s’exécute en temps au plus O(m).

Représentation d’une famille de chaînes de caractères


On désire représenter une famille de n mots dont chacun a une longueur au plus égala à
max.
1) Donner une solution très simple à base de tableau.
2) Dire pourquoi cette solution se traduit par un gaspillage d’espace.
On suppose que ces mots sont numérotés 1, 2, … , n.
3) Donner une nouvelle représentation utilisant deux vecteurs et qui est plus compacte
que celle de la question 1.
Indication : s’inspirer du stockage des matrices creuses.

Exercice
Chercher sur internet l’algorithme de Rabin-Karp pour la recherche d’une chaîne V[1..n]
dans un texte T[1..m], le comprendre, le réécrire avec vos propres notations et l’expérimenter.

Problème  : Vecteurs, polynômes et grands entiers


1) Donner une représentation vectorielle (avec dessin), pour l’ensemble des polynômes de
degré au plus n et à coefficients entiers, P(x) = a0 + a1x 1 + … + akxk + … + anx n.
2) Décrire les procédures qui permettent de faire les opérations suivantes :
 Détermination du degré d’un polynôme
 Somme de deux polynômes P1 et P2 avec pour résultat un polynôme P3.
3) On suppose que ces polynômes ont au plus 10 monômes non nuls. Proposez une nouvelle
représentation vectorielle (avec dessin) plus adaptée, avec illustration sur les deux cas
suivants : P = -1 + a100x100 et P = 5x10 + 2x – 8x30
4) Décrire pour cette nouvelle représentation, les procédures qui permettent d’effectuer les
opérations suivantes :
 Détermination du degré d’un polynôme
 Somme de deux polynômes P1 et P2 avec pour résultat un polynôme P3.
Dans la suite on suppose qu’on dispose d’un langage de programmation pour lequel le plus
grand nombre de type entier est 9999.
On désire représenter de grands entiers positifs ou nuls (entiers naturels) ayant jusqu’à n = 4m
chiffres décimaux.
5) Proposer une représentation vectorielle sachant qu’on effectuera uniquement des
comparaisons
6) Donner un algorithme de comparaison de deux grands entiers naturels
7) Proposer une représentation vectorielle sachant qu’on effectuera aussi des additions et
donner un algorithme d’addition de deux grands entiers naturels
8) Donner un algorithme de calcul de A-B où A et B sont deux grands entiers naturels, A  B
9) Donner une représentation des entiers relatifs (positifs, nuls ou négatifs)
10) Donner un algorithme d’addition de deux grands entiers relatifs
On désire représenter de très grands entiers naturels (ayant un très grand nombre non borné a
priori de chiffres).
11) Proposer une représentation de ces très grands entiers naturels par liste chaînée sachant
qu’on effectuera uniquement des comparaisons et des additions
12) Donner un algorithme d’addition de deux très grands entiers naturels
13) Proposer une représentation de ces très grands entiers par liste chaînée sachant qu’on
effectuera des comparaisons, des additions et des multiplications
14) Donner un algorithme de multiplication d’un très grand entier naturel par un nombre
naturel inférieur ou égal à 99.
15) Déduire un algorithme de multiplication de deux très grands entiers naturels

Problème 1 (Listes chaînées et caractères)


On s’intéresse à la représentation des mots sur les caractères alphabétiques
1) Sachant qu’un mot est de longueur au plus 20, donner une structure de données sous forme d’un
vecteur très simple pour la représentation d’un mot.(On suppose que ‘#’ n’apparaît dans aucun
mot)
2) Sachant qu’un caractère occupe un octet en mémoire, quel est l’espace mémoire nécessaire pour
la représentation d’un mot proposée dans la question 1 ?
3) Proposer une représentation d’un mot par une liste chaînée de caractères, en l’illustrant sur le
mot ‘examen’
4) Sachant que votre ordinateur a 4 Go de RAM, combien d’octets faut-il pour représenter un
pointeur (adresse) en machine ? (Indication : 210 = 1024  103)
5) Combien d’octets faut-il pour représenter une cellule de la liste chaînée proposée à la question 3
6) Sachant qu’un mot comporte en moyenne 7 caractères :
6.1) Quel est l’espace mémoire nécessaire en moyenne pour la représentation de la question
3 pour un mot ?
6.2) Quelle est la meilleure des deux représentations proposées dans les questions 1 et 3. (1
pt)

Soit un texte correspondant à une suite de caractères alphabétiques et subdivisé en lignes, chaque
ligne étant numérotée. On désire construire pour chaque mot du texte, une cellule d’index
contenant  :
- le mot lui-même
- le nombre de fois que ce mot apparaît dans le texte
- la liste des numéros de ligne où ce mot apparaît (on ne connaît pas a priori le nombre
maximum de lignes d’un texte)
7) Donner la représentation pour une cellule d’index

La recherche des occurrences d’un mot donné peut être faite à partir de n’importe quelle ligne. Par
exemple, si un mot apparaît dans les lignes 1, 7, 10, 56 et qu’on cherche ses occurrences à partir de
la ligne 9, on aimerait obtenir comme résultat la liste de numéros de lignes suivante : 10, 56, 1, 7.
8) Quelle est la structure de données adaptée pour la représentation de l’ensemble des lignes où
apparaît un mot donné ?. Donner les déclarations correspondantes.
9) Quelle est la structure de données adaptée pour la représentation de l’index. (2 pts)
Exercice : File de priorité
On considère des clients en liste d’attente pour un vol Yaoundé-Maroua. Parmi ces clients très
nombreux en raison de la Fête du Ramadan, il y en a dans les classes suivantes : Classe économique
(50 000 FCFA le billet), Classe Premium (70 000 FCFA le billet), Classe Affaires (100 000 FCFA le
billet), Première classe (120 000 FCFA le billet). Camair-Co accorde naturellement la priorité aux
clients les plus chers et, pour des clients ayant des billets de même prix, à ceux qui sont arrivés les
premiers.
Proposer une représentation pour cette liste d’attente

Exercice
On considère un problème où on gère des éléments de la manière suivante :
 Chaque élément est inséré une seule fois
 Les autres opérations sur un élément sont des recherches et des modifications sont
groupées par vagues survenant dans un laps de temps très court.
Dire de manière claire et précise comment peuvent se réaliser les opérations suivantes en utilisant
les doublons :
- la recherche d’un élément
- la modification d’un élément
- la suppression d’un élément

Exercice
On considère la représentation d’une relation R d’un ensemble E vers un ensemble F. Une telle
relation est tout simplement sur le plan mathématique, un ensemble formé de couples (x,y) où x est
élément de E et y élément de F.
Proposer une représentation d’une relation :
 Dans le cas général
 Dans le cas particulier des arêtes d’un graphe
 Dans le cas particulier où la relation est en réalité une fonction

Exercice (compréhension des algorithmes de tri)


Le tri par sélection et le tri par comparaisons-échanges ont tous les deux une performance
moyenne de O(…) pour un vecteur de n éléments. Toutefois le tri par sélection a deux
désavantage : D’abord, il n’est pas …. Ensuite, lorsqu’on affine le tri par comparaisons-
échanges, on peut minimiser la longuer parcourue à chaque …, ce qui n’est pas vra pour ….

Le tri par fusions successives est de complexité moyenne O( …), ce qui est optimum. Son
gros défaut est qu’il requiert, pour trier un vecteur v l’utilisation d’un autre … pour effctuer
les …. Le tri par segmentation contourne ce défait en évitant les … tout en conservant …
Mlaheureusement, sa performance moyenne de … cache une grande disparité. Par exemple, il
se comporte très mal et requiert un temps … pour une suite déjà triée, ce qui est vraiment
dommage. Pour avoir un algorithme de tri idéal, on pourrait combiner les avantages des deux
algorithmes, en adoptant le tri par … tout en veillant à ce que, à chaque …, les deux vecteurs
obtenus soient ….

Exercice
On désire trier un vecteur V1..n contenant des éléments très gros pour lesquels l’échange de
deux éléments est coûteux en temps. Pour cela on a une astuce qui consiste à utiliser un
vecteur appelé index Pos1..n tel que Posi donne la position de l’élément numéro i dans V.
1) Initialiser le vecteur Pos
2) Comment devra être Pos après le tri ?
3) Dire par quoi il faut remplacer l’instruction suivante :
Si Vi < Vj alors Echanger (Vi, Vj).