Sunteți pe pagina 1din 14

PRCTICAS

TXN. DATOS
MULTIMEDIA

INTRODUCCIN A LA COMPRESIN:
HUFFMAN ENTROPA

1. Introduccin
El objetivo de esta prctica es doble, por un lado ser capaz de entender el concepto de
entropa, y por otro conocer los fundamentos del algoritmo de codificacin de datos
Huffman. Para lograr estos objetivos, en primer lugar realizaremos una implementacin
del algoritmo de Huffman y del clculo de la entropa, y en segundo lugar
codificaremos diversos conjuntos de datos, observando los resultados para ver la
relacin existente entre el ndice de compresin obtenido por Huffman y la estimacin
ptima ofrecida por la entropa.
Para el desarrollo de la prctica, se proporciona un cdigo fuente incompleto, que
contiene una implementacin de Huffman que se deber analizar y completar. Esta
implementacin se ha escrito en lenguaje ANSI C++, y la interaccin con el usuario se
realiza por medio de paso de comandos en una ventana de sistema, por lo que su
compilacin se puede realizar en cualquier entorno de desarrollo C++, tanto Windows
como Linux.

2. Codificacin Huffman
En compresin, buscamos ser capaces de representar un conjunto de smbolos (o cadena
de smbolos) obtenidos a partir de un cierto alfabeto, usando el menor nmero de bits
posible, pero preservando en todo momento la capacidad de descomprimir o decodificar
la informacin. En general, el sistema que realiza el proceso directo lo llamamos
compresor o codificador, mientras que el que reconstruye los datos originales (o una
aproximacin a ellos si realizamos compresin con prdidas) lo llamamos descompresor
o decodificador.
El algoritmo de codificacin/compresin Huffman se propuso en 1952 como una forma
sencilla y ptima de mapear cada smbolo de un alfabeto con un cdigo (codeword) de
longitud ptima. De esta forma, para comprimir cada smbolo de la cadena,
simplemente debemos usar el cdigo que se ha calculado mediante Huffman. Para
conseguir esta asignacin ptima, los smbolos se representan con cdigos cuya
longitud es inversamente proporcional a la probabilidad del smbolo. De esta forma, los
smbolos menos probables se representan con cdigos ms largos, y los ms probables
con cdigos ms cortos.
El proceso de asignacin de cdigos se lleva a cabo mediante la construccin de un
rbol binario, desde las hojas hacia la raz, de manera que los nodos hoja son los
smbolos del alfabeto. En la construccin del rbol, los nodos menos probables se unen
sucesivamente para formar otro nodo de mayor probabilidad, de forma que cada uno de
los enlaces aade un bit al cdigo de los smbolos que estamos juntando. Este proceso
termina cuando slo se dispone de un nodo, de forma que ste representa la raz del
rbol.

Para mejorar su comprensin, veamos un ejemplo sobre este proceso. Supongamos que
queremos codificar la siguiente cadena de smbolos:
S={aabaacc}
que usa el alfabeto
A={a, b, c}
La probabilidad de cada uno de los smbolos vendr dada por las siguientes expresiones:
P(a)=4/7

P(b)=1/7

P(c)=2/7

Como es lgico, el smbolo a, que se repite mucho, nos interesa representarlo con el
menor nmero de bits posibles.
El primer paso del algoritmo ser plantear un grafo no conexo de nodos que representan
cada uno de los smbolos del alfabeto, junto con su probabilidad asociada. Para mejorar
su comprensin y facilitar el clculo, en lugar de usar directamente las probabilidades
P(a), P(b), P(c), usaremos una cuenta de repeticiones, a modo de histograma, de forma
que
H(a)=4
H(b)=1
H(c)=2
El grafo inicial ser el siguiente:

H(a)=4

H(b)=1

H(c)=2

El primer paso ser juntar los nodos menos probables en un nuevo nodo, asignando un
bit distinto a cada uno de los enlaces. El grafo resultante es el siguiente:
bc
0
a

H(bc)=3

H(a)=4

A continuacin nos quedan dos nodos por unir, repetimos la misma operacin y
obtenemos ya el rbol final.
abc H(abc)=7
1
0

bc
0

1
c

Para obtener los cdigos a usar en la codificacin, simplemente debemos recorrer el


