Planche 1

PC7
Jean-Jacques.Levy@inria.fr
http://www.polytechnique.fr/poly/~levy/
INRIA -- Rocquencourt
tel: 01 39 63 56 89


secrétariat: Catherine Bensoussan
cb@lix.polytechnique.fr
Aile 00,
LIX
tel: 34 67


http://w3.edu.polytechnique.fr/informatique/


Planche 2

Plan

  1. Correction HC
  2. Files d'attente
  3. Files de priorité
  4. La structure d'arbre
  5. Optimalité du tri
  6. Compression -- code de Huffman
  7. Code de Huffman dynamique
  8. Ziv-Lempel
  9. 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

  1. Noter les répétitions et générer des escape-séquences avec le compte

  2. Codes fixes ou de taille variable
  3. Méthode Code Huffman
  4. 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


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


Planche 18

Codes de Huffman dynamiques


Planche 19

Méthode Ziv Lempel -- compress, gzip


Planche 20

Méthode Ziv Lempel -- bis

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


This document was translated from LATEX by HEVEA.