Programación 2 Solución Práctico 10 - Diseño de Tipos Abstractos de Datos Solución Práctico 10 Objetivos Profundizar en diferentes especificaciones e implementaciones para TADs. Practicar con versiones acotadas y no acotadas de los TADs, así como con estructuras estáticas y dinámicas. Practicar en diseñar TADs adecuados para resolver nuevos problemas, tomando en cuenta requerimientos como por ejemplo de eficiencia en tiempo o en espacio. Ejercicio 2 (Examen Diciembre 2003) Especifique un TAD T de elementos de un tipo genérico que permita almacenar a lo sumo K elementos donde se respeta la política LIFO (el último en entrar es el primero en salir). Se pide considerar operaciones constructoras, selectoras/destructoras y predicados. La operación de inserción no debe tener precondición, esto es: siempre se pueden agregar elementos pero sólo los últimos K (a lo sumo) se consideran. Desarrolle en una implementación del TAD T en la que las operaciones constructoras, selectoras y predicados se realicen sin recorrer la estructura. Solución Ejercicio 1 a) La Figura 1 presenta una especificación para el TAD T (Pila circular acotada). # define K < valor apropiado > /** * constructoras ** */ T vacia (); /* construye una estructura ( pila ) vacia que puede almacenar hasta K elementos */ T insertar ( generico e , T p ); /* agrega el elemento e a la estructura */ /* si la estrucutura ya contiene K elementos , se elimina el mas viejo */ /** * selectoras ** */ generico tope ( T p ); /* devuelve el elemento que ha sido insertado en ultima instancia */ /* pre : ! esta_vacia ( p ) */ T quitar ( T p ); /* devuelve la estructura sin el elemento mas nuevo */ /* pre : ! esta_vacia ( p ) */ /** * predicados ** */ bool esta_vacia ( T p ); /* devuelve true sii la estructura esta vacia */ bool tiene_k ( T p ); /* devuelve true sii la estructura cuenta con K elementos */ Figura 1: Especificación para el TAD T. b) A continuación una implementación estática para el TAD T. Instituto de Computación - Facultad de Ingeniería - UdelaR Página 1 de 6 Programación 2 Solución Práctico 10 - Diseño de Tipos Abstractos de Datos struct nodoT { generico vector [ K ]; int tope , base , cant ; }; typedef nodoT * T ; T vacia (){ T nuevaT = new nuevaT - > base = nuevaT - > tope = nuevaT - > cant = return nuevaT ; }; nodoT ; 0; K -1; /* Marca el ultimo en ser insertado */ 0; T insertar ( generico e , T p ){ p - > tope = (p - > tope + 1) % K ; /* Resto de la division entera o modulo */ p - > vector [p - > tope ] = e ; if (p - > cant < K ) p - > cant ++; else p - > base = (p - > base + 1) % K ; }; generico tope ( T p ){ return p - > vector [p - > tope ]; }; T quitar ( T p ){ p - > cant - -; p - > tope - -; if (p - > tope < 0) p - > tope = K -1; return p ; /* comparte memoria */ }; bool esta_vacia ( T p ){ return p - > cant == 0; }; bool tiene_k ( T p ){ return p - > cant == K ; }; Notar la relación que existe entre vacia, insertar y quitar y cómo se logra la circularidad en esta implementación. En vacía se inicializa el tope a K-1 para que en la primera inserción el tope coincida con la posición del último elemento insertado. La circularidad al insertar en parte se logra aplicando módulo K cuando se incrementa el tope. En insertar la base (que es donde estaría el elemento más viejo) se mueve si la pila se encuentra llena y en quitar se posiciona el tope al final del arreglo en caso de que queden elementos. No hay recorridas, por lo que todas las operaciones se ejecutan en tiempo constante peor caso. Ejercicio 4 (Basado en: Examen Diciembre 2005) Se quiere especificar e implementar un TAD Cola de Prioridad acotado (con un valor M) cuyos elementos sean de tipo T y tengan identificadores enteros en el rango [1..M], que determinen a cada elemento unívocamente. Cada elemento tiene asociada una prioridad: un número entero. Los elementos son procesados según su prioridad, de mayor a menor. Instituto de Computación - Facultad de Ingeniería - UdelaR Página 2 de 6 Programación 2 Solución Práctico 10 - Diseño de Tipos Abstractos de Datos Se debe especificar un TAD Cola de Prioridad (empty, isEmpty, insert, max, deleteMax, isFull) de acuerdo a lo descrito arriba que incluya como funcionalidad adicional una operación que dado el identificador x de un elemento y un número natural n, decremente el valor de prioridad del elemento x en n unidades, si x está en la estructura. También se debe proponer las estructuras y representaciones a usar en la implementación del TAD anterior de tal manera que las operaciones insert, deleteMax y la operación adicional realicen log2 (n) comparaciones en el peor caso, siendo n la cantidad de elementos en la cola de prioridad. Además, max e isFull deben operar sin recorrer la cola de prioridad. Concretamente: • Explique, con un dibujo y los comentarios pertinentes, la estructura que utilizaría para implementar el TAD, justificando cómo se satisfacen las restricciones previamente establecidas. • Defina en C* la estructura utilizada para representar el TAD, pero NO implemente las operaciones del TAD. Solución Ejercicio 2 Respecto a la especificación la Figura 2 presenta el TAD Cola de Prioridad (Cola de prioridad con identificadores acotados en un rango). /** * constructoras ** */ void empty ( CPrio & c ); /* Retorna la cola de prioridad vacia . */ void insert ( T dato , int id , int prio , CPrio & c ); /* Inserta el dato en la cola c con identificador id y prioridad prio . /* Pre : ! isFull ( c ). */ /* Pre : No existe elemento con identificador id en c . */ /* Pre : id esta en el rango 1.. M */ */ void decrPrio ( int id , int decr , CPrio & c ); /* Decrementa el valor de prioridad del elemento con identificador id en decr unidades . /* Pre : Existe elemento con identificador id en c . */ /* Pre : id esta en el rango 1.. M */ /** * selectoras ** */ T max ( CPrio c ); /* retorna el elemento de mayor prioridad . /* Pre : ! isEmpty ( c ). */ */ */ /** * predicados ** */ bool isEmpty ( CPrio c ); /* Devuelve true sii la estructura esta vacia */ bool isFull ( CPrio c ); /* devuelve true sii la estructura esta llena */ /** * destructoras ** */ void deleteMax ( CPrio & c ); /* Elimina el elemento de mayor prioridad . */ /* Pre : ! isEmpty ( c ). */ Respecto a la implementación se propone formar una estructura compleja, que consista de una tabla y un heap vinculados a través de los identificadores y las posiciones para cumplir las restricciones de orden. El heap se utiliza para manejar las prioridades y todas las operaciones de la cola, como es usual, pero la exigencia de la operación adicional, nos obliga a tener una tabla vinculada al heap, para evitar la búsqueda lineal y acceder en tiempo constante a cualquier elemento dado su identificador, para así poder decrementarle la prioridad al hacer un filtrado en log2(n) peor caso. Instituto de Computación - Facultad de Ingeniería - UdelaR Página 3 de 6 Programación 2 Solución Práctico 10 - Diseño de Tipos Abstractos de Datos Lo primero que hay que visualizar es el árbol(heap) y la tabla para un acceso en tiempo constante a cualquier nodo: Yendo a la implementación concreta en C y dado que la cantidad máxima de elementos está acotada en M y que el rango de los identificadores es [1..M], las estructuras consideradas serán: Heap: arreglo con tope (de 1 a M) que almacene elementos de tipo T con su prioridad e identificador. Tabla: arreglo de 1 a M que almacene enteros de rango [0..M] que indican la posición en el Heap del elemento que tiene como identificador el índice del arreglo, donde 0 (o vacío en el dibujo) indica la no existencia de dicho elemento en la cola. Por lo que la figura anterior se puede ver también como: La operación max se soluciona obteniendo el primer elemento del heap, lo cual no requiere recorrer la cola de prioridad. La operación isFull se soluciona verificando que el tope del arreglo sea igual a M, lo cual tampoco requiere recorrer la cola de prioridad. Ya para la operación decrPrio obsérvese qué ocurre en caso de querer decrementar en 10 la prioridad del nodo de identificador 1: 1) Se accede al nodo del heap sin recorrer ninguna estructura de la cola utilizando la tabla: Instituto de Computación - Facultad de Ingeniería - UdelaR Página 4 de 6 Programación 2 Solución Práctico 10 - Diseño de Tipos Abstractos de Datos 2) Se aplica el filtrado todas las veces que sea necesario (en este caso una vez, intercambiando 14 por 21). Notar que también se debe actualizar la tabla en cada intercambio del filtrado: 3) Así que finalmente queda: Como un filtrado requiere log2(n) en peor caso en un heap, y la tabla se accede en tiempo constante, se cumple con las restricciones del peor caso. Las operaciones insert y deleteMax se resuelven de forma similar. Finalmente la estructura en C sería: Instituto de Computación - Facultad de Ingeniería - UdelaR Página 5 de 6 Programación 2 Solución Práctico 10 - Diseño de Tipos Abstractos de Datos # define M < valor apropiado > struct nodo { int id , prio ; T dato ; }; struct CPrio { int tabla [ M ]; nodo heap [ M ]; int topeHeap ; }; Instituto de Computación - Facultad de Ingeniería - UdelaR Página 6 de 6