rbol de la raz a cada una de las hojas, asignando a cada smbolo el cdigo resultante de

unir las etiquetas asociadas a cada uno de los enlaces que se han recorrido. De esta
forma, los cdigos finales en el ejemplo son los siguientes:
C(a)=0

C(b)=10

C(c)=11

Y por tanto la cadena original S={aabaacc} quedara codificada como sigue


C(aabaacc)= 0 0 10 0 0 11 11
El resultado final de la compresin es que hemos empleado 10 bits para codificar los 7
smbolos originales, as que se han usado 10/7 = 1,43 bits por smbolo.
Un inconveniente del proceso de decodificacin Huffman es que es necesario disponer
del rbol a partir del que se codifican los datos. Por lo tanto, no es suficiente con
almacenar la cadena final, sino que tambin hay que comunicar al decodificador las
probabilidades de la fuente (o el histograma asociado a los smbolos), de forma que el
decodificador sea capaz de reconstruir el rbol (otra alternativa sera transmitir
directamente la tabal de codewords). Este proceso no sera necesario si la fuente que
estamos codificando tuviera unas caractersticas estadsticas bien conocidas a priori,
tanto por el codificador como por el decodificador.
El decodificador, una vez ha reconstruido el rbol, puede decodificar la cadena original
fcilmente. Para ello, debe recorrer el rbol desde la raz hacia las hojas, usando los bits
de la cadena codificada para avanzar en el recorrido hacia las hojas. As, a partir de
C(aabaacc), el primer bit es un 0, lo que nos dar directamente el smbolo a, ya que se
llega desde la raz directamente a una de las hojas. Con el siguiente 0 ocurre lo mismo.
A continuacin partimos nuevamente de la raz, pero tenemos un 1, con lo que nos
situamos en el nodo bc, y necesitamos leer un bit ms, en este caso un 0, para llegar a
una hoja, y decodificar as el smbolo b. Este proceso contina mientras tenemos bits
que leer.

3. Entropa
La entropa es un concepto que representa los lmites de la codificacin basada en la
entropa, en la que se codifican los datos sin necesidad de conocer la naturaleza de los
mismos. Huffman es un ejemplo de codificacin basada en la entropa. La entropa
denota el mnimo nmero de bits por smbolo necesarios para representar una cadena.
Es un ndice que denota la cantidad de informacin que existe en una fuente de datos (la
cadena a codificar).
Aunque el clculo de la entropa general no se puede calcular, de forma prctica se suele
emplear la entropa de primer orden como una aproximacin. Esta entropa viene
definida como sigue:
1
H = P (a i ) log 2
P(ai )
ai A
siendo ai cada uno de los smbolos del alfabeto A.
En el ejemplo del punto anterior, la entropa se puede calcular como

1
1
1
+ P(b) log 2
+ P(c) log 2
P(a)
P(b)
P (c )
4
7 1
2
7
H = log 2 + log 2 7 + log 2 = 1,38
2
7
4 7
7

H = P(a ) log 2

Fjate que aunque la entropa nos dice que los datos se pueden codificar usando 1,38
bits por smbolo, el resultado final usando Huffman es que han sido necesarios 1,43 bits
por smbolo, ya que la entropa indica una cota inferior de los bits por smbolo
necesarios.

4. Cdigo fuente
El programa que implementa Huffman est formado por cuatro mdulos/ficheros
principales:
- codificador.cpp: obtiene los datos del usuario por parmetro (o si no se indica
ningn dato, se asignan valores por defecto) y llama a las rutinas necesarias para
realizar la codificacin Huffman.
- Huffman.cpp: este mdulo es el encargado de implementar Huffman.
- Histograma.cpp: sirve para realizar una cuenta de smbolos en la cadena
original, y calcular as las probabilidades necesarias.
- FichBits.cpp: es un sencillo mdulo para leer/escribir en disco bits sueltos, o
palabras de cualquier longitud.
Veamos cada uno de estos mdulos con algo ms de detalle.
4.1 FichBits.cpp

Este mdulo se ofrece totalmente implementado. El codificador lo usar para escribir en


fichero cada uno de los cdigos que genere. Para esto, previamente se debe inicializar
pasndole el nombre del fichero que debe crear, por medio de la funcin
int InicializaEscritura(char *nombre);

