# LZ77 et LZ78

LZ77 et LZ78 sont deux algorithmes de compression de données sans perte proposés par Abraham Lempel et Jacob Ziv en 1977 et 1978 (d'où leurs noms). Ces deux algorithmes posent les bases de la plupart des algorithmes de compression par dictionnaire, à tel point qu'on parle couramment d'algorithme LZ pour désigner un algorithme de compression par dictionnaire. Ces algorithmes reposent de manière essentielle sur une mesure de complexité pour les suites de longueur finie, la complexité de Lempel-Ziv.

## LZ77
### Principe

LZ77 est un algorithme de compression par dictionnaire utilisant une fenêtre glissante ; les motifs déjà rencontrés dans la fenêtre sont remplacés par une référence à leur première apparition (typiquement, par une paire (distance, longueur)).
Par extension, LZ77 est aussi utilisé pour désigner la famille des algorithmes de compression par dictionnaire utilisant une fenêtre glissante, comme LZSS et LZMA.

### Description de l'algorithme LZ77
#### Compression

Etant donnée une séquence S à compresser, on l'écrit d'abord sous forme de plusieurs composantes S$_i$ juxtaposées. Pour ce faire, on choisit un entier L$_S$ qui sera la longueur maximale d'un S$_i$ et un entier n > L$_S$ qui sera la taille du buffer. On initialise les n-Ls caractères du buffer par des zéros et on complète le buffer par les L$_S$ premiers caractères de S.
L'entier n-Ls est la taille de la fenêtre glissante (initialisée avec des zéros comme indiqué précédemment). Pour trouver les S$_i$, on réalise à chaque itération une production exhaustive de la sous-séquence de S la plus longue possible en prenant le contenu de la fenêtre glissante comme préfixe, avec ici la contrainte que la longueur de la séquence produite ne peut dépasser L$_S$.
Après initialisation de la fenêtre à zéro et en appliquant ce processus de production on obtient donc la séquence S$_1$ de longueur L$_1$ < Ls puis on fait glisser la fenêtre en enlevant les L$_1$ premiers caractères de la fenêtre glissante du buffer et en faisant entrer dans le buffer les L$_1$ caractères suivants de S. On répète ce procédé (production puis glissement de la fenêtre) pour trouver les autres S$_i$ jusqu'à arriver au bout de la séquence S.
Cette décomposition découpe S sous forme de composantes comme dans le processus de calcul de la complexité de Lempel-Ziv.
À chaque étape i, après avoir trouvé S$_i$ par un processus de production, on lui affecte un mot de code qui sera sa représentation dans le fichier compressé. Ce mot de code n'est rien d'autre que le triplet (position, longueur, caractère) correspondant.
Chaque S$_i$ est ainsi codé par un triplet C$_{i,1}$C$_{i,2}$C$_{i,3}$ et sera représenté ainsi dans le fichier compressé. Le mode de représentation est donc le suivant :
* C$_{i,1}$ est la représentation en base α (α étant la taille de l'alphabet des caractères de S) de p$_i$-1, avec p$_i$ l'indice dans la fenêtre glissante où commence la copie qui correspond à S$_i$(p$_i$ est le pointeur pour la production de S$_i$).
* C$_{i,2}$ est la représentation en base α de L$_i$ - 1 (la longueur de la production à cette étape)
* C$_{i,3}$ est le dernier caractère de S$_i$
Cet algorithme est étroitement lié au calcul de complexité de Lempel-Ziv. Après avoir fait précédé S de n-L$_S$ zéros, on ne fait en effet que décomposer S en composantes S$_i$ (qu'on remplace par leur mot de code) en prenant, à chaque étape, le contenu de la fenêtre glissante comme préfixe et en ayant une longueur maximale L$_S$ fixée pour toutes les composantes.
Vu que p$_i$ est un indice appartenant au préfixe (ici, la fenêtre glissante), il est compris entre 1 et n-L$_S$ et peut être représenté en base α par $\lceil \log _{\alpha }(n-L_{S})\rceil$ caractères. De même, les L$_i$ étant bornés par L$_S$, on a besoin de $\lceil \log _{\alpha }(L_{S})\rceil$ caractères pour représenter L$_i$-1. C$_{i,3}$ étant un caractère unique, chaque S$_i$ sera représenté dans le fichier compressé par $L_{C}=\lceil \log _{\alpha }(n-L_{S})\rceil +\lceil \log _{\alpha }(L_{S})\rceil +1$ caractères.

#### Décompression

La décompression est très semblable à la compression.
Initialisation :
* on utilise une fenêtre de taille n-L$_S$ qui est initialisée avec des zéros,
* on récupère p$_i$ - 1 (et donc p$_i$) sur les $\lceil \log _{\alpha }(n-L_{S})\rceil$ premiers caractères de C$_i$ (C$_{i,1}$),
* les $\lceil \log _{\alpha }(L_{S})\rceil$ caractères suivants (C$_{i,2}$) donnent la valeur de L$_i$ - 1.
Après l'initialisation, à chaque étape i on répète L$_i$ -1 fois le processus suivant :
* on recopie le caractère qui est à l'indice p$_i$ à la fin de la fenêtre,
* puis on fait glisser la fenêtre vers la droite pour faire sortir son premier caractère et faire entrer le caractère qui vient d'être recopié dans la fenêtre.
Vu que les L$_i$ - 1 premiers caractères de S$_i$ sont juste une copie de la partie de la fenêtre commençant à l'indice p$_i$, on récupère par le processus ci-dessus les L$_i$-1 premiers caractères de S$_i$. À ce stade, il ne nous manque que le dernier caractère de S$_i$ qui n'est rien d'autre que le dernier caractère (C$_{i,3}$) de C$_i$.
Ceci amène à la dernière phase de l'étape i qui consiste à :
* recopier le dernier caractère de C$_i$ (qui est le dernier caractère de S$_i$) à la fin de la fenêtre avant d'effectuer un dernier décalage à droite, d'un caractère, de la fenêtre.
On obtient ainsi S$_i$ en récupérant les L$_i$ derniers caractères de la fenêtre. Après avoir appliqué ce processus à tous les mots de code C$_i$, la séquence S est obtenue en concaténant les S$_i$.

### Exemple

On applique l'algorithme de compression à la séquence ternaire
S = 001010210210212021021200
dont l'alphabet A={0,1,2} compte α=3 caractères.
On prend n=18 et L$_S$=9.
On commence par calculer L$_C$ qui est égale à 5 (n-L$_S$=9 et $\lceil \log _{3}9\rceil =2$).
Compression:
Étape 0. Initialisation du buffer :
On a, dans le buffer de taille n=18, comme expliqué précédemment n-L$_S$=9 zéros suivis des L$_S$=9 premiers caractères de S (cf. Image1).
Étape 1.
Dans cet exemple, n'importe quel indice de la fenêtre glissante pourrait être pris comme p$_1$ (la plus longue correspondance est 00 quel que soit le pointeur initial), on choisit p$_1$ =9 (cf. Image2_lz77)
On obtient donc S$_1$=001; L$_1$=3, ce qui donne :
* C$_{1,1}$=22 (représentation en base 3 de p$_1$-1 avec $\lceil \log$ $_3$ (18 - 9)$\rceil$= 2 caractères)
* C$_{1,2}$=02 (représentation en base 3 de L$_1$ - 1 avec $\lceil \log$ $_3$ (9)$\rceil$= 2 caractères)
* C$_{1,3}$=1 (le dernier caractère de S$_1$)
On fait ensuite glisser la fenêtre pour en faire sortir les L$_1$=3 premiers caractères et faire entrer les 3 prochains caractères de S dans le buffer (cf. Image3_lz77).
Étape 2.
On passe alors au calcul de C$_2$ (code correspondant à S$_2$) : On prend p$_2$=8 (qui permet d'avoir la plus longue correspondance) avec L$_2$= 4 et S2=0102. En procédant de la même manière que pour S$_1$, on obtient C$_2$=21102 (cf. Image4_lz77 ).
On continue ainsi jusqu'à la fin de la séquence pour trouver C$_3$=20212 et C$_4$=02220.
Après compression, on obtient pour S la séquence compressée :
22021 21102 20212 02220
Décompression:
On procède à la décompression de S comme suit :
* on décompresse dans l'ordre les C$_i$ pour obtenir les S$_i$
* on concatène ces S$_i$ pour obtenir S.
Pour C$_1$=22021, on a :
* p$_1$=9 (C$_{1,1}$=22, les 2 premiers caractères de C$_1$, représente 8 en base 3, d'où p$_1$=8+1=9),
* L$_1$=3 (C$_{1,2}$=02, les 2 caractères suivants de C$_1$, représente 2 en base 3, d'où L$_1$=2+1=3),
* le dernier caractère C$_{1,3}$=1 de C$_1$ est le dernier caractère du mot S$_1$.
On applique le processus de décompression présenté ci-dessus à C$_1$ (cf. décompression_lz77). On retrouve ainsi S$_1$=001.
On procède de même, dans l'ordre, pour les C$_i$ qui restent.

### Limites et utilisations

LZ77 présente certains défauts, en particulier, si aucune chaîne n'est trouvée dans le dictionnaire, le symbole à compresser est alors codé par "position=0", "longueur=0", "nouveau symbole", c'est-à-dire qu'il occupe 3 octets au lieu d'un seul dans le fichier original. Ce défaut est supprimé dans la variante LZSS.
L'algorithme LZ77 est notamment utilisé dans l'algorithme deflate (avant un codage de Huffman) et pour la compression des fichiers dans le système de fichier Windows NTFS.

## LZ78

LZ78 est un algorithme de compression par dictionnaire (publié dans (en) « Compression of Individual Sequences via Variable-Rate Coding », IEEE Transactions on Information Theory,‎ septembre 1978) utilisant un dictionnaire global (et non plus une fenêtre), construit de la même façon par le compresseur et le décompresseur, respectivement à la compression et à la décompression. Cela permet d'éviter le principal problème de son prédécesseur (LZ77) qui ne peut retrouver une chaîne de caractères si elle se trouve en dehors de la fenêtre.
Par extension, LZ78 est aussi utilisé pour désigner la famille des algorithmes de compression par dictionnaire utilisant un dictionnaire implicite, comme Lempel-Ziv-Welch.
LZ78 a eu moins de succès que LZ77, pour des raisons d'efficacité, et parce qu'il a été protégé par un brevet logiciel aux États-Unis.