Planche 1
Planche 2
Plan
- Correction HC
- Files d'attente
- Files de priorité
- La structure d'arbre
- Optimalité du tri
- Compression -- code de Huffman
- Code de Huffman dynamique
- Ziv-Lempel
- Exercices
Planche 3
Correction HC
En général, c'est bien (beaucoup mieux qu'en 1992).
Pour manipuler des structures de données dynamiques, les programmes
récursifs sont souvent plus simples, et aussi plus efficaces.
Pour calculer une complexité, n'oubliez jamais de finir en
simplifiant, avec des formules simples comme O(n3).
Evitez les effets de bord. On parcourt une liste avec une variable,
plutôt que de la modifier pour considérer tjrs le 1er élément
Þ bugs.
Aller du particulier vers le général dans une série de
conditionnelles.
Programmez avec des for plutôt que des while. Le contrôle
(et la terminaison) sont alors plus faciles à comprendre.
Les programmes à trouver sont tjrs simples, pas plus que ~10
lignes. Si ce n'est pas le cas, c'est que vous êtes dans une impasse.
Planche 4
Files d'attente
Premier arrivé, premier servi (Fist In, First Out). Structure de
données très fréquente dans les programmes: par exemple dans les OS,
le hardware, le temps-réel, etc.
Une première méthode compacte consiste à gérer un tampon circulaire.
class FIFO {
final static int MaxF = 100;
int debut, fin;
boolean pleine, vide;
int contenu[];
FIFO () {
debut = 0; fin = 0; pleine = false; vide = true;
contenu = new int[MaxF];
}
Planche 5
Files d'attente avec tampon circulaire
static void ajouter (int x, FIFO f) {
if (f.pleine)
erreur ("File Pleine.");
f.contenu[f.fin] = x;
f.fin = (f.fin + 1) % MaxF;
f.vide = false; f.pleine = f.fin == f.debut;
}
static int supprimer (FIFO f) {
if (f.vide)
erreur ("File Vide.");
int res = contenu[f.debut]
f.debut = (f.debut + 1) % MaxF;
f.vide = f.fin == f.debut; f.pleine = false;
return res;
}
Belle symétrie de ces procédures. Complexité en O(1).
Planche 6
Files d'attente avec des listes
class FIFO {
Liste debut, fin;
FIFO (Liste a, Liste b) {
Liste garde = new Liste();
debut = fin = garde;
}
static void ajouter (int x, FIFO f) {
Liste a = new Liste (x, null);
f.fin.suivant = a;
f.fin = a;
}
Planche 7
Files d'attente avec des listes -- bis
static int supprimer (FIFO f) throws EmptyFifoException {
if (f.debut == f.fin)
throw new EmptyFifoException();
int res = f.debut.suivant.contenu;
f.debut = f.debut.suivant;
return res;
}
}
class EmptyFifoException extends Exception {}
Complexité en O(1),
Programmes un peu moins beaux,
Espace mémoire non borné.
Ici, on met une garde pour ne pas avoir à séparer le cas file vide.
Planche 8
La structure d'arbre
La structure d'arbre est fondamentale en informatique. Ils
interviennent chaque fois qu'une dichotomie a besoin d'être faite sur
un ensemble (arbres de recherche, arbres BSP), pour représenter les
termes du calcul symbolique ou de tout calcul algébrique. Dans cette
PC, nous considérons d'abord 3 exemples où cette structure n'est a
priori pas évidente.
Un arbre a une racine, des feuilles, des noeuds (éventuellement
internes), une hauteur, Une feuille n'a pas de fils, un noeud a des
fils. Un sous-arbre est un arbre dont la racine est un noeud.
Un arbre binaire est un arbre où tout noeud a au plus 2 fils.
Dans un arbre binaire
Card (Feuilles) = Card (Noeuds) + 1
Planche 9
Files de priorités
On rajoute un système de priorités. La file est organisée en un arbre
binaire quasi-parfait, dont tout noeud a une valeur supérieure à celle
de ses fils. Une astuce permet de représenter ces arbres par un
tableau, ou encore un ``tas'' (heap).
class FilePriorite {
final static int MaxF = 100;
int t[];
int nb;
FilePriorite () { t = new int[MaxF]; nb = 0; }
static void ajouter (FilePriorite f, int v) {
++f.nb; int[] t = f.t; int n = f.nb; int i = n - 1;
while( i > 0 && t[(i - 1)/2] <= v ) {
t[i] = t[(i - 1)/2];
i = (i - 1)/2;
}
t[i] = v;
}
static int premier (FilePriorite f) { return f.t[0]; }
Planche 10
Files de priorités
static int supprimer (FilePriorite f) {
int[] t = f.t;
int res = t[0];
t[0] = t[f.nb - 1]; -- f.nb;
int v = t[0];
for (int i = 0; 2*i + 1 < f.nb; ) {
int j = 2*i + 1;
if (j + 1 < f.nb && t[j + 1] > t[j]) ++j;
if (v >= t[j]) break;
t[i] = t[j]; i = j;
}
t[i] = v;
return res;
}
Rajouter les erreurs ``file pleine'' ou ``file vide'' en début de ces
2 procédures (cf files normales en tableau circulaire).
Complexité?
Planche 11
Files de priorités en ML
type 'a tas = {mutable nb: int; t: 'a array} ;;
let ajouter v f =
f.nb <- f.nb + 1;
let t = f.t in
let n = f.nb in
let i = ref (n - 1) in
if !i >= Array.length t then failwith "file pleine" else
while !i > 0 && t.((!i - 1) / 2) <= v do
t.(!i) <- t.((!i - 1) / 2);
i := (!i - 1) / 2
done;
t.(!i) <- v;;
let premier f = f.t(0);;
let new_tas n v = {nb = 0; t = Array.make n v} ;;
Planche 12
Files de priorités en ML -- bis
let supprimer f =
if f.nb = 0 then failwith "file vide" else
let t = f.t in
let res = t.(0) in
t.(0) <- t.(f.nb - 1); f.nb <- f.nb - 1;
let v = t.(0) and i = ref 0 in
begin try
while 2 * !i + 1 < f.nb do
let j = ref (2 * !i + 1) in
if !j + 1 < f.nb && t.(!j + 1) > t.(!j) then incr j;
if v >= t.(!j) then raise Exit;
t.(!i) <- t.(!j);
i := !j
done
with Exit -> () end;
t.(!i) <- v;
res;;
Planche 13
Optimalité du tri
Les arbres peuvent aussi représenter les arbres de décision: les
noeuds internes sont étiquetés par des prédicats, les feuilles sont
des actions à considérer.
Théorème
Le tri de N éléments, fondé uniquement sur les comparaisons des
éléments deux à deux, fait au moins O(N log N) comparaisons.
Démonstration: on considère l'arbre de décision dont chaque noeud
interne pose une question sur la comparaison entre 2 éléments. Le fils
de gauche correspond à la réponse négative, le fils droit à
l'affirmatif. Les feuilles représentent la permutation à effectuer
pour obtenir le tableau trié. Une exécution est donc un chemin de la
racine à une feuille. Or il y a N! feuilles. Donc la hauteur est
log N! » N log N.
Planche 14
Compression de fichiers
- Noter les répétitions et générer des escape-séquences avec le
compte
- Codes fixes ou de taille variable
- Méthode Code Huffman
- Méthodes adaptatives (Huffman dynamiques, Ziv Lempel)
Exemple: les bitmaps ont des grandes séquences de 0 et de 1.
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFACBABCBCCDCDDCCCDDDCDEDDCDDCDDCCCDDDCDDCDCCDDDCDDDDDCC\
CDCCCCBCCCCCDCCDDDDDDDDDDEEEDFFDEDFEFEFFDEFFEFFFFEEFFFFFFFFEFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBDCDDCCDDDDEDDDD\
DDDDDDEDDDEDEDEEDDDCDCDDCDDCDDDDDDDCDCDDDDDDDDCDCCCCCDDCDCDDDDD
DDDDDDEDDDDCDDDDDCDDCCBBCCCDCCCCCCDCCCCBBCACCBDCCCCEDDDDDEDCBCC\
BCCBBACBCBBCCCCCCBDDCCCCBBCBCCCBBCCDDDBDDACDBAABBADE9BDECADDCBCEF
DEEFDEFEEEFFEEEFEFEFEFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
(les 7 premières lignes de ma photo d'identité)
psfile=magueule.PS hoffset=160 voffset=-210
Les textes ont souvent beaucoup de répétitions. Par exemple, la chaîne
ADACADABRA (5 A, 2 D, 1 B, 1 C) qui vaut
41444143414441425241 en hexa
010000010100010001000001010000110100000101
00010001000001010000100101001001000001
en binaire
peut être compressée en
A=0, D=10, B=110, C=111
01001110010011011110
Planche 15
Codes de Huffman
- Code (préfixe) en taille variable. Chaque lettre est représentée
par une suite de 0 et de 1, indiquant son chemin à partir de la racine
dans l'arbre de Huffman.
- Deux passes. La première compte les occurrences de chaque
lettre. La seconde construit l'arbre de Huffman, et traduit le texte.
- A chaque étape de la construction de l'arbre, on cherche les
deux lettres les moins fréquentes parmi les 26 lettres de
l'alphabet. Soient n1 et n2 leurs nombres d'occurences. On met
ces deux lettres comme sous-arbres de gauche et de droite d'un nouveau
caractère fictif dont le nombre d'occurence est n1 + n2. Et on
recommence tant qu'il existe deux lettres différentes.
- Pour trouver rapidement les deux lettres les moins fréquentes,
on gère une liste de priorité du nombre d'occurence des lettres (les
26 normales et les 26 autres fictives rajoutées par l'algorithme).
Planche 16
Codes de Huffman -- bis
Il faut gérer un tas pour trouver les lettres de nombre d'occurences
minimal. On ne considère que les 128 valeurs des codes Ascii (pour
simplifier), et les éventuels 127 noeuds intermédiaires possibles dans
l'arbre de Huffman.
final static boolean GAUCHE = false, DROIT = true;
int[] occ = new int[256] // nombre d'occurences
int[] pere = new int[256]; // indice du père
boolean[] sens = new int[256]; // fils gauche/droit
int[] code = new int[256]; // code de Huffman
int[] lcode = new int[256]; // longueur du code de Huffman
Pour générer les codes de Huffman, on utilise les opérations sur les
bits (décalage <<, union |). Pour imprimer le résultat,
on doit utiliser un registre à décalage.
Pour simplifier, on utilisera le fait que les 128 valeurs Ascii sont
les 128 premières valeurs Unicode. (Il faut faire de l'UTF-8 pour être
plus propre ou faire de la recherche en table sur les valeurs Unicode).
Planche 17
Codes de Huffman dynamiques -- compact
- L'encodeur et décodeur construisent dynamiquement les codes de
Huffman en une seule passe. Ils suivent la même stratégie de
construction de l'arbre de Huffman.
- Méthode FGK [Faller and Gallager 73; Knuth 85] Les noeuds de
l'arbre de Huffman sont reliés par une liste triée en ordre croissant
(au sens large) sur la valeur des noeuds, et telle que tout noeud
(sauf la racine) est relié à son frère.
- Une feuille 0 représente tous les caractères non rencontrés.
Lorsqu'un nouveau caractère est rencontré, on l'envoit et on remplace
ce noeud par un nouveau noeud interne 1 dont les fils sont les
feuilles 0 et 1 correspondant au nombre d'occurences du nouveau
caractère. On va au cas suivant.
- Quand un caractère déja rencontré est à nouveau dans le fichier
à coder. On échange sa position dans l'arbre de Huffman avec le plus
loin dans la liste de même valeur. On augmente sa valeur de 1. Et on
itère l'incrémentation sur son père.
Planche 18
Codes de Huffman dynamiques
Planche 19
Méthode Ziv Lempel -- compress, gzip
Planche 20
Méthode Ziv Lempel -- bis
- Au début on met n-Ls zéros devant la chaîne à coder.
- On cherche dans la partie droite la plus longue chaîne déjà
rencontrée dans la fenêtre. On note son adresse a, sa longueur l
et le caractère qui crée la différence c.
- On recentre la fenêtre sur le caractère c et on recommence à
chercher une autre chaîne.
Le principe est donc de factoriser des chaînes de taille variable,
(avec éventuel recouvrement!). Cela marche très bien sur le texte.
Pour les bitmap graphiques, il y a d'autres procédés de compression
(non exacts) .jpg (ou .mpg) pour les images (animées). Champ très
actif de recherche avec le développement de l'internet et le
basculement de la vidéo sur l'internet.
La théorie algébrique des codes sont enseignés à la majeure Math et Info.
(Demazure)
Planche 21
Exercices en TD
- Finir la recherche en table.
- Coder du texte avec des facteurs de répétitions.
- Faire des codes de Huffman statiques.
- Pour lire un fichier, utiliser la redirection de l'entrée
standard, et on lit des lignes sur l'entrée standard. Pour rediriger
l'entrée, on fait sous unix:
% java Huffman < fichier_de_données
De même pour créer un fichier, on peut rediriger la sortie standard et
faire
% java Huffman > fichier_résultat
This document was translated from LATEX by HEVEA.