Posteriormente, cada una de las palabras generadas se pueden guardar en disco por
medio de la funcin
void EscribePalabra(int nbits, int palabra);

a la que se le pasa como primer parmetro la longitud del cdigo, y como segundo
parmetro el cdigo a almacenar. Como alternativa, si slo deseamos escribir un bit se
puede usar la funcin void EscribeBit(int bit);
Finalmente, se debe finalizar el proceso, cerrando as el fichero creado y liberando los
recursos ocupados, por medio de una llamada a la funcin
void FinalizaEscritura();

Para realizar el proceso de lectura del fichero, que ser necesario en la decodificacin,
usaremos las funciones int InicializaLectura(char *nombre) y void

FinalizaLectura() para inicializar y terminar


LeeBit(), que devuelve un bit ledo del fichero.

el proceso de lectura, y la funcin int

4.2 Histograma.cpp

Este mdulo est formado por tres variables globales, y siete rutinas que operan sobre
ellas. Las variables son:
-

unsigned int *Histograma:

Vector que contiene el histograma. Es una cuenta


de smbolos, de forma que en la posicin 0 se encuentra el nmero de veces que
aparece el primer smbolo del alfabeto (la a), en la posicin 1 tenemos las
veces que aparece el segundo smbolo (b), etc.
int longCadena: Indica la longitud de la cadena a codificar.
int tamAlfabeto: Indica el tamao del alfabeto (por defecto, nuestro alfabeto
ser desde la a hasta la e, as que ser de cinco smbolos distintos).

De las rutinas podemos destacar:


-

void InicializaHistograma(int longitudAlfabeto, int longitudCadena, char

Sirve para crear el histograma, y calcularlo a partir de los datos que se


obtienen por el tercer parmetro (cadena), cuya longitud viene indicada por el
segundo parmetro (longitudCadena).
int LeeHistograma(int pos): Con esta funcin obtenemos el nmero de veces
que aparece el smbolo pos en la cadena. La usaremos para realizar la
construccin del grafo inicial para el rbol de Huffman.
float CalculaEntropia(): Calcula la entropa a partir de la informacin en el
histograma (variable Histograma)
*cadena):

El resto de rutinas de este mdulo pueden ser consultadas en el fichero fuente, y se usan
para finalizar el histograma (liberar recursos), para guardar el histograma en disco, para
leerlo a partir del disco, y para imprimir el histograma por pantalla.
4.3 Huffman.cpp

Es aqu donde se realiza la implementacin del codificador y decodificador Huffman.


4.3.1 Construccin del rbol Huffman
Una parte fundamental del proceso de codificacin y decodificacin es la construccin
del rbol, que nos va a permitir obtener los cdigos Huffman en el codificador, y
decodificar la cadena original siguiendo el rbol en el decodificador. Para la
construccin y el recorrido de este rbol, hemos definido en Huffman.h la estructura de
tipo TNodo, que representa un nodo del rbol. Su definicin es la siguiente:
typedef struct nodo
{
struct nodo *infDer;
struct nodo *infIzq;
struct nodo *der;
unsigned int cuenta;
unsigned char simbolo;

} TNodo;

La informacin sobre el propio nodo viene dada por los atributos cuenta y simbolo,
que representan el nmero de veces que aparecen los smbolos representados por ese
nodo en la cadena a codificar (es decir, la probabilidad de ese nodo), y el propio
smbolo que se representa. Fjate que el campo smbolo slo es realmente necesario
cuando el nodo representa a una hoja, sin embargo la cuenta debe actualizarse
continuamente.
Los atributos infDer e infIzq se usan para indicar cules son los nodos hijo derecho e
hijo izquierdo del nodo actual, formando de esta forma el rbol. Observa que estos
atributos para los nodos hoja apuntarn siempre a NULL.
El ltimo de los atributos, der, se emplea durante la construccin del rbol,
manteniendo una lista enlazada horizontal que indica cules son los nodos que se
pueden juntar para seguir construyendo el rbol. Para entender el objetivo de esta lista
ser necesario recordar en algo ms de detalle la construccin de los rboles. Recuerda
que inicialmente el grafo est formado por nodos independientes. A continuacin,
juntamos los dos nodos menos probables del grafo y formamos as un nuevo rbol. A
partir de este momento, los nodos que se encuentran en las hojas de este rbol ya no son
susceptibles de ser juntados, sino que nicamente se considera el nodo raz de este
nuevo rbol. De esta forma, fjate que estamos juntando los nodos raz de los rboles
para hacer rboles mayores. Por tanto, es necesario mantener una lista de cules son los
nodos raz de los rboles, y que son susceptibles de ser unidos para crear rboles
mayores.
A continuacin veremos un ejemplo grfico para facilitar la comprensin de estas
estructuras. Un nodo aislado lo vamos a representar grficamente como sigue:
der

