LABORATORIO DE ESTRUCTURAS DE DATOS
PRÁCTICAS CURSO 1996-97
1º INGENIERÍA INFORMÁTICA

PRÁCTICA Nº 8: dos sesiones

(Semanas del 26 de Mayo al 6 de Junio de 1997)


CÓDIGOS DE HUFFMAN

OBJETIVO

PLANTEAMIENTO DEL PROBLEMA

Para mostrar la forma en que pueden usarse los árboles binarios como estructuras de datos, se va a ver en esta práctica el problema de la construcción de los llamados "códigos de Huffman":

Supóngase que se tienen mensajes formados por secuencias de caracteres (un texto cualquiera tendría esta forma). En cada mensaje, los caracteres son independientes entre sí y aparecen con una determinada probabilidad conocida a priori. Se desea codificar cada carácter mediante una secuencia de ceros y unos, de manera que ningún código de un carácter sea prefijo del código de otro carácter, esta propiedad de prefijos evitará cualquier tipo de ambigüedad en el proceso de decodificación del mensaje.

El problema de codificación que se plantea sería: dado un conjunto de caracteres, en nuestro caso supongamos que todo el código ASCII (256 valores), y sus probabilidades de aparición en un texto (este dato no es conocido a priori y tendremos que hacer un estudio previo), encontrar un código para esos caracteres que cumpla la propiedad de prefijos y tal que la longitud media del código de un carácter sea mínima. La razón por la que se desea minimizar la longitud media de un código es que ello permitirá comprimir la longitud del mensaje codificado.

El algoritmo de Huffman permite encontrar un código con las propiedades especificadas. La idea del algoritmo consiste en llegar a formar un árbol binario que represente el código. En los nodos terminales (hojas) de este árbol se almacenarán los caracteres que se desea codificar junto con la probabilidad de aparición de cada uno de ellos, cumpliéndose que los caracteres más probables están más próximos a la raíz que los menos probables. De manera que el camino desde la raíz del árbol a cualquier hoja del mismo representará el código para el carácter de esa hoja, de acuerdo con el criterio de que los subárboles izquierdos se etiquetan con un 0 y los subárboles derechos lo hacen con un 1. Según este criterio, los caracteres con mayor probabilidad de aparición tendrán los códigos de menor longitud, mientras que los que tengan una probabilidad de aparición menor tendrán asociado un código de mayor longitud.

El algoritmo de Huffman consiste en lo siguiente:

Suponiendo conocida la probabilidad de aparición de un carácter en un texto (sea pi) hacer:

  1. Crear un bosque formado por tantos árboles independientes como caracteres se vayan a codificar. Cada carácter será la raíz de un árbol independiente, almacenándose junto a él su probabilidad de aparición.

  2. Buscar en el bosque los dos árboles que presenten las menores probabilidades de aparición.

  3. Agrupar los dos árboles encontrados en el paso 2) en un solo árbol que tenga como subárbol izquierdo el árbol que tenía menor valor pi asociado y como subárbol derecho el otro árbol. En la raíz de este nuevo árbol no se almacena ningún carácter pero sí la probabilidad de aparición de los caracteres agrupados, que será la suma de sus dos probabilidades de aparición (pi + pj).

  4. Repetir desde el paso 2) hasta que en el bosque sólo quede un árbol que agrupe a todos los caracteres.

REALIZACIÓN DE LA PRÁCTICA

Codificar/Decodificar archivos de texto mediante códigos de Huffman.

Para codificar archivos, habrá que realizar las siguientes tareas:

  • Decodificación

    Los procesos de Codificación y Decodificación se englobarán en un mismo programa que permita ejecutar la tarea deseada. Se deja a criterio del alumno la conveniencia de realizar un diseño del programa en forma de módulos independientes, utilizando Unidades o ficheros Include.

    Para realizar convenientemente todas las operaciones con árboles binarios, será necesario definir un tipo de datos ARBOL (que se implementará mediante una representación enlazada), con un conjunto de operaciones básicas asociadas. Éstas serán las siguientes:

    1. ) CREAR_ARBOL () --> ARBOL
      Proporciona como resultado un árbol vacío.

    2. ) VACIO ( ARBOL ) --> LÓGICO
      Indica si el árbol está vacío o no.

    3. ) HACER_ARBOL ( ARBOL , VALOR , ARBOL ) --> ARBOL
      Construye un árbol binario que tiene como información asociada con su nodo raíz el dato de tipo VALOR pasado como 21 parámetro, el subárbol izquierdo será el primer parámetro de la función, mientras que el subárbol derecho será el último parámetro.

    4. ) HIJO_IZQUIERDO ( ARBOL ) --> ARBOL
      Devuelve el subárbol izquierdo de un árbol.

    5. ) HIJO_DERECHO ( ARBOL ) --> ARBOL
      Devuelve el subárbol derecho de un árbol.

    6. ) REC_DATOS ( ARBOL , VALOR )
      Devuelve el dato de tipo VALOR (lo almacena en el 21 parámetro del procedimiento) almacenado en la raíz del árbol.

    7. ) LIBERAR ( ARBOL )
      Libera el espacio ocupado por el árbol en memoria.

      Cualquier operación de manipulación de árboles que haya que realizar sobre árboles binarios en el programa se debe basar exclusivamente en estas operaciones básicas.

    Finalmente, para unificar criterios, vamos a establecer la forma de los tipos de datos fundamentales para la realización de la práctica:
       CONST
          Primero = 0;
          Ultimo = 255;
    {Estas dos constantes definen el rango del código ASCII que se
    va a considerar. Si las rutinas las escribimos con referencia a estas
    constantes, el programa se podrá adaptar fácilmente a otro
    conjunto de caracteres sin más que modificar estos
    valores}
    
       TYPE
          Posicion = Primero..Ultimo; {Establece el rango de caracteres válidos}
    
    {Definición de la tabla de frecuencias de aparición de caracteres}
          Frecuencia = 0..MaxInt; {Para indicar el n1 de veces que aparece un carácter}
          Tabla_Frec = array [ Posicion ] of Frecuencia;
    
    {Definición de la matriz que almacenará el código generado por el método de Huffman}
          Tabla_Codigos = array [ Posicion ] of String; { Las cadenas contendrán solo '0's o '1's }
    
    {Definición del tipo árbol}
    {Un nodo del árbol constará de la siguiente información:
          Frec    : Indica la frecuencia de aparición de los caracteres
                    que forman el árbol del cual el nodo es raíz.
          simbolo : Indica si el nodo almacena un carácter o
                    está formado por la agrupación de caracteres. Sólo los
                    nodos terminales tendrán este campo puesto a true. En ese caso, se
                    define un campo más, info que almacena el carácter.
    }
    
          Valor = Record
                     Frec : Frecuencia;
                     case simbolo : boolean of
                                       true: (info: char);
                                       false: ();
                   end;
    
          Arbol = ^Nodo;
    
          Nodo = Record
                    Dato : Valor;
                    Sub_Izq, Sub_Der : Arbol;
                 end;
    
    {Un bosque será una agrupación de árboles,
    tantos como posibles caracteres tengamos}
          Bosque = array [ Posicion ] of Arbol;
    
    {Tipos fichero que se utilizarán para manipular archivos de
    disco}
          Ficheros = file of char;
    {No se utiliza text porque sólo interesan los caracteres
    individualmente, se usarán tanto para los ficheros codificados como para
    los de texto}
    
    
    ENTREGA DE PROGRAMAS: Al finalizar la segunda sesión de prácticas.