ESTRUCTURA DE DATOS Y DE LA INFORMACIÓN 2 PRÁCTICA 1 – Colas de prioridad Calendario de entregas grupo 1a 1b 2a 2b 2c 2d 3b 3c comienzo miércoles 17/2 jueves 18/2 viernes 19/2 lunes 15/2 martes 16/2 jueves 18/2 jueves 18/2 martes 16/2 entrega miércoles 3/3 jueves 4/3 viernes 5/3 lunes 1/3 martes 2/3 jueves 4/3 jueves 4/3 martes 2/3 ENUNCIADO DE LA PRÁCTICA En esta práctica se creará una librerı́a para la gestion de colas de prioridad basado en heaps, y varios programas que la utilizan. La librerı́a deberá ser completamente abstracta, es decir, las funciones de librerı́a no ”saben” que datos están contenidos en el heap. Para la librerı́a, la cadena que constituye el heap es una cadena de punteros a void (void **). Para su funcionamiento,el heap necesita comparar elementos, y esta comparación depende de lo que hay en los elementos. Si la librerı́a es abstracta, esta comparación no se puede hacer dentro de la librerı́a ya que, si no, tendrı́amos que cambiar la librerı́a cada vez que se cambian los datos. El problema se soluciona pasando a la librerı́a como parametro, la función de comparación que hay que utilizar. Por esto, en el fichero heap.h se define el tipo de la función de comparación typedef int (*comp_f) (void *i, void *j); La declaración comp_f f; indica que f es el puntero a una función de este tipo. La función recibe como parámetros dos punteros a void, que apuntarán a dos elementos del heap, y devuelve el resultado: -1: el elemento i precede al elemento j; 0: los dos elementos son iguales; 1: el elemento j precede el elemento i; Cabe notar que hemos dicho precede, y no es mayor que. Esta interfaz es similar a la de otras funciones de comparación en C como las que usan qsort o bsearch. Podéis usar tambin cualquier valor negativo en lugar de -1, y cualquier valor positivo en lugar de 1. En esta práctica se utilizarán heaps con datos relativo a personas, y cada elemento del heap contendrá el apellido y el DNI de una persona. Hay cuatro maneras de ordenar estos datos: 1 i) por DNI en orden ascendiente; ii) por DNI en orden descendiente; iii) por apellido en orden alfabético; iv) por apellido en orden alfabético inverso. Para cada una de estas posibilidades habrá que crear una función de comparación de tipo comp_f. Por ejemplo, la función de comparación para el caso i) devolverá -1 si el elemento i tiene un DNI menor que el elemento j, mientras que la función de comparación para el caso ii) devolverá -1 si el elemento i tiene un DNI mayor que el elemento j. Las funciones de comparación para los casos iii) y iv) se comportarán de la misma manera, pero haciendo la comparación de los apellidos. La función de comparación se pasa como parametro a la función de creación del heap y se almacena en un campo de la estructura de heap. Un aspecto importante de la práctica es que la cola de prioridad debe permitir su actualización cuando se “decrementa” la “prioridad” asociada a un dato, es decir, se cambia su clave asociada con el posible resultado de que ese dato suba hacia arriba en el heap. Para poder hacer esto con la complejidad teórica vista en clase para las colas de prioridad, es necesario mantener un ı́ndice en el heap que indica, para cada elemento, cuál es su posición en ppdatos. Para ello, es necesario actualizar esta posición cada vez que se cambia en cualquiera de las funciones de heap. Para ello, los datos que se pasan para crear el heap son almacenados en un struct que contiene el dato como tal (como puntero a void) y un ı́ndice que creamos nosotros, y que servirá como ı́ndice en el array posicion que almacenamos dentro de la estructura del heap. * * * Ejercicio 1 Escribir las funciones de manejo de heaps y colas de prioridad definidas en el fichero heaps.h. Es obligatorio seguir el prototipado de las funciones definidas en este fichero cabecera. Ejercicio 2 Para probar las funciones escritas anteriormente, escribir un programa que genere un heap a partir de los datos de DNI y nombres contenidos en el fichero de texto dni.txt. Este fichero tiene como primera lı́nea un entero representando el nmero total de datos, y a continuacin la lista de datos (un dato por lı́nea). El programa (de nombre heaps_1) aceptará, por este orden, los siguientes parámetros en la lı́nea de comandos: i) Nombre del fichero a procesar ii) Flag indicando el criterio de ordenación en el heap (1=ascendente; 0=descendiente) iii) Flag indicando el campo sobre el que opera la ordenación (0=DNI; 1=nombre) iv) Flag indicando el tipo de operación (0=generar heap (hpBuildHeap); 1=ordenar datos (hpHeapsort)) v) Nombre del fichero de salida (formato igual que el fichero de entrada). Las datos se escriben segn el orden en que aparecen en el array que contiene el heap. 2 Ejemplo: heaps_1 dni.txt 1 1 1 salida.txt genera un heap ascendente sobre el nombre con los datos contenidos en el fichero ”dni.txt”, ordena las claves con hpHeapsort, y guarda los datos resultantes en el fichero ”salida.txt” (Heapsort sobre un heap ascendente proporciona las claves ordenadas de mayor a menor). El fichero de salida debe tener el mismo formato que el ”dni.txt”, y en particular debe poder ser procesado por el programa heaps_2 del siguiente ejercicio. Ejercicio 3 Escribir un programa que, dado un fichero de texto en el formato habitual (ej. dni.txt), indique si el fichero representa un heap. En particular, el programa deberá escribir en pantalla: i) si el fichero representa un heap ascendente, un heap descendiente, o está desordenado respeto al DNI; ii) si el fichero representa un heap ascendente, un heap descendiente, o está desordenado respeto al nombre; El fichero pasado como argumento se interpreta de la siguiente manera: el nodo compuesto por el par ”DNI NOMBRE” de la lı́nea i-ésima es el padre de los nodos que se encuentran en las lı́neas (2*i)-ésima y (2*i+1)-śima. El programa (de nombre heaps_2 ) acepta como parámetro el nombre del fichero y devuelve la respuesta por la salida estándar. Ejemplo: heaps_2 dni.txt Ejercicio 4 Escribir un programa para probar la rutina de inserción de datos en un heap, hpHeapInsert. El programa (de nombre heaps_3) generará un heap a partir de los datos contenidos en un fichero (usar dni.txt). Tras esto, el programa abrirá un segundo fichero de datos (usar dni_nuevos.txt) e insertará de uno en uno los datos de este fichero en el heap creado, mediante llamadas oportunas a hpHeapInsert. Una vez insertados todos los datos, el programa escribirá en un fichero de salida los datos del heap resultante. El programa aceptará, por este orden, los siguientes parámetros en la lı́nea de comandos: i) Nombre del fichero con los datos iniciales ii) Nombre del fichero con los datos a insertar iii) Flag indicando el criterio de ordenación en el heap (1=ascendente; 0=descendiente); iv) Flag indicando el campo sobre el que opera la ordenaciń (0=DNI; 1=nombre); v) Nombre del fichero de salida Ejemplo: heaps_3 dni.txt dni_nuevos.txt 0 0 salida.txt Nota: Recordad que debı́s emplear un ńico fichero Makefile para generar los ejecutables. 3