simbolo
(cuenta)
infIzq

infDer

Un ejemplo de situacin inicial, con un alfabeto de cuatro smbolos, sera la siguiente:


inicioHoriz

a
NULL

b
NULL

NULL

c
NULL

NULL

d
NULL

NULL

NULL

NULL

Observa como todos los nodos inicialmente forman rboles aislados, y sus punteros a
descendientes apuntan a NULL. Adems se ha formado la lista horizontal (con el
puntero der) para identificar la raz de cada rbol. En esta lista, el ltimo de los nodos
apuntara a NULL. Para su localizacin y manipulacin, es necesario indicar cul es el
primer elemento de esta lista. Este primer elemento vendr identificado por la variable
global de Huffman.cpp, inicioHoriz.

A continuacin mostramos un ejemplo del estado del rbol a medio construir


ab

inicioHoriz

cd

a
NULL

b
NULL

NULL

NULL

NULL

NULL

d
NULL

NULL

NULL

La lista horizontal sigue apuntando a la raz de los subrboles que se han construido (en
este caso dos). Finalmente, el rbol quedara como sigue
nodoRaiz

abcd

NULL

ab

inicioHoriz

cd

a
NULL

b
NULL

NULL

NULL

NULL

d
NULL

NULL

NULL

Este ltimo ejemplo nos sirve para introducir otra variable global del mdulo
Huffman.cpp, la variable nodoRaiz, que debe apuntar a la raz del rbol de Huffman
una vez construido. Este puntero ser la forma que tenemos para referenciar el rbol
para su posterior recorrido.
La funcin que se encarga de construir el rbol de Huffman es
TNodo *ConstruyeArbol()

Esta funcin tiene dos partes. En primer lugar se construye el grafo inicial, formado por
un nodo por cada smbolo del alfabeto. En segundo lugar, se realiza el proceso de
construccin del rbol. Para ello, se utiliza las funciones
TNodo *SacaMenor()
void JuntaNodo(TNodo *Nodo1, TNodo *Nodo2)

La primera de ellas saca el nodo con menor ndice de aparicin (por tanto, con menor
probabilidad) de la lista horizontal, y devuelve un puntero a ese nodo. La segunda
funcin aade un nuevo rbol a la lista horizontal, formado por la unin de los dos
nodos pasados por parmetro (y de sus descendientes).
La construccin del rbol consistir en sacar sucesivamente los dos nodos menores e ir
juntndolos, hasta que no quede ningn nodo en la lista horizontal. Finalmente, la
funcin debe devolver el nodo raz del rbol construido.

Para liberar la memoria ocupada por el rbol, se usar la funcin void


DestruyeArbol(TNodo *Raiz), que recorre el rbol de manera recursiva, liberando la
memoria ocupada por cada uno de sus nodos.
4.3.2 Construccin del vector de cdigos
Una vez se ha formado el rbol, hay que usarlo para construir los cdigos asociados a
cada smbolo del alfabeto (la tabla de codewords), realizando un recorrido del propio
rbol. Esto se ha implementado de manera recursiva, de la raz a las hojas, por medio de
la funcin:
void ConstruyeCodigos(TNodo *Raiz, unsigned int codigo,
unsigned char longcodigo)

En el recorrido del rbol, esta funcin asigna un 1 a cada rama derecha y un 0 a


