Travaux Pratiques. Compression en Codage de Huffman. 1.3. Organisation D Un Projet de Programmation
Travaux Pratiques. Compression en Codage de Huffman. 1.3. Organisation D Un Projet de Programmation
Travaux Pratiques. Compression en Codage de Huffman. 1.3. Organisation D Un Projet de Programmation
Module ETRS711
Travaux pratiques
Compression en codage de Huffman
1. Organisation du projet
1.1. Objectifs
Le but de ce projet est d'écrire un programme permettant de compresser des fichiers textes,
sans perte d'information, afin qu'ils occupent moins d'espace sur un disque. On demande
également d'écrire un décompresseur qui devra restaurer le fichier original. Les algorithmes
de compression sont nombreux. Celui proposé ici est l'algorithme de Huffman. Nous nous
proposons de le coder en langage C.
Il est préférable que vous respectiez les propositions de prototypage des fonctions faites dans
l’énoncé afin que le debugage soit plus aisé.
On fait exactement la même chose en reprenant en compte le nouveau nœud (et non plus les
caractères espace et U). Dans l’exemple précédent l’occurrence la plus faible est celle de b et
celle du nouveau nœud (on aurait pu prendre aussi ‘a’ ou ‘e’ puisque leur valeur est 2 aussi,
cela revient au même à la fin).
Figure 3 : Création d’un autre nouveau nœud
IMPORTANT : Dans toute la suite du TP, on appellera « une feuille » de l’arbre les éléments
qui sont en bout de l’arbre (les caractères). On appellera un nœud les éléments regroupant
deux feuilles (ou deux nœuds).
Avec d'autres choix, on peut obtenir des arbres différents, mais l'algorithme fonctionne avec
tout choix respectant la construction ci-dessus. À partir de l'arbre, on construit le code d'un
caractère en lisant le chemin qui va de la racine au caractère. Un pas à gauche étant lu comme
0 et un pas à droite comme 1.
On peut voir que ce codage vérifie toujours les propriétés a) à d), et permet donc la
compression et la décompression. Avec cet algorithme, le décompresseur doit connaître les
codes construits par le compresseur.
Lors de la compression, le compresseur écrira donc ces codes en début de fichier compressé,
sous un format à définir, connu du compresseur et du décompresseur. Le fichier compressé
aura donc deux parties disjointes :
· Une première partie permettant au décompresseur de retrouver le code de chaque
caractère;
· Une seconde partie contenant la suite des codes des caractères du fichier à compresser.
Attention, le décompresseur doit toujours pouvoir trouver la séparation entre ces deux parties.
Aussi, vous pouvez toujours vérifier le code générer par votre algorithme en vérifiant que le
code générer pour coder un caractère ne doit pas ressembler au début d’un code d’un autre
caractère.
3. Travail demandé
3.1. Première partie : Occurrences des caractères
L’objectif est de travailler avec des fichiers (qui par la suite seront les fichiers à compresser).
Il faut donc que nous maitrisions les fonctions de manipulation de fichier.
Q1. Réaliser un programme qui ouvre un fichier texte et qui affiche une partie du contenu
à l’écran. (Utiliser la fonction fread()).
Q2. Réaliser un programme qui ouvre un fichier texte et qui affiche la totalité du contenu
du fichier à l’écran. Vous ferez une lecture grâce à la fonction fgetc( ) jusqu'à ce que vous
rencontriez le caractères de fin de fichier (EOF=End Of File).
Nous nous intéressons au comptage des occurrences des caractères présents dans le fichier
texte. Pour cela nous utiliserons un tableau de caractère appelé tab_caractere[256]. Ce tableau
comporte 256 éléments. Dans chaque case de ce tableau, nous rentrerons le nombre
d’occurrence du caractère dont le code ASCII est donné par l’index de tab_caractère.
Exemple :
Le code ASCII du ‘a’ est 97.
tab_caractere[97] comportera donc le nombre d’occurrence du caractère ‘a’.
Q3. Coder une fonction dont le prototype est [ void occurence(FILE* file, int tab[256]) ].
Cette fonction comptera les occurrences des caractères du fichier ‘file’, et stockera les
occurrences dans le tableau ‘tab’ passé en paramètre. Afficher à l’écran une partie du
tableau pour vérifier le fonctionnement.
La seule différence entre un nœud et une feuille, est que pour une feuille, les pointeurs vers
les nœuds suivant sont NULL. C’est de cette façon que nous les distinguerons.
Q4. Réaliser une boucle dans votre programme afin de créer dynamiquement une structure
‘nœud’ pour chacun des caractères contenu dans le fichier. Vous testerez votre programme en
affichant le contenu du champ ‘c’ caractère et ‘occurrence’ de chacune des structures créées.
Chacune de ces structures seront réservées en mémoire par la fonction malloc(). De plus, afin
de conserver l’ensemble des structures créées, nous sauvegarderons les pointeurs sur ces
structures nœud (struct nœud*) dans le tableau suivant : [struct noeud*
arbre_huffman[256] ]
Explications complémentaires :
On doit donc créer pour chacun des caractères de notre tableau tab_caractere[256] une
structure nœud comme suit :
struct nœud struct nœud struct nœud struct nœud struct nœud struct nœud
c=’ ‘ c=’U‘ c=’b‘ c=’n‘ c=’a‘ c=’e‘
Occurrence=1 Occurrence=1 Occurrence=1 Occurrence=3 Occurrence=2 Occurrence=2
gauche=NULL gauche=NULL gauche=NULL gauche=NULL gauche=NULL gauche=NULL
droite=NULL droite=NULL droite=NULL droite=NULL droite=NULL droite=NULL
bits=0 bits=0 bits=0 bits=0 bits=0 bits=0
code=0 code=0 code=0 code=0 code=0 code=0
Afin de conserver les références sur chacune des structures créées, il faut que nous
conservions l’adresse de ces structures. Nous allons donc les conserver dans un tableau
appelé :
[ struct nœud* arbre_huffman[256] ]
Q5. Réalisez exactement la même chose mais cette fois ci en faisant appel à une fonction
dont le prototype est le suivant : [ struct noeud* creer_feuille(int* tab, int index )]
Nous allons maintenant faire apparaître des nouveaux nœuds dans l’arbre comme préciser
dans la Figure 2.
Une fois que cette fonction est validée, vous pouvez parcourir votre tableau de Huffman
[struct noeud* arbre_huffman[256] ] afin de rechercher les nœuds comportant la plus petite
occurrence. Dès lors que vous avez trouvé ces deux nœuds, vous aller créer un nouveau nœud
dont la valeur d’occurrence vaut la somme des deux nœuds d’origine comme préciser dans la
Figure 2.
Q7. Réaliser la fonction [ void creer_noeud(struct noeud* tab[], int taille) ] qui réalisera
la recherche des plus petites occurrences, fera la création du nouveau nœud et le
sauvegardera dans le même tableau de Huffman.
Note : il y a deux emplacements possibles pour le nouveau nœud. « ! » est le nouveau nœud.
Figure 7 : Création du tableau de Huffman avec un nouveau nœud
A la fin de la création de l’arbre, vous devez obtenir une seule référence (pointeur sur
structure) qui est celle du haut de l’arbre binaire, et qui possède dans les champs « droite » et
« gauche » les références des deux nœuds suivant (qui eux même possèdent deux références
des deux nœuds suivants, etc, etc…)
La méthode pour le parcours de l’arbre de la racine jusqu’au feuille est donné par le code C
suivant. Il s’agit d’une fonction récursive, c'est-à-dire qu’elle s’appelle elle-même :
· niveau : nombre de bit du code de chaque caractère
· element : un nœud
· code : code du caractère
Q9. Dans la fonction créer code, lorsque vous êtes dans une feuille et que vous avez trouvé
le code correspondant, faites appel à une fonction [ void affichage_code(int nbr_bits, int
codage) ] qui permettra d’afficher le code du caractère sous forme binaire.
La compression va maintenant pouvoir avoir lieu. Pour cela, nous allons modifié un peu la
fonction créer_code() afin de sauvegarder toutes les liens (pointeur) vers les structures
« noeud » des caractères existants dans le fichier. Ceci sera très similaire au tableau
tab_caractere du début (int tab_caractere[256]).
Nous conservons donc le pointeur sur la structure de chacun des caractères du fichier dans le
tableau :
Struct nœud * alphabet[256]
Le positionnement dans ce tableau sera fait de la même façon que pour tab_caractere, c'est-à-
dire :
Le code ASCII du ‘a’ est 97.
alphabet[97] comportera donc le pointeur vers la structure nœud du caractère « a ».
Q10. Réaliser une entête dans votre fichier et faite en la relecture immédiate. Grâce à cette
entête le logiciel de décompression pourra prendre connaissance des caractères présent dans
le fichier, et pour chacun d’entre eux connaître son code.
Note : Entre la création de l’entête, et sa relecture, vous pouvez effacer le tableau struct nœud
* alphabet[256]. En effet, l’objectif même de la relecture est justement de recréer ce tableau
sans en avoir la connaissance au préalable.
Q11. Ecrire l’ensemble des codes correspondant à chaque caractère du fichier d’origine les
uns à la suite des autres.
Q13. Parcourir votre arbre pour chacun des bits lus et afficher la valeur du caractère
décompressé.
BON COURAGE