P R ÁCTICA 2 (A) TABLAS DE DISPERSI ÓN E STRUCTURAS DE DATOS Y ALGORITMOS FACULTAD DE I NFORM ÁTICA C URSO 2009-2010 1. Introducción Esta práctica tiene como objetivo aprender a implementar y a evaluar tablas de dispersión, ası́ como aprender a evaluar su rendimiento. En esta práctica se va a trabajar con tablas de dispersión que almacenan imágenes [7] y les asocian una cadena de caracteres. 2. La clase imagen En la primera práctica se introdujo una clase para representar imágenes. Dicha clase contenı́a un conjunto básico de métodos. En esta segunda práctica vamos a enriquecer dicha clase con algunos métodos adicionales que nos resultarán útiles. Los métodos que vamos a incorporar son el constructor de copia y además sobrecargaremos el operador de igualdad para poder comparar dos imágenes. Para sobrecargar el operador de igualdad resulta muy útil tener el sobrecargado los operadores del igualdad y desigualdad de pı́xeles (class Pixel). De esta manera la clase pı́xel deberá quedar como: class Pixel { public: unsigned char r,g,b; Pixel(unsigned char r=0, unsigned char g=0, unsigned char b=0); void to_gray_level(); bool operator==(const Pixel& der) const; bool operator!=(const Pixel& der) const; }; mientras que la clase imagen quedará como: class ImageColor { public : ImageColor (); ImageColor (int width , int height); ImageColor (std::istream & fich_in); ImageColor(const ImageColor &der); //Ejercicio 1 ˜ImageColor (); int getWidth () const; int getHeight () const; int coord2index (int row , int col) const; bool read_ppm (std::istream & fich_in); void write_ppm (std::ostream & fich_out) const; Pixel& operator ()(int row , int col); void to_gray_level(); bool operator==(const ImageColor & der) const; //Ejercicio 1 private : Pixel * data ; int width , height ; }; Ejercicio 1. Escribe los métodos constructor de copia y el método de sobrecarga del operador de igualdad de la clase ImageColor para poder comparar dos imágenes. Escribe un programa que te permita comprobar el buen funcionamiento 1 de los métodos implementados. Para ello puedes tomar como referencia el programa de pasar a gris y la imagen que se utilizaba en la práctica 1. 3. Tablas de dispersión En esta práctica se pretende trabajar con tablas de dispersión para guardar un conjunto de imágenes. En esta tabla, el método de resolución de colisiones que vamos a utilizar será el de “encadenamiento separado”. Cada imagen de este conjunto llevará asociada un breve descripción acerca de lo que representa la imagen. Puesto que estamos interesados en realizar una lectura algo más eficiente de las imágenes, éstas estarán guardadas en formato binario. Por este motivo hemos ampliado la funcionalidad del método de lectura de la práctica 1. Si bien no se pide que implementes esta nueva función, sı́ es importante que la consultes y trates de entenderla. La clase que utilizaremos para representar la tabla de dispersión será como sigue (parte del código puedes encontrarlo en ∼/asigDSIC/FI/eda/PR2): #ifndef HASH_TABLE_H #define HASH_TABLE_H #include "imagen.h" class hash_node { public: ImageColor * image; // Puntero a una imagen char * description; // Descripción de la imagen unsigned int hash_value; // Guardamos el valor calculado por la // función de dispersión sobre la imagen para ahorra cáculos hash_node *next; // Puntero al siguiente nodo hash_node(ImageColor *img, // realiza una copia local char * desc, // realiza una copia local unsigned int hash_val, hash_node *nxt); ˜hash_node(); // libera la copia local de imagen y descricion }; // define el tipo "puntero a una función de recibe una imagen etc." typedef unsigned int (* Hash_function_pointer)(ImageColor *image); // tres ejemplos de funcion que dispersa una imagen en un unsigned int unsigned int hash_suma(ImageColor * image); unsigned int hash_division(ImageColor * image); unsigned int hash_multiplicativa(ImageColor * image); class hash_table{ hash_node **table; int tableSize; int numElem; // vector de "cubetas" o cabezas de listas // Talla del vector table // Número de imagenes insertadas Hash_function_pointer hash_function; // puntero a la func. dispersion float rehashing_value; void rehash(); // Redispersion: // Si se supera se hace rehashing // Dobla el numero de cubetas public: hash_table(Hash_function_pointer hash_func, int nslots=1024, float rehashing_val=3.0); ˜hash_table (); // hace una copia local de la imagen void insert(ImageColor *ima, char *desc); // Devuelve true si el elemento está en el conjunto, en cuyo // caso deja en desc un puntero a la descripción bool search (ImageColor *ima, const char* &desc) const; // Devuelve el numero de elementos del conjunto int getSize() const { return numElem; } }; #endif // HASH_TABLE_H A continuación comentamos algunos detalles de esta clase. Cada cubeta de la tabla de dispersión se implementará como una lista enlazada de nodos del tipo hash_node. Cada nodo de estas listas guarda una imagen. Puesto que el computo de la función de dispersión puede ser costoso, guardaremos en una variable hash_value dicho valor. Esto nos permitirá ahorrar algunos cálculos, puesto que para determinar si dos imágenes son iguales, sólo las compararemos cuando el contenido la variable hash_value de ambas sea igual. En esta implementación hemos proporcionado tres funciones de dispersión distintas que generan un valor numérico a partir de una imagen (hay que hacer módulo el número de cubetas si se quiere utilizar como ı́ndice de la tabla). La tabla debe usar la misma función durante toda su utilización, pero puedes elegir una de las tres para compararlas. En el constructor, además de especificar qué función de dispersión vamos a utilizar (observa que no es posible cambiar dicha función más adelante), podemos indicar el número de cubetas y el factor de carga elegido para realizar una redispersión. Ejercicio 2. Implementa el método el método constructor y el método destructor de la clase hash_table. Compı́lalos junto con el programa ejercicio2 (que encontrarás en ∼/asigDSIC/FI/eda/PR2) y comprueba que no da errores de ejecución. Ejercicio 3. Implementa el método insert de la clase hash_table. Compı́lalo junto con el programa ejercicio3 (que encontrarás en ∼/asigDSIC/FI/eda/PR2) y comprueba que no da errores de ejecución. Ejercicio 4. Implementa el método search de la clase hash_table. Una posible manera de comprobar el correcto funcionamiento del programa implementado es escribir un programa que después de cargar un conjunto de imágenes, busque en la tabla una imagen guardada en un fichero y escriba su descripción. Escribe dicho programa de acuerdo con la sintaxis: ejercicio4 fichero_indice_de_imagenes fichero_con_imagen_a_buscar Como ı́ndice puedes utilizar el fichero que encontrarás en ∼/asigDSIC/FI/eda/DATOS/IMAGENES/indice. En este fichero, cada lı́nea contiene el nombre de un fichero que contiene una imagen y su descripción. Es conveniente que no copies las imágenes en tu HOME puesto que no tendrás suficiente espacio. Cópialas en el directorio /tmp o, mejor aun, no copies las imágenes y úsalas desde la ruta original. Cuando insertamos un elemento que no estaba en la tabla, se incrementa en uno el número de elementos. En ese momento se recalcula el factor de carga (número de elementos dividido por número de cubetas). Si el nuevo factor de carga supera el lı́mite especificado, debemos doblar el número de cubetas y redispersar los nodos de todas las listas enlazadas. Ejercicio 5. Implementa el método de insertar, incluyendo el método de redispersión. Comprueba su correcto funcionamiento. 4. Estudio del comportamiento de las tablas de dispersión Para estudiar el comportamiento de las tablas de dispersión es muy útil sacar estadı́sticas sobre la forma en que las funciones consiguen repartir los elementos entre las cubetas. Ejercicio 6. Implementa los métodos siguientes en la clase hash_table: double get_load_factor() const; double get_standard_deviation() const; void get_histogram(ostream& fich) const; El método histogram recibe un fichero abierto en modo escritura (por ejemplo, la salida estándar) y escribe por dicho fichero el histograma de ocupación como una secuencia de lı́neas con dos valores: longitud_lista número_cubetas ... Este formato permite visualizar el histograma de ocupación con la herramienta gnuplot usando el comando plot: gnuplot> plot ’histograma.txt’ using 1:2 with boxes Ejercicio 7. Se propone usar el programa implementado para estudiar las propiedades de las tres funciones de dispersión descritas. Para cada función hay que obtener el correspondiente histograma de ocupación de cubetas para diferentes números de cubetas. 5. Ejercicios adicionales Ejercicio. Sobrecarga el operador de asignación en la clase ImageColor. Ejercicio. Las funciones de dispersión estudiadas en esta práctica hacen un cálculo a partir de todos los pı́xeles de la imagen. Define nuevas funciones de dispersión que utilicen sólo una parte de los pı́xeles (la mitad, los que están en posiciones pares, etc). Estudia el comportamiento de la tabla de dispersión con las nuevas funciones. Ejercicio. Sobrecarga el operador de igualdad para la clase hash_table de tal forma que devuelva true si dos tablas son iguales y false en caso contrario. La dos tablas pueden tener un número de cubetas y una función de dispersión diferente. Ejercicio. Escribe un constructor de copia en la clase hash_table que reproduzca la tabla. Ejercicio. Escribe una clase para trabajar con tablas de dispersión que almacenen cadenas de caracteres. Define una función de dispersión apropiada. Estudia el comportamiento de la tabla de dispersión implementada con los datos que puedes encontrar en ∼/asigDSIC/FI/eda/DATOS/datos palabras.gz (el fichero descomprimido ocupa 85 MB). Referencias [1] D.E. Knuth “Sorting and Searching”, vol. 3, “The Art of Computer Programming”, Addison-Wesley, 1973. [2] T. Cormen, Ch. Leiserson, R. Rivest, “Introduction to Algorithms”, MIT, 1990. [3] B.J. McKenzie, R. Harries, T. Bell “Selecting a Hashing Algorithm”, Software–Practice and Experience, Vol. 20(2), pp. 209–224, 1990. [4] G.H. Gonnet, R. Baeza-Yates, “Handbook of algorithms and data structures: in Pascal and C”, Addison-Wesley, 2nd ed., 1991. [5] M.A. Weiss, “Estructuras de datos y algoritmos”, Addison-Wesley, 1995. [6] G.L. Heileman “Data structures, algorithms and object-oriented programming”, McGraw-Hill, 1996. [7] J. M. Geusebroek, G. J. Burghouts, and A. W. M. Smeulders, “The Amsterdam library of object images”, Int. J. Comput. Vision, 61(1), 103-112, January, 2005