cada rama izquierda (estos valores se pueden cambiar modificando los #define
RAMA_DER y RAMA_IZQ). Una vez se llega a las hojas, se asigna al smbolo representado
por esa hoja el cdigo correspondiente, que se ha generado en el camino hacia la hoja.
Despus de ejecutar esta funcin, los cdigos quedan almacenados en el vector
unsigned int *codigos, que est definido como variable global. En este vector, la
posicin cero mantiene el cdigo del primer smbolo (a), la posicin uno la del
segundo, etc. Para saber cual es la longitud de cada uno de los cdigos, es necesario
consultar otro vector, que se define como unsigned int *longCodigos.
4.3.3 Codificacin
Antes de poder usar Huffman para codificar los datos, es necesario llamar a la funcin
int InicializaHuffmanCod(int longitudAlfabeto,
unsigned int longCadena, char *cadena, char *nombre)

indicando por parmetro (1) la longitud del alfabeto a usar, (2) la longitud de la cadena,
(3) la propia cadena a codificar, y (4) el nombre con el fichero que va a contener la
cadena codificada. Es importante destacar que la cadena a codificar debe contener un
vector de bytes que representan con un 0 al primer smbolo (a), con un 1 al segundo
smbolo (b), etc., as que previamente se debe haber normalizado la cadena usando la
funcin ConvierteCadena()(ver mdulo codificador.cpp).
Bsicamente en la inicializacin de Huffman: (a) se inicializa el fichero de escritura, (b)
se calcula el histograma a partir de los datos de la cadena, (c) se construye el rbol de
Huffman, (d) se guarda el histograma en disco para que el decodificador lo tenga
disponible, (e) se construye la tabla de cdigos (codewords) a usar en la codificacin a
partir del rbol, y (f) se libera la memoria usada por el rbol.
Una vez creada la tabla de cdigos, para codificar un smbolo de la cadena es suficiente
con llamar a la funcin
void CodificaSimbolo(int simbolo);

que escribe en disco el cdigo asociado al smbolo que se desea codificar.

Finalmente, para terminar el proceso de codificacin, y liberar los recursos asociados,


hay que llamar a la funcin void FinalizaHuffmanCod().
4.3.4 Decodificacin
Como en el proceso de codificacin, antes y despus de empezar la decodificacin hay
que llamar a las funciones InicializaHuffmanDecod() (para reservar recursos y
construir el rbol de Huffman) y FinalizaHuffmanDecod() (para liberar recursos).
Para decodificar cada smbolo, a partir de la informacin previamente codificada, que se
lee progresivamente de disco, hay que usar la funcin
Int DecodificaSimbolo()

que debe recorrer el rbol desde la raz a las hojas, usando los bits que se leen del
fichero. Para realizar este proceso se puede usar la funcin auxiliar
TNodo *Descendiente(TNodo *nodo, unsigned char bit)

que devuelve el hijo izquierdo o derecho del nodo que se pasa por parmetro. Con el
parmetro bit se especifica si se desea obtener el hijo izquierdo o el derecho.
4.3.5 Otras funciones
Finalmente,

en

el mdulo Huffman.cpp, se definen las funciones void


ImprimeCodigos() para imprimir por pantalla los cdigos Huffman una vez generados,
y float CalculaBitsPorSimbolo(), que devuelve el nmero de bits por smbolo que
se han empleado en la codificacin, usando para ello una cuenta de bits (variable global
int
cuentaDeBits) y una cuenta de smbolos (variable global int
cuentaDeSimbolos).
4.4 codificador.cpp

Este es el mdulo principal (main) del programa. En primer lugar se realiza la lectura de
parmetros por consola, asignando valores por defecto si no se especifican (variables
globales cadenaPorDefecto, ficheroPorDefecto y opcionPorDefecto).
Posteriormente, mediante la funcin int ConvierteCadena(), comprueba que todos
los valores de la cadena a codificar estn dentro del alfabeto que se est usando, que
viene definido entre a y e (este ltimo valor se puede cambiar modificando
MAX_SIMBOLO), y convierte la cadena a codificar, de una representacin en cdigo
ASCII a una representacin ms adecuada para el mdulo Huffman, en la que la a se
representa con un 0, la b con un 1, etc.
Despus, si la opcin seleccionada es codificar, se inicializa la codificacin Huffman,
codificando posteriormente uno a uno los smbolos de la cadena de entrada, de la
siguiente forma
for (f=0;f<nsimbolos;f++)
CodificaSimbolo(cadena[f]);

