201 lines
6.4 KiB
Java
201 lines
6.4 KiB
Java
package fr.iutfbleau.sae.mhuffman;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
|
|
/**
|
|
* Implémente un arbre de Huffman utilisé pour la compression de données.
|
|
* <p>
|
|
* La classe {@code HuffmanTree} est chargée de représenter la structure
|
|
* de l'arbre de Huffman et de générer les codes binaires associés aux symboles.
|
|
* Elle s'appuie sur la classe {@link HuffmanNode} pour représenter les nœuds
|
|
* de l'arbre.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* L'arbre est construit à partir des fréquences des symboles calculées
|
|
* en amont (par exemple à l'aide d'une {@code FrequencyTable}).
|
|
* Chaque symbole est d'abord représenté par une feuille, puis les nœuds
|
|
* sont combinés progressivement selon l'algorithme de Huffman afin
|
|
* d'obtenir un arbre binaire optimal.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Une fois l'arbre construit, celui-ci est parcouru afin de générer une
|
|
* table de correspondance associant à chaque symbole un code binaire unique.
|
|
* Les symboles les plus fréquents se retrouvent plus proches de la racine
|
|
* et possèdent donc des codes plus courts, ce qui permet de réduire
|
|
* la taille des données compressées.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Cette classe ne s'occupe pas de la lecture ou de l'écriture des bits.
|
|
* Elle fournit uniquement la structure et les informations nécessaires
|
|
* à la compression, qui sont ensuite exploitées par des flux binaires
|
|
* dédiés.
|
|
* </p>
|
|
*
|
|
* @author Algassimou Pellel Diallo,Ayoub Anhdire
|
|
* @version 1.0
|
|
* @since 2025-12-13
|
|
*/
|
|
public class HuffmanTree {
|
|
|
|
/**
|
|
* Racine de l'arbre de Huffman.
|
|
* <p>
|
|
* Ce nœud est le résultat final de la construction de l'arbre et constitue
|
|
* le point de départ pour la génération des codes binaires ainsi que
|
|
* pour le décodage des données compressées.
|
|
* </p>
|
|
*/
|
|
private HuffmanNode root;
|
|
|
|
/**
|
|
* Dictionnaire pour enregistrer les codes Huffman
|
|
*/
|
|
private Map<Integer,String> codes;
|
|
|
|
/**
|
|
* Chaine de caracteres qui va nous permettre de sauvegader le code Huffman
|
|
* Permet en d'autres termes de construire la chaine de 1 et de 0
|
|
*/
|
|
|
|
private String chaineCarac;
|
|
|
|
/**
|
|
* Construit un arbre de Huffman.
|
|
* <p>
|
|
* Le constructeur est responsable de l'initialisation de la structure
|
|
* de l'arbre. En pratique, il combine les nœuds feuilles représentant
|
|
* les symboles par ordre croissant de fréquence jusqu'à obtenir un
|
|
* unique nœud racine.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Les détails de la construction (structure de données utilisée,
|
|
* ordre des fusions, etc.) sont volontairement séparés de la logique
|
|
* de génération des codes.
|
|
* </p>
|
|
*/
|
|
public HuffmanTree(int[] freq) {
|
|
// J'initialise la racine à null.
|
|
this.root = null;
|
|
|
|
// je cree une collection de feuilles
|
|
|
|
/////////////////////////////////// Voir si ya moyen doptimiser //////////////////////////////////////
|
|
|
|
// tu pourrait utiliser une PriorityQueue pour placer selon les fréquences comme dit dans l'énoncé !!!!
|
|
|
|
List<HuffmanNode> feuilles = new ArrayList<>();
|
|
|
|
// pour chaque valeur(symbole) dans la table de frequence
|
|
for (int i = 0; i < freq.length; i++) {
|
|
// si la frequence est superieure a 0 , on cree une feuille
|
|
if (freq[i] > 0) {
|
|
// pour la valeur (symbole) i avec frequence freq[i], on cree une feuille
|
|
HuffmanNode feuille = new HuffmanNode(i, freq[i]);
|
|
// on ajoute la feuille à la collection
|
|
feuilles.add(feuille);
|
|
}
|
|
}
|
|
|
|
// On tri les feuilles par frequence croissante j'utilise un comparator qui compare la valeur retournee par getFrequence de chaque feuille
|
|
// Referencement de methode avec ::
|
|
feuilles.sort(Comparator.comparingInt(HuffmanNode::getFrequence));
|
|
// flemme de faire un algo de tri alors que java le fait tres bien a voir a la fin si je vais coder une liste chainee avec un tri par insertion personnalise
|
|
|
|
|
|
// Fusion des nœuds jusqu'à obtenir la racine
|
|
// Tant qu'il y a plus d'une feuille dans la collection
|
|
while (feuilles.size() > 1) {
|
|
// je prends les deux feuilles de plus faible fréquence
|
|
HuffmanNode left = feuilles.remove(0);
|
|
HuffmanNode right = feuilles.remove(0);
|
|
|
|
// je crée un nœud interne en les combinant
|
|
HuffmanNode parent = new HuffmanNode(left, right);
|
|
|
|
// j'insère le nœud parent dans la collection à la bonne position pour maintenir l'ordre (plus performant qu'un tri complet à chaque itération)
|
|
int index = 0;
|
|
// tant que l'index est dans les limites et que la frequence du noeud à l'index est inférieure à celle du parent
|
|
while (index < feuilles.size() && feuilles.get(index).getFrequence() < parent.getFrequence()) {
|
|
index++;
|
|
}
|
|
feuilles.add(index, parent);
|
|
}
|
|
|
|
// a la fin il ne reste qu'un seul noeud : la racine de l'arbre
|
|
this.root = feuilles.get(0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Retourne la racine de l'arbre de Huffman.
|
|
* <p>
|
|
* Cette méthode permet d'accéder à la structure complète de l'arbre,
|
|
* notamment lors de la génération des codes ou du décodage des données.
|
|
* </p>
|
|
*
|
|
* @return le nœud racine de l'arbre de Huffman
|
|
*/
|
|
public HuffmanNode getRoot() {
|
|
return root;
|
|
}
|
|
|
|
/**
|
|
* @return Map on stockera les codes Huffman sous forme de dictionnaire
|
|
*/
|
|
|
|
public Map<Integer,String> generateCodes(){
|
|
/**
|
|
* Le but de cette méthode est de pouvoir generer les codes Huffman à partir de l'arbre :
|
|
* Les branches prendront comme valeur 1 ou 0 selon differents cas :
|
|
* 1 - si on saute vers un fils droit
|
|
* 0 - si on saute vers un fils gauche.
|
|
* On construit les codes qui partent de la racine jusqu'à notre objectif
|
|
*/
|
|
|
|
this.codes = new HashMap<>();
|
|
this.chaineCarac = new String();
|
|
|
|
if(root.isLeaf()){
|
|
codes.put(root.getValue(),chaineCarac);
|
|
return codes;
|
|
}
|
|
|
|
HuffmanNode temp = root;
|
|
|
|
if (root.getLeft() != null) {
|
|
root = root.getLeft();
|
|
chaineCarac = chaineCarac + "0";
|
|
generateCodes();
|
|
// on retire le dernier bit lorsqu'on remonte car sinon les codes seront faussés
|
|
chaineCarac = chaineCarac.substring(0, chaineCarac.length() - 1);
|
|
}
|
|
|
|
if (temp.getRight() != null) {
|
|
root = temp.getRight();
|
|
chaineCarac = chaineCarac + "1";
|
|
generateCodes();
|
|
chaineCarac = chaineCarac.substring(0, chaineCarac.length() - 1);
|
|
}
|
|
|
|
|
|
root = temp;
|
|
return codes;
|
|
|
|
}
|
|
|
|
|
|
public static Map<Integer,String> getDictionnary(){
|
|
return this.codes;
|
|
}
|
|
|
|
|
|
}
|