Estructuras de datos II. Convocatoria: Junio 2005 1 Estructuras de datos II Convocatoria: Junio 2005 Dpto. LSIIS. Unidad de Programación Normas Duración: El examen se compone de 1 ejercicio y una colección de preguntas cortas. Su duración será de dos horas y cuarto. Transcurrida una hora del comienzo del examen se recogerán las preguntas cortas. Calificaciones: Las calificaciones se publicarán en el tablón de anuncios de la asignatura el dı́a 20 de Junio de 2005. Revisión: La revisión del examen se realizará el dı́a 22 de Junio de 2005. Soluciones: Las soluciones a los ejercicios se publicarán en el tablón de la asignatura junto con las notas. 1. Montı́culos sobre Ficheros Directos [4 puntos] La implementación de colas con prioridad vista en clase utiliza un montı́culo. Como es sabido, los montı́culos se pueden representar de forma eficiente utilizando una secuencia de elementos, puesto que se puede determinar de manera sencilla y eficiente el ı́ndice de la secuencia donde se encuentra el nodo padre y los hijos de cualquier dado. En la implementación vista en clase, el montı́culo se implementa mediante un registro que contiene un array Secuencia donde se guardan los pares (Elemento, P rioridad) y el contador Longitud. En este ejercicio se pretende modificar la implementación de colas con prioridad basada en montı́culos de manera que la estructura de datos Secuencia, en vez de un array, sea un fichero de acceso directo, mediante la utilización del paquete Ada.Direct IO. Por tanto, los accesos a array de la implementación original se deberán reemplazar por operaciones de lectura y escritura a fichero. Se incluye a continuación la parte relevante de interfaz del paquete genérico colas con prioridad que debemos utilizar. with Ada . D i r e c t I O ; generic type T i p o E l e m e n t o i s private ; package C o l a s P e r s i s type ColaP i s limited private ; subtype T i p o P r i o r i d a d i s N a t u r a l ; procedure C r e a r V a c i a ( C o l a : out ColaP ) ; function E s V a c i a ( C o l a : in ColaP ) return B o o l e a n ; function E s t a L l e n a ( C o l a : in ColaP ) return B o o l e a n ; procedure I n s e r t a r ( C o l a : in out ColaP ; E l e m e n t o : in T i p o E l e m e n t o ; P r i o r i d a d : in T i p o P r i o r i d a d ) ; private type Tipo Componente i s record Elemento : Tipo Elemento ; P r i o r i d a d : T i p o P r i o r i d a d ; end record ; package S e c u e n c i a s i s new Ada . D i r e c t I O ( Tipo Componente ) ; use S e c u e n c i a s ; subtype T i p o P o s i c i o n i s P o s i t i v e C o u n t ; subtype T i p o L o n g i t u d i s Count ; type ColaP i s record Secuencia : File Type ; Longitud : Tipo Longitud := 0; end record ; end C o l a s P e r s ; Con esta implementación el código de la operación Crear Vacı́a es el siguiente: procedure C r e a r V a c i a ( C o l a : out ColaP ) is begin Create ( Cola . Secuencia , I n O u t F i l e , ”” ) ; Cola . Longitud : = 0 ; end C r e a r V a c i a ; Estructuras de datos II. Convocatoria: Junio 2005 2 Se pide: Apartado a) ¿Qué implementación es más eficiente en tiempo, la vista en clase que utiliza arrays, o la propuesta en este ejercicio que utiliza memoria secundaria? ¿Por qué? Apartado b) Indı́quense (varias) ventajas de la implementación propuesta respecto de la vista en clase. ¿Por qué? Apartado c) Realı́cese el código de las operaciones Es Vacı́a, Está Llena e Insertar. Como recordatorio, se incluye a continuación la parte relevante del paquete Ada.Direct IO. generic type E l e m e n t T y p e i s private ; package Ada . D i r e c t I O i s type F i l e T y p e i s limited private ; type F i l e M o d e i s ( I n F i l e , I n o u t F i l e , O u t F i l e ) ; type Count i s range 0 . . System . D i r e c t I O . Count ’ L a s t ; subtype P o s i t i v e C o u n t i s Count range 1 . . Count ’ L a s t ; procedure C r e a t e ( F i l e : in out F i l e T y p e ; Mode : in F i l e M o d e : = I n o u t F i l e ; Name : in S t r i n g : = ” ” ; Form : in S t r i n g : = ” ” ) ; procedure Open ( F i l e : in out F i l e T y p e ; Mode : in F i l e M o d e ; Name : in S t r i n g ; Form : in S t r i n g : = ” ” ) ; procedure C l o s e ( F i l e : in out F i l e T y p e ) ; procedure D e l e t e ( F i l e : in out F i l e T y p e ) ; procedure R e s e t ( F i l e : in out F i l e T y p e ; Mode : in F i l e M o d e ) ; procedure Read ( F i l e : in F i l e T y p e ; I t e m : out E l e m e n t T y p e ; From : in P o s i t i v e C o u n t ) ; procedure Read ( F i l e : in F i l e T y p e ; I t e m : out E l e m e n t T y p e ) ; procedure W r i t e ( F i l e : in F i l e T y p e ; I t e m : in E l e m e n t T y p e ; To : in P o s i t i v e C o u n t ) ; procedure W r i t e ( F i l e : in F i l e T y p e ; I t e m : in E l e m e n t T y p e ) ; procedure S e t I n d e x ( F i l e : in F i l e T y p e ; To : in P o s i t i v e C o u n t ) ; function I n d e x ( F i l e : in F i l e T y p e ) return P o s i t i v e C o u n t ; function S i z e ( F i l e : in F i l e T y p e ) return Count ; function E n d O f F i l e ( F i l e : in F i l e T y p e ) return B o o l e a n ; Solución Apartado a) El orden de complejidad para las distintas operaciones es idéntico en las dos implementaciones, la vista en clase y la propuesta en este ejercicio. La diferencia radica en que en la implementación vista en clase los accesos al montı́culo se realizan sobre un array que reside en memoria principal, mientras que en la implementación propuesta en este ejercicio el montı́culo se almacena en memoria secundaria. En el caso de la operación Insertar, en ambas implementaciones el orden de complejidad es logarı́tmico (O(log n)), siendo n el número de elementos que contiene el montı́culo. Dado que en general los accesos a memoria secundaria son más lentos que los accesos a memoria principal, la implementación vista en clase será mas eficiente en tiempo que la implementación propuesta en este ejercicio. Apartado b) La implementación propuesta en este ejercicio presenta varias ventajas con respecto a la utilización de arrays en memoria principal. Las dos ventajas fundamentales son: 1. Los ficheros de acceso directo son estructuras de datos dinámicas en el sentido de que su tamaño puede aumentar en tiempo de ejecución según sea necesario. De esta manera, no se necesita fijar un tamaño máximo predeterminado para la cola con prioridad, como ocurre en la implementación que utiliza arrays. 2. En general, los ficheros de acceso directo pueden contener muchos más datos que los arrays almacenados en memoria principal, porque la memoria secundaria suele tener mayor capacidad que la memoria principal. Estructuras de datos II. Convocatoria: Junio 2005 3 En nuestro caso, la persistencia asociada al uso de ficheros no representa una ventaja importante, puesto que se utilizan ficheros auxiliares temporales. Esto se puede observar en el código de la operación Crear Vacı́a donde aparece la lı́nea: Create ( Cola . Secuencia , I n O u t F i l e , ”” ) ; donde se puede ver que el tercer argumento (nombre del fichero) es la cadena vacı́a. Los ficheros temporales desaparecen cuando termina la ejecución del programa y como no tienen un nombre significativo, no se puede acceder a ellos desde otros programas. Ni siquiera desde otra ejecución sucesiva del mismo programa. Apartado c) Es Vacı́a Para comprobar si una cola con prioridad es vacı́a basta con comprobar si su longitud es cero. function E s V a c i a ( C o l a : ColaP ) return B o o l e a n i s begin return C o l a . L o n g i t u d = 0 ; end E s V a c i a ; Está Llena Como los ficheros directos son una estructura de datos dinámica, en principio pueden crecer todo lo que sea necesario para guardar nuevos datos. Ası́ que una solución que se admite como válida es la siguiente. function E s t a L l e n a ( C o l a : ColaP ) return B o o l e a n is begin return F a l s e ; end E s t a L l e n a ; Por otro lado, como la implementación de los ficheros binarios de acceso directo necesita internamente un ı́ndice (cursor) que es de tipo Count, en la práctica el tamaño de los ficheros directos está limitado por el valor máximo que podamos representar usando el tipo Count. Ası́ que otra implementación de la operación Está Llena que es totalmente válida es la siguiente: function E s t a L l e n a ( C o l a : ColaP ) return B o o l e a n is begin return C o l a . L o n g i t u d = Count ’ L a s t ; end E s t a L l e n a ; Insertar Para la realización del procedimiento Insertar, como se dijo durante el examen, se podı́a asumir la existencia del procedimiento auxiliar Intercambiar sin necesidad de implementarlo. Una posible implementación de dicho procedimiento es la mostrada a continuación: procedure I n t e r c a m b i a r ( Secuencia : in out F i l e T y p e ; Pos1 , Pos2 : in Tipo Posicion ) is Componente1 , Componente2 : Tipo Componente ; begin Read ( S e c u e n c i a , Componente1 , Pos1 ) ; Read ( S e c u e n c i a , Componente2 , Pos2 ) ; W r i t e ( S e c u e n c i a , Componente2 , Pos1 ) ; W r i t e ( S e c u e n c i a , Componente1 , Pos2 ) ; end I n t e r c a m b i a r ; Estructuras de datos II. Convocatoria: Junio 2005 4 Finalmente se muestra una implementación del procedimiento Insertar. Este procedimiento empieza incrementando la longitud de la secuencia y almacena el nuevo dato en la última posición de la secuencia. Posteriormente intercambia los valores del dato actual (o problema) con el de su padre en el montı́culo hasta que alcanzamos la raı́z del montı́culo o el dato actual está ordenado (su prioridad no es mayor que la del padre). procedure I n s e r t a r ( Cola : in out ColaP ; E l e m e n t o : in Tipo Elemento ; P r i o r i d a d : in Tipo Prioridad ) is Indice Actual , Indice Padre : Tipo Longitud ; Padre : Tipo Componente ; Ordenado : B o o l e a n : = F a l s e ; begin Cola . Longitud := Cola . Longitud + 1; W r i t e ( C o l a . S e c u e n c i a , ( Elemento , P r i o r i d a d ) , C o l a . L o n g i t u d ) ; I n d i c e A c t u a l := Cola . Longitud ; while ( I n d i c e A c t u a l > 1 ) and not Ordenado loop Indice Padre := Indice Actual / 2 ; Read ( C o l a . S e c u e n c i a , Padre , I n d i c e P a d r e ) ; i f ( P r i o r i d a d > Padre . P r i o r i d a d ) then I n t e r c a m b i a r ( Cola . Secuencia , I n d i c e A c t u a l , I n d i c e P a d r e ) ; Indice Actual := Indice Padre ; else Ordenado : = True ; end i f ; end loop ; end I n s e r t a r ; Observaciones Algunas observaciones adicionales sobre la solución del ejercicio son las siguientes: Para conocer la longitud de la secuencia en un momento dado se debe consultar el valor de Cola.Secuencia y no el de Size (Cola.Secuencia) porque pueden no ser iguales. En concreto, las operaciones de borrado decrementan Cola.Secuencia, pero no Size (Cola.Secuencia). La operación Insertar recibe un fichero que ya está abierto. El fichero se abre en la operación Crear Vacı́a y se cierra en la operación Destruir. No se debe por tanto abrir ni cerrar durante la ejecución de Insertar. En la operación de Insertar hay que tener cuidado para no leer del fichero posiciones (padres) no existentes, bien porque el nuevo elemento insertado sea el único de la cola (y por tanto no tiene padre), bien porque el nodo problema es ya la raı́z del montı́culo tras iterar en el bucle que reconstruye la propiedad de los valores de un montı́culo. Convocatoria: Junio 2005 Estructuras de datos II. 2. 5 Preguntas cortas [6 puntos] Apellidos: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nombre: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Número de matrı́cula: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Grupo: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Esta hoja se deberá rellenar con los datos del alumno y entregar como parte del examen. Todas las preguntas valen 1 punto. La primera de ellas es una pregunta de test, y sólo tiene una respuesta válida. Si se responde de manera incorrecta, se descontará medio punto de la nota. El resto de preguntas no descuentan puntuación si se responden incorrectamente. Este ejercicio se recogerá transcurrida 1 hora desde el comienzo del examen. 1. Las bases de datos surgen para paliar los problemas de las aplicaciones basadas en archivos. Establezca cuales eran esas dificultades: ( ) Independencia en los programas de los archivos de datos, redundancia de datos, dificultad de mantenimiento (X) Dificultad de mantener la integridad de los datos, dependencia en las aplicaciones de los archivos de datos, redundancia ( ) Los datos solo se encuentran en un fichero, dificultad de mantenimiento, falta de seguridad y ausencia de redundancia ( ) Los cambios en los archivos de datos obliga a modificar los programas que los utilizan, los datos no son accesibles para las aplicaciones, no se tienen datos que no aporten información 2. Construye un árbol-B de grado m=5 que almacene un número n=17 de elementos y que tenga la mayor profundidad posible. Supón que los elementos del árbol son números naturales, y escoge los n que quieras para formar parte del árbol. 9 3 1 3. 2 6 4 5 12 7 8 10 11 15 13 14 16 17 Dadas las siguientes funciones de complejidad, establece el orden de inclusión correcto entre los conjuntos O(f) (debes dar una secuencia de inclusiones de la forma O(f ) ⊂ O(g) ⊂ O(h) ⊂ . . . ) : f1 (n) = log22 n f2 (n) = 23 log2 n f3 (n) = 6n1/2 f4 (n) = n3 + 2n + 4 f5 (n) = 2n f6 (n) = 10n2 + 6n f7 (n) = 3n log4 n Escribe aquı́ la secuencia correcta de inclusiones: O(f2 ) ⊂ O(f1 ) ⊂ O(f3 ) ⊂ O(f7 ) ⊂ O(f6 ) ⊂ O(f4 ) ⊂ O(f5 ) Convocatoria: Junio 2005 Estructuras de datos II. 4. 6 Para cada una de las implementaciones de tablas mencionadas, debes decir el orden de complejidad en el peor caso para la operación de borrado de un elemento: Vector ordenado : O(n) Árbol AVL : O(log n) Árbol de búsqueda : O(n) Vector de booleanos : O(1) ( n denota el número de elementos amacenados en la tabla) 5. Supóngase que se decide implementar el TAD matrices utilizando tablas hash con direccionamiento abierto. Supóngase también que es asumible el declarar como tamaño máximo de la tabla la cantidad n × m, siendo n y m el máximo número de filas y columnas de cualquier variable de dicho TAD matrices. Escoger el tipo de claves y una función hash con las que instanciar el paquete de tablas de manera que se minimice la complejidad de las operaciones Almacenar y Recuperar (operaciones del TAD matriz). Se puede considerar que el TAD de matrices se ha instanciado con 1 . . . n y 1 . . . m como Tipo Indice Filas y Tipo Indice Columnas respectivamente. ¿Cuál es la complejidad en el peor caso de ambas operaciones? Asumimos que la longitud de la tabla es n × m. Las claves son pares de ı́ndices (i, j), donde i indica la fila y j indica la columna. Como función hash que garantiza que no se producirán colisiones podemos utilizar la que aplana la matriz por filas, es decir: f (i, j) = (i − 1) × m + j. Con esta elección garantizamos complejidad O(1) en el peor caso para ambas operaciones. 6. Se tiene una tabla hash con direccionamiento abierto de tamaño 10. Inicialmente la tabla está vacı́a. Asumimos que la clave de los elementos de la tabla es un número natural, que la función hash es hash(clave) = clave M OD 10, y que resolvemos los conflictos siguiendo la estrategia lineal (Siguiente(P os) = (P os + 1) mod 10). Dibujar la tabla resultante de insertar, en este orden, las claves 13, 45, 22, 63, 3, 19 (omitimos el campo correspondiente a la información asociada a cada clave, por ser irrelevante para la ejecución de las operaciones). A continuación, eliminar la clave 63 e insertar la clave 3. Pintar la tabla resultante de la ejecución de estas dos últimas operaciones. 0 1 V V 0 1 V V 2 3 4 5 22 13 63 45 3 O O O O O 6 7 V 2 3 4 5 6 22 13 3 45 3 O O O O 8 L/V V 7 V 9 8 V 19 Clave O Estado 9 19 Clave O Estado