Solución

Anuncio
Primer parcial de EDA
Facultad de Informática de Valencia
31 de enero de 2005 – Duración 3.5 horas
No olvides poner el nombre. No utilices lápiz ni tinta roja.
Pregunta 1 (2.5 puntos)
Dada la siguiente implementación en C++ de un árbol binario:
c l a s s nodo abb { // c l a s e a u x i l i a r para r e p r e s e n t a r un nodo
public :
int v a l o r ;
nodo abb ∗ h i z q , ∗ h d e r ;
};
c l a s s abb { // c l a s e á r b o l b i n a r i o
public :
nodo abb ∗ r a i z ;
abb ( ) ;
˜abb ( ) ;
void l i s t a r p r o f u n d i d a d ( i n t d i s t ) ; // <−− E j e r c i c i o a p a r t a d o a )
void l i s t a r a l t u r a ( i n t d i s t ) ; // <−− E j e r c i c i o a p a r t a d o b )
};
Se pide:
a) (1.25 puntos) Implementa un método que escriba por pantalla los valores de todos los nodos que tengan
una distancia dada desde la raı́z (la distancia de la raı́z a sı́ misma es 0). Ejemplos: una llamada con valor
0 visualizarı́a la raı́z del árbol, con valor 1 sus hijos, etc.
Solución:
void
if
}
void
if
abb : : l i s t a r p r o f u n d i d a d ( i n t d i s t ) { // s i á r b o l no v a cı́ o :
( r a i z && d i s t >=0) r a i z −>l i s t a r p r o f u n d i d a d ( d i s t ) ; // método aux .
nodo abb : : l i s t a r p r o f u n d i d a d ( i n t d i s t ) { // a ñadimos t b en c a b e c e r a
( d i s t == 0)
c o u t << v a l o r << e n d l ;
else {
i f ( h i z q ) h i z q −>l i s t a r p r o f u n d i d a d ( d i s t −1) ;
i f ( h d e r ) h d e r −>l i s t a r p r o f u n d i d a d ( d i s t −1) ;
}
}
b) (1.25 puntos) Implementa un método que escriba por pantalla los valores de los nodos tales que la distancia
a sus hojas más lejanas sea igual a un valor dado. Ejemplos: una llamada con distancia 0 visualizarı́a las
hojas del árbol, una llamada con valor 1 los nodos que tengan sólo hojas como hijos, etc.
Solución:
void abb : : l i s t a r a l t u r a ( i n t d i s t ) { // s i á r b o l no v a cı́ o :
i f ( r a i z && d i s t >=0) r a i z −> l i s t a r a l t u r a ( d i s t ) ; // método a u x i l i a r
}
i n t nodo abb : : l i s t a r a l t u r a ( i n t d i s t ) { // l o a ñadimos t b en c a b e c e r a
i n t a l t u r a = 0 , o t r a ; // primero c a l c u l a m o s l a d i s t a n c i a a l a h o j a
i f ( h i z q ) // más l e j a n a , bajamos por e l s u b á r b o l i z q u i e r d o
a l t u r a = h i z q −>l i s t a r a l t u r a ( d i s t ) +1;
i f ( h d e r ) { // y l u e g o por e l d e r e c h o
o t r a = h d e r −> l i s t a r a l t u r a ( d i s t ) +1;
i f ( otra > altura ) altura = otra ;
} // una v e z c o n o c i d o e l v a l o r sabemos s i podemos i m p r i m i r e l nodo :
i f ( a l t u r a == d i s t ) c o u t << v a l o r << e n d l ;
return a l t u r a ; // d e v o l v e m o s l a a l t u r a para que l a u s e q u i e n nos l l a m ó
}
Pregunta 2 (2.5 puntos)
Disponemos de dos tablas hash con el mismo número de cubetas y con la misma función de dispersión que
almacenan valores enteros. Las tablas realizan una resolución de colisiones por encadenamiento. Las listas
asociadas a cada cubeta contienen los valores ordenados de menor a mayor. Queremos obtener otra tabla con el
mismo número de cubetas que contenga una copia de los elementos de ambas tablas. Si algún elemento está en
las dos tablas, no debe aparecer repetido. Las listas asociadas a cada cubeta en la tabla resultado deben contener
los elementos ordenados de menor a mayor. Implementa este método de modo que el coste de la operación sea
lineal con el número de elementos más el número de cubetas de la tabla resultante.
struct nodo {
int c l a v e ;
nodo ∗ s i g ;
};
class tablaHash {
public :
i n t numcubetas ;
nodo ∗ ∗ t a b l a ;
i n t f u n c i o n h a s h ( i n t c l a v e ) ; // d e v u e l v e v a l o r e n t r e 0 y numcubetas −1
t a b l a H a s h ( i n t numcubetas ) ;
˜ tablaHash ( ) ;
void i n s e r t a r ( i n t c l a v e ) ;
// implementar e l método s i g u i e n t e :
// s i l a s dos t a b l a s no t i e n e n mismo número de c u b e t a s d e v u e l v e 0
t a b l a H a s h ∗ u n i r ( const t a b l a H a s h ∗ o t r a ) const ;
};
Solución: Como las tres tablas tienen el mismo número de cubetas y la misma función de dispersión, podemos
unir los elementos cubeta por cubeta, sin necesidad de utilizar la función de dispersión. Para obtener una lista
ordenada con los elementos que aparecen en dos listas ordenadas podemos utilizar el algoritmo merge (del
mergesort) modificado para no duplicar los elementos repetidos. El coste de unir las dos listas es lineal con la
suma del tamaño de ambas listas, lo que es como mucho 2 veces el número de elementos de la lista resultante.
Un algoritmo que recorre las dos tablas y tiene un coste lineal con la suma de las longitudes de las listas de cada
cubeta tiene un coste lineal con el número de cubetas más el número de elementos de la tabla resultante:
t a b l a H a s h ∗ t a b l a H a s h : : u n i r ( const t a b l a H a s h ∗ o t r a ) const {
i f ( numcubetas ! = o t r a −>numcubetas ) return 0 ; // por s i no s e cumple
t a b l a H a s h ∗ r = new t a b l a H a s h ( numcubetas ) ; // creamos t a b l a a d e v o l v e r
f o r ( i n t i = 0 ; i < numcubetas ; i ++) // actuamos c u b e t a a c u b e t a
r−>t a b l a [ i ] = u n i r l i s t a s ( t a b l a [ i ] , o t r a −>t a b l a [ i ] ) ; // f u n c i ó n a u x i l i a r
return r ; // ya e s t á
}
nodo ∗ u n i r l i s t a s ( const nodo ∗ l 1 , const nodo ∗ l 2 ) { // f u n c i ó n a u x i l i a r
nodo ∗ c a b e z a =0 , ∗∗ a c o l a = & cabeza , ∗ nueva ; // a c o l a para i n s e r t a r a l f i n a l
while ( l 1 && l 2 ) { // como e l a l g o r i t m o merge
// l a l i n e a s i g u i e n t e a ñade nodo ” nueva ” a l f i n a l de l a l i s t a :
nueva = new nodo ; nueva−>s i g = 0 ; ∗ a c o l a = nueva ; a c o l a = &( nueva−>s i g ) ;
i f ( l 1 −>c l a v e < l 2 −>c l a v e ) { // copiamos e l menor y avanzamos :
nueva−>c l a v e = l 1 −>c l a v e ; l 1 = l 1 −>s i g ;
} e l s e { // e l menor o i g u a l e s l 2
nueva−>c l a v e = l 2 −>c l a v e ; // copiamos e l menor o i g u a l
i f ( l 1 −>c l a v e == l 2 −>c l a v e ) l 1 = l 1 −>s i g ; // s i i g u a l e s no d u p l i c a m o s
l 2 = l 2 −>s i g ; // l 2 s e avanza en t o d o c a s o
}
} // uno de l o s dos w h i l e s s i g u i e n t e s s e g u r o que no s e e j e c u t a :
while ( l 1 ) { // terminamos de a ñ a d i r e l e m e n t o s de l 1 , como merge
nueva = new nodo ; nueva−>s i g = 0 ; ∗ a c o l a = nueva ; a c o l a = &( nueva−>s i g ) ;
nueva−>c l a v e = l 1 −>c l a v e ; l 1 = l 1 −>s i g ;
}
while ( l 2 ) { // terminamos de a ñ a d i r e l e m e n t o s de l 2 , como merge
nueva = new nodo ; nueva−>s i g = 0 ; ∗ a c o l a = nueva ; a c o l a = &( nueva−>s i g ) ;
nueva−>c l a v e = l 2 −>c l a v e ; l 2 = l 2 −>s i g ;
}
return c a b e z a ; // ya e s t á
}
Pregunta 3 (2.5 puntos)
Dada la siguiente implementación de un max heap:
c l a s s max heap {
i n t ∗ v e c t o r ; // ı́ n d i c e s v á l i d o s d e s d e 1 h a s t a tamanyo
// e l e m e n t o s g u a r d a d o s en ı́ n d i c e s d e s d e 1 h a s t a ocupados
i n t tamanyo ; // d e l v e c t o r
i n t ocupados ; // número e l e m e n t o s que hay en e l heap
public :
max heap ( i n t tamanyo maximo ) ;
˜ max heap ( ) ;
void h e a p i f y ( i n t pos ) ;
void b u i l d h e a p ( ) ;
bool e x t r a e r m a x i m o ( i n t ∗ e l e m e n t o ) ;
void i n s e r t a r ( i n t e l e m e n t o ) ; // <− método a implementar
};
a) (2 puntos) Implementa de la forma más eficiente el método de inserción de modo que se doble el tamaño
del vector cuando se va a insertar un nuevo elemento y éste esté lleno.
Solución:
void max heap : : i n s e r t a r ( i n t e l e m e n t o ) {
i f ( ocupados == tamanyo ) { // en e s t e c a s o doblamos tamaño d e l v e c t o r
i n t ∗ nuevo = new i n t [ tamanyo ∗ 2 ] ; // creamos o t r o v e c t o r
nuevo −−; // para que l o s ı́ n d i c e s empiecen en 1
f o r ( i n t i =1; i<=tamanyo ; i ++) nuevo [ i ] = v e c t o r [ i ] ; // copiamos
vector
v e c t o r ++; // para que empiece en 0 , como en C/C++
delete [ ] v e c t o r ; // y p o d e r e l i m i n a r l o adecuadamente
v e c t o r = nuevo ; // asignamos nuevo v e c t o r a l a t r i b u t o v e c t o r
tamanyo ∗ = 2 ; // a c t u a l i z a m o s a t r i b u t o tamanyo
} // e l r e s t o d e l método e s como s i e m p r e :
ocupados++;
i n t p o s i c i o n = ocupados ;
while ( ( p o s i c i o n > 1) && ( e l e m e n t o > v e c t o r [ p o s i c i o n / 2 ] ) ) {
vector [ posicion ] = vector [ posicion / 2 ] ;
posicion = posicion /2;
}
vector [ p o s i c i o n ] = elemento ;
}
b) (0.5 puntos) Indica el coste del método implementado ¿serı́a necesario volver a realizar un buildheap?
Razona la respuesta.
Solución: El método de inserción tiene coste O(log n) cuando no se dobla el vector, O(n) cuando se dobla,
donde n es el número de elementos. Por tanto, en el peor caso es O(n). No hace falta utilizar buildheap
en ningún momento porque los elementos en el nuevo vector pueden aparecer en las mismas posiciones,
puesto que la relación entre padres e hijos no depende el tamaño máximo del vector.
Puedes utilizar atributos,métodos,etc. auxiliares si lo consideras conveniente.
Pregunta 4 (2.5 puntos)
Tenemos una lista de pares de amigos, y otra lista de pares de enemigos. Por simplicidad, cada individuo viene
identificado por un valor numérico desde 0 hasta el número de individuos menos 1.
Queremos implementar una función que compruebe si la famosa frase:
“Los amigos de mis amigos son mis amigos”
se cumple para las dos listas previamente proporcionadas.
Por ejemplo, si 0 y 1 son amigos, 1 y 2 son amigos pero 0 y 2 son enemigos, la frase no es cierta.
El prototipo de la función a implementar es:
bool amigosdemisamigos ( par ∗ amigos , i n t num pares amigos ,
par ∗ enemigos , i n t n u m p a r e s e n e m i g o s ) ;
donde se reciben los vectores de entrada y sus respectivos tamaños. Previamente hemos declarado:
struct par {
int a , b ;
};
Nota: En caso de utilizar alguna de las estructuras de datos vistas en clase, hay que indicar lo que hace pero
no es necesario implementarla.
Solución: Si los amigos de los amigos fuesen también amigos (¡vaya lı́o!), se darı́a la propiedad transitiva en la
relación “ser amigo de” y se podrı́a particionar el conjunto de individous en clases de equivalencia o grupos de
amigos entre sı́. Podemos suponer esto, formar las clases de equivalencia utilizando un mfset (utilizando merge
para cada par de amigos y ası́ constituir los grupos hipotéticos de amigos), posteriormente debemos comprobar si
algún par de enemigos están en la misma clase de equivalencia (utilizando find), lo que invalidarı́a la suposición
y podrı́amos devolver false, en caso de no invalidarse devolvemos true:
i n t max( i n t a , i n t b ) return ( a>b ) ? a : b ; // a u x i l i a r
i n t maxvector ( par ∗ v , i n t d ) { // a u x i l i a r , para o b t e n e r num i n d i v i d u o s
i n t i ,m=0;
f o r ( i =0; i <d ; i ++) m = max(m, max( v [ i ] . a , v [ i ] . b ) ) ;
return m;
}
bool amigosdemisamigos ( par ∗ amigos ,
int pares de amigos ,
par ∗ enemigos , i n t p a r e s d e e n e m i g o s ) {
i n t i , n=max( maxvector ( amigos ,
pares de amigos ) ,
maxvector ( enemigos , p a r e s d e e n e m i g o s ) ) +1;
m f s e t m( n ) ; // c j t . de i n d i v i d u o s , de 0 a n−1
f o r ( i = 0 ; i <p a r e s d e a m i g o s ; i ++) // para cada r e l a c i ó n de a m i s t a d
m. merge ( amigos [ i ] . a , amigos [ i ] . b ) ; // juntamos l o s g r u p o s de amigos
f o r ( i = 0 ; i <p a r e s d e e n e m i g o s ; i ++) // comprobamos s i a l g ú n enemigo
i f (m. f i n d ( enemigos [ i ] . a ) == m. f i n d ( enemigos [ i ] . b ) ) // e s t á en e l
return f a l s e ; // mismo grupo de amigos , de s e r a sı́ no s e cumple
return true ; // en o t r o c a s o sı́ s e puede a f i r m a r
}
Hemos utilizado la clase mfset donde el constructor recibe n que es el número de elementos del conjunto, los
elementos del conjunto de identifican como 0, 1, . . . , n − 1. El método merge une dos clases de equivalencia y el
método f ind devuelve un representante que identifica la clase de equivalencia de un elemento dado.
Descargar