Para, en ltimo lugar, finalizar la codificacin Huffman.


Si la opcin es seleccionada es decodificar, se inicializa la decodificacin, se
decodifica la cadena, smbolo a smbolo, mostrndose por pantalla de la siguiente forma
for (f=0;f<nsimbolos;f++)
printf("%c",DecodificaSimbolo()+'a');

y por ltimo se finaliza la decodificacin.


Finalmente, fjate que existe una ltima opcin que realiza ambos procesos
secuencialmente: primero la codificacin y luego la decodificacin de los datos.

5. Instrucciones de uso del programa


Como hemos visto en el punto anterior, el programa dispone de una serie de parmetros
por defecto, de forma que para probar su funcionamiento simplemente es necesario
teclear el nombre del programa en una ventana del sistema y pulsar enter.
Por defecto, se usa la cadena que viene definida en la variable global
cadenaPorDefecto de codificador.cpp, y se almacena en el fichero indicado en la
variable ficheroPorDefecto. Sin embargo, si quedemos modificar algn parmetro de
la codificacin, se pueden usar las opciones i cadena para indicar la cadena a
codificar, y o nombreFichero para indicar el fichero de salida. Adems, en caso de
querer usar alguna de las opciones anteriores, tendremos que indicar en primer lugar que
queremos codificar smbolos, usando el modificador c, de la siguiente forma:
codificador c i abcde o nombre2.huf

Para la decodificacin habra que usar d, y en caso de no querer usar el nombre de


fichero por defecto, usar i nombreFichero, tal y como sigue
codificador d i nombre2.huf

Observa que se puede realizar ambos procesos de codificacin y decodificacin en la


misma ejecucin, con la opcin a.

6. Trabajo a realizar
El trabajo a realizar en esta prctica es el siguiente:
1) Leer la memoria y entender cmo funciona el programa para la codificacin y
decodificacin de datos usando Huffman.
2) Implementar las funciones que se han dejado en blanco. En concreto, hay que
completar las siguientes funciones:
-

De codificador.cpp:

10

o int

ConvierteCadena
(unsigned
int
longcadena,
char
*cadenaOriginal, char *cadena): Esta es una funcin muy sencilla,

pero necesaria para poder trabajar posteriormente con los smbolos de


una manera ms cmoda.
Principalmente recibe como parmetro una cadena indicada en
cadenaOriginal, cuya longitud es longcadena, y que est
representada en cdigo ASCII, y se debe pasar a una representacin
numrica, dejando el resultado de esta conversin en el parmetro
cadena. Ser sencillo hacer la conversin restando a cada elemento de
cadenaOriginal el carcter a.
Adems, esta funcin devuelve 0 si la conversin se ha realizado con
xito, o 1 si algn elemento de la cadena a convertir est fuera del
alfabeto (es decir, es menor que a o mayor que el carcter definido en
MAX_SIMBOLO).
-

De Histograma.cpp:
o void InicializaHistograma(): En esta funcin, ya se encuentra
implementada la parte para reservar memoria para el histograma. Lo
siguiente que debes implementar es inicializar a cero todas las posiciones
del histograma, para a continuacin completarlo a partir de la
informacin que viene dada en la cadena que se lee por parmetro, y
que tiene de longitud longitudCadena.
Para poder probar sin problemas que el histograma se ha implementado
correctamente, puedes aadir temporalmente al final de esta funcin las
instrucciones ImprimeHistograma();exit(0);
Es importante que recuerdes que, en este punto del programa, los
smbolos del vector cadena ya estn normalizados por la funcin
ConvierteCadena().
o

A partir del histograma, y sabiendo que el


nmero de smbolos totales en el histograma viene dado por la variable
global longCadena, puedes obtener la probabilidad de cada smbolo del
alfabeto, y por tanto su entropa, usando para ello la expresin
anteriormente indicada.
Nuevamente, para comprobar que su clculo es correcto puedes aadir
temporalmente las instrucciones
float CalculaEntropia():

printf("\nEntropia: %f\n",CalculaEntropia());exit(0);

al final de la funcin InicializaHistograma().


Si ests usando como cadena por defecto la aabaaacceaabbbadeee, el
resultado debe ser 2.037401 bits/smbolo.
-

De Huffman.cpp:
o TNodo *ConstruyeArbol(): En esta funcin se proporciona ya
implementada la construccin del grafo inicial a partir de la informacin
del histograma. La segunda parte de la funcin es la que hay que
completar, y en ella se realiza la construccin del rbol. Para esto, hay

11

que obtener los dos nodos de menor probabilidad (usar la funcin


SacaMenor()) y juntarlos en el rbol como un solo nodo (con la funcin
JuntaNodo()).
Observa que si el segundo nodo extrado con SacaMenor() devuelve
NULL, quiere decir que ya no hay ms nodos a sacar. En ese momento
hay que terminar la funcin, devolviendo mediante la instruccin de C
return el primer nodo ledo, que resulta ser la raz del rbol.
Para comprobar que esta funcin est bien implementada, es suficiente
con ejecutar el programa y comprobar que la tabla de cdigos que se
visualiza en pantalla es correcta.
o

void CodificaSimbolo(int simbolo): Llegados a este punto ya se


ha construido el vector de cdigos (codigos) con sus longitudes
correspondiente (longCodigos). Resultar muy sencillo utilizar estos

dos vectores para codificar un smbolo, utilizando para ello la funcin


EscribePalabra() del mdulo FichBits.h.
Si est bien implementado, el fichero resultante de codificar la cadena
por defecto tendr un tamao de 24 bytes (las probabilidades tambin se
guardan en el fichero).
o

float CalculaBitsPorSimbolo(): Esta funcin devuelve el resultado


de la compresin (en bits por smbolos) como el resultado de la divisin
(en coma flotante) de los atributos globales cuentaDeBits y
cuentaDeSimbolos (recuerda que estas variables deben ser actualizadas
en la funcin CodificaSimbolo()).
Para probar la correccin de esta cuenta con la cadena por defecto, el
resultado debe ser 2.105263 bits/smbolo.

int DecodificaSimbolo().

Esta funcin debe recorrer el rbol desde el


nodo raz (indicado por la variable nodoRaiz) hasta las hojas. A partir de
este nodo raz, se puede ir descendiendo utilizando la funcin
Descendiente() y LeeBit() de la siguiente forma
Nodo=Descendiente(Nodo, LeeBit());

En el momento en el que alguno de los hijos del nodo que estamos


usando para recorrer el rbol sea NULL (el Nodo->infIzq o Nodo>infDer, de manera indistinta) no tendremos que seguir descendiendo,
sino que ya habremos llegado a un nodo hoja, y podremos devolver el
valor del atributo simbolo de esta hoja.
Para comprobar la correccin de la decodificacin, bastar con usar el
programa para decodificar una cadena previamente codificada.
3) Una vez completada la implementacin, es interesante ver el siguiente ejemplo.
Codifica la cadena que hay por defecto, y compara los valores de la entropa y el
resultado de la compresin por Huffman, cul es menor? tiene sentido?.
A continuacin codifica la cadena abcde y anota los valores de la entropa y el
resultado de la compresin. Posteriormente codifica la cadena aaaaabcde y
anota nuevamente los anteriores valores. Codifica ahora aaaabcde, con un
nmero total de as de aproximadamente 50 y anota el resultado. Repite
12

nuevamente este proceso con 100 y con ms as. Hacia que valor parece
converger la entropa? Y el resultado de Huffman? Por qu crees que se da
este resultado?

7. Posibles extensiones de la prctica


A continuacin proponemos una serie de ampliaciones sobre la prctica. Recuerda
que puedes completar una o varias de ellas, de manera que cuantas ms
ampliaciones hagas, mejor puntuacin obtendrs.
1) Realiza una implementacin adaptativa de Huffman, y comprala con la versin
original observando la tasa de compresin y tiempo de ejecucin.
Las versiones adaptativas de los codificadores basados en la entropa son
capaces de adaptarse dinmicamente a las propiedades estadsticas del conjunto
de smbolos a codificar, de manera que incluso se puede desconocer a priori las
probabilidades los smbolos que forman el alfabeto (evitando la formacin
inicial del histograma). As, inicialmente todos los smbolos del alfabeto pueden
tener la misma probabilidad de aparicin, y a medida que vamos codificando los
smbolos vamos aumentando las probabilidades de aquellos smbolos que van
apareciendo.
Una forma sencilla de implementar la adaptatividad en el programa de prcticas
es empezar con una cuenta de smbolos igual a cero para todos los smbolos y
calcular el rbol de Huffman inicial y sus codewords (lgicamente nos dar
unos cdigos de longitud similar para todos los smbolos). A partir de aqu, por
cada smbolo que codifiquemos, incrementaremos en uno la cuenta asociada a
ese smbolo, y recalcularemos el rbol de Huffman para actualizar los
codewords segn las nuevas estadsticas. Observa que as no nos hace falta
guardar las estadsticas de los smbolos del alfabeto, ya que el decodificador la
podr generar dinmicamente de la misma forma que hizo el codificador.
2) Implementa un codificador Huffman de smbolos agrupados.
Como hemos visto en esta prctica, una de las principales limitaciones de
Huffman es que necesita asignar un nmero entero de bits a cada smbolo (y por
tanto cada smbolo se codifica con al menos un bit). Esta limitacin hace que la
mnima tasa de bits por smbolo sea de 1, incluso cuando las probabilidades
estn concentradas en muy pocos smbolos. Para resolver esto, podemos
codificar agrupaciones de smbolos en lugar de smbolos sueltos.
Una primera opcin de implementacin puede ser codificar los smbolos por
parejas, de forma que el alfabeto a usar quede definido como todos los posibles
pares obtenidos de combinar dos smbolos del alfabeto inicial. Por ejemplo, si
tenemos el siguiente alfabeto de tres smbolos, A={a, b, c}, el nuevo alfabeto
combinado sera A={aa, ab, ac, ba, bb, bc, ca, cb, cc}, de forma que si uno de los
smbolos iniciales fuera muy probable (por ejemplo el smbolo a), la
repeticin de ese mismo smbolo (en este caso aa) tambin sera muy
probable, y se le podra asignar un nico bit. De esta manera, si lo vemos desde
el punto de vista del alfabeto inicial, podramos conseguir una codificacin del

13

smbolo probable (a) con tan slo medio bit. De hecho, esta es la idea que
subyace en la codificacin aritmtica.
En esta extensin de la prctica puedes probar a agrupar distinta cantidad de
smbolos, y comparar el resultado con la versin original, viendo qu sucede al
variar las caractersticas estadsticas de los smbolos del alfabeto (por ejemplo,
concentrando las probabilidades en uno o dos smbolos, distribuyndolas entre
todos los smbolos del alfabeto, etc).
3) Implementa una versin completa: adaptativa y con smbolos agrupados.
Uno de los principales inconvenientes de la codificacin agrupando smbolos es
que aumenta el tamao del alfabeto, y por tanto la cantidad de informacin
estadstica que el codificador debe comunicar al decodificador para que ste sea
capaz de construir el rbol de Huffman. Para evitar la necesidad de transmitir
estas estadsticas puedes realizar una versin adaptativa del codificador de
smbolos agrupados, tal y como se ha explicado en la primera ampliacin de
prcticas.
Una vez implementadas las cuatro versiones (la original y las tres extensiones
propuestas), sera interesante que las compararas entre ellas en trminos de
eficiencia en compresin y costes computacionales. Para ello puedes usar
distintos tipos de datos de entrada, como un fichero de texto (en cdigo ASCII) o
informacin multimedia (p.ej., una imagen sin comprimir, en formato crudo).
Ten en cuenta que el compresor que has implementado es sin prdidas (lossless)
y de carcter general, con lo que la tasa de compresin que obtendrs con
informacin multimedia ser mucho ms baja que en compresores de imgenes
especficos con prdidas (lossy).
4) Estudio y uso de una implementacin eficiente de Huffman adaptativo.
En
http://www.cipr.rpi.edu/~said/FastAC.html
puedes
encontrar
una
implementacin eficiente de Huffman adaptativo realizada por Amir Said (un
experto de reconocido prestigio en el campo de la compresin de datos). En esta
ampliacin os proponemos estudiar esta implementacin, y evaluarla,
comparndola con la implementacin de Huffman bsica que habis completado
en prcticas. Tambin sera interesante realizar una comparacin de esta versin
de Huffman con alguna implementacin de un codificador aritmtico adaptativo.

14

S-ar putea să vă placă și