Les files
- Connaître la structure de file et savoir s’en servir
1 Introduction
La deuxième structure dynamique abstraite que nous allons voir est la file. Il s’agit d’une structure basée sur le principe FIFO (First In First Out) : le premier élément entré est le premier à sortir.
On l’utilise pour le traitement des files d’attentes, les communications entre processus, les parcours de graphes (BFS), etc.
2 Structure d’une file
2.1 Principe
Une file F est une structure de données qui ne propose que les 4 opérations suivantes :
Lire(F): renvoie la valeur de l’élément en tête de la fileEstVide(F): renvoieVRAIsi la file est vide,FAUXsinonEnfiler(F, x): ajoute l’élémentxà la fin de la fileDéfiler(F): supprime l’élément en tête de la file et renvoie sa valeur
En théorie, chacune de ces opérations s’exécute en temps constant : \(O(1)\).
Considérons la séquence d’instructions suivante sur une file initialement vide :
Enfiler(F, 1)→ File :[1](1 en tête)Enfiler(F, 3)→ File :[1, 3](1 en tête, 3 en fin)Défiler(F)→ Renvoie 1, File :[3]Défiler(F)→ Renvoie 3, File :[](vide)
Représentation horizontale (convention usuelle) :
Enfiler(F,1) Enfiler(F,3) Défiler(F) Défiler(F)
[] → [1] → [1|3] → [3] → []
(tête) (tête|fin) (tête)
Le principe FIFO signifie que le premier élément enfilé (1) est le premier à être défilé.
2.2 Implémentation naïve
On peut implémenter une file contenant au plus \(n\) éléments à l’aide d’un tableau de taille \(n\) et deux indices tete et queue :
- Le tableau
F[1..n]contient les éléments enfilés - Deux variables entières
teteetqueueindiquent respectivement la position de l’élément en tête et la position disponible pour enfiler un élément à la fin - La file correspond aux éléments
F[tete : queue - 1]
ALGORITHME EstVide(F):
DONNEES
F : tableau de taille n
DEBUT
SI tete = queue ALORS
RENVOYER VRAI
SINON
RENVOYER FAUX
FSI
FIN
ALGORITHME Lire(F):
DONNEES
F : tableau de taille n
DEBUT
RENVOYER F[tete]
FIN
ALGORITHME Enfiler(F, x):
DONNEES
F : tableau de taille n
DEBUT
SI queue = n ALORS
AFFICHER "débordement"
RENVOYER ERREUR
SINON
F[queue] ← x
queue ← queue + 1
FSI
FIN
ALGORITHME Defiler(F):
DONNEES
F : tableau de taille n
DEBUT
SI EstVide(F) = VRAI ALORS
RENVOYER ERREUR
SINON
tete ← tete + 1
RENVOYER F[tete-1]
FSI
FIN
Cette implémentation naïve a un problème majeur : même si la file est logiquement vide après plusieurs enfilements et défilements, l’indice queue continue d’avancer. On peut donc avoir un débordement même si la file ne contient que quelques éléments !
Solution : Utiliser un tableau circulaire.
2.3 Implémentation avec tableau circulaire
Il faut que lorsque la queue arrive à \(n\), le prochain élément soit à l’indice 1 (on “boucle” le tableau).
Tableau de taille 7 avec les éléments 7, 3, 9 aux positions 2, 3, 4 :
Indices: 1 2 3 4 5 6 7 (1) ...
[ ] [7] [3] [9] [ ] [ ] [ ] (retour à 1)
↑ ↑
tête queue
Après la case 7, on revient à la case 1 grâce à l’opération modulo.
Algorithmes corrigés avec modulo :
ALGORITHME Enfiler(F, x):
DONNEES
F : tableau de taille n
DEBUT
SI (queue + 1) mod n = tete ALORS
AFFICHER "débordement"
RENVOYER ERREUR
SINON
F[queue] ← x
queue ← (queue + 1) mod n
FSI
FIN
ALGORITHME Defiler(F):
DONNEES
F : tableau de taille n
VARIABLES
x : entier
DEBUT
SI EstVide(F) = VRAI ALORS
RENVOYER ERREUR
SINON
x ← F[tete]
tete ← (tete + 1) mod n
RENVOYER x
FSI
FIN
- Les variables
teteetqueuesont globales et partagées entre toutes les opérations - L’opération
mod npermet de “boucler” les indices - La condition de débordement devient :
(queue + 1) mod n = tete - Chaque opération reste en temps \(O(1)\)
- On perd une case (ne peut stocker que \(n-1\) éléments) pour distinguer file vide de file pleine
3 Exemple : Affichage de tous les nombres binaires entre 1 et \(n\)
| Problème | Affichage des écritures binaires des nombres entre 1 et \(n\) |
| Entrée | Un entier \(n\) |
| Sortie | Les écritures en binaire des nombres de 1 à \(n\) |
3.1 Version naïve
Principe : On énumère les entiers de 1 à \(n\) et à chaque fois, on les convertit en binaire.
ALGORITHME ConvertirBase2(x):
DONNEES
x : entier en base 10
VARIABLES
C : chaîne de caractères vide
DEBUT
TQ x > 0 FAIRE
SI (x mod 2) = 1 ALORS
C ← Concatener("1", C)
SINON
C ← Concatener("0", C)
FSI
x ← ⌊x / 2⌋
FTQ
RENVOYER C
FIN
Complexité : \(\Theta(\log_2(x))\)
ALGORITHME EnumereBase2(n):
DONNEES
n : entier
VARIABLES
i : entier
DEBUT
i ← 1
TQ i ≤ n FAIRE
Afficher(ConvertirBase2(i))
i ← i + 1
FTQ
FIN
Complexité : \(\Theta(n \log_2(n))\)
3.2 Version avec file
Principe :
L’idée principale repose sur le fait qu’on peut obtenir toutes les écritures des nombres entiers entre \(2^k\) et \(2^{k+1}-1\) en prenant les écritures des entiers entre \(2^{k-1}\) et \(2^k-1\) et en ajoutant “0” puis “1” à la fin de l’écriture.
De plus, comme on enfile les écritures dans l’ordre croissant des valeurs et qu’on n’omet aucune écriture, notre file contient dans l’ordre les nombres de 1 à \(n\).
1
/ \
10 11
/ \ / \
100 101 110 111
À partir de “1”, on génère “10” et “11”. À partir de “10”, on génère “100” et “101”. À partir de “11”, on génère “110” et “111”.
Parcours en largeur : 1, 10, 11, 100, 101, 110, 111…
ALGORITHME EnumereFileBase2(n):
DONNEES
n : entier
VARIABLES
i : entier
C : chaîne de caractères
F : File vide
DEBUT
i ← 1
Enfiler(F, "1")
TQ i ≤ n FAIRE
C ← Defiler(F)
Afficher(C)
Enfiler(F, Concatener(C, "0"))
Enfiler(F, Concatener(C, "1"))
i ← i + 1
FTQ
FIN
Complexité : \(\Theta(n)\)
Bien plus efficace que la version naïve en \(\Theta(n \log n)\) !
| Étape | Défile | Affiche | Enfile | État de la file |
|---|---|---|---|---|
| Init | - | - | “1” | [1] |
| 1 | “1” | 1 | “10”, “11” | [10, 11] |
| 2 | “10” | 10 | “100”, “101” | [11, 100, 101] |
| 3 | “11” | 11 | “110”, “111” | [100, 101, 110, 111] |
| 4 | “100” | 100 | … | … |
| 5 | “101” | 101 | … | … |
| 6 | “110” | 110 | … | … |
| 7 | “111” | 111 | … | … |
Résultat : 1, 10, 11, 100, 101, 110, 111 (en binaire : 1, 2, 3, 4, 5, 6, 7)
4 Comparaison File vs Pile : Parcours de graphes
Utiliser une file revient à faire un parcours en largeur (BFS) d’un graphe, alors qu’une pile permet de faire le parcours en profondeur (DFS).
4.1 Parcours en largeur (BFS) avec file
On visite tous les nœuds niveau par niveau :
1 (1)
/ \
10 (2) 11 (3)
/ \ / \
100(4) 101(5) 110(6) 111(7)
Ordre de visite : 1, 10, 11, 100, 101, 110, 111
4.2 Parcours en profondeur (DFS) avec pile
On explore complètement une branche avant de passer à la suivante :
1 (1)
/ \
10 (5) 11 (2)
/ \ / \
100(7) 101(6) 110(4) 111(3)
Ordre de visite : 1, 11, 111, 110, 10, 101, 100
- BFS (File) : Plus court chemin, exploration niveau par niveau, génération ordonnée
- DFS (Pile) : Détection de cycles, exploration exhaustive, problèmes de backtracking
5 Comparaison Pile vs File
| Caractéristique | Pile (Stack) | File (Queue) |
|---|---|---|
| Principe | LIFO (Last In First Out) | FIFO (First In First Out) |
| Ajout | Empiler au sommet | Enfiler à la fin |
| Retrait | Dépiler du sommet | Défiler de la tête |
| Parcours graphe | Profondeur (DFS) | Largeur (BFS) |
| Applications | Récursion, undo/redo, expressions | Files d’attente, BFS, ordonnancement |
| Complexité ops | \(O(1)\) | \(O(1)\) (avec tableau circulaire) |