Está en la página 1de 6

Estrategias de Programación y Estructuras de Datos.

Junio 2019, 1ª Semana


P1. Pregunta sobre la práctica. Se desea añadir una funcionalidad a la práctica
consistente en la inclusión de tres métodos que permitan comparar operandos
(recordemos que son enteros con signo):
• boolean equal(Operand n) comprueba si el operando llamante es igual al
operando parámetro n.
• boolean greater(Operand n) comprueba si el operando llamante es mayor
que el operando parámetro n.
• boolean lower(Operand n) comprueba si el operando llamante es menor que
el operando parámetro n.
a) (0.5 Puntos) ¿En qué clase deberían implementarse estos métodos? Justifique su
respuesta.
Si nos fijamos bien, la respuesta a esta pregunta está en el propio enunciado, ya
que se está indicando que el objeto llamante ha de ser un operando. Por lo tanto,
la única posibilidad es que estos métodos se implementen en la clase Operand.
Si los implementásemos en las clases ValueSeq o ValueInt, los métodos no
tendrían información sobre el signo del operando, por lo que ambas clases
quedan descartadas.
b) (1.5 Puntos) Describa el funcionamiento del código de los tres métodos
indicados (no es necesario programar). Para ello puede utilizar cualesquiera de
los métodos que ya existen en la práctica.
Hay muchas formas de implementar estos métodos y aquí vamos a indicar dos de
ellas.
La primera aproximación que explicaremos será utilizar el método greater de
las clases que extienden Value, que nos permite comparar dos valores (sin
signo) y nos dice si el llamante es mayor que el parámetro. Gracias a este método
comenzaremos programando el método greater en la clase Operand.
Para ello, el primer paso será comparar los signos del operando llamante y del
parámetro. Si el signo del llamante es mayor, entonces devolvemos directamente
verdadero. Si fuese menor, devolveríamos falso.
Si los signos son iguales, deberemos discernir si son positivos, en cuyo caso
comprobaremos si el valor del operando llamante es mayor que el valor del
operando parámetro, devolviendo el resultado de esta comparación. Si los signos
fueran negativos, deberíamos hacerlo al contrario: comprobar si el valor del
operando parámetro es mayor que el del operando llamante. Si ambos signos son
0, significará que ambos operandos son 0 y se debería devolver falso.
Una vez implementado greater en Operand, la implementación de lower es
directa, sin más que llamar a greater invirtiendo los papeles de ambos
operandos (el operando llamante pasaría a ser el parámetro y viceversa).
Por último, para implementar equal podríamos emplear los dos métodos ya
implementados aprovechando que dos valores son iguales si uno no es mayor ni
menor que el otro.

No olvide consignar su nombre y DNI en todas las hojas que entregue


Estrategias de Programación y Estructuras de Datos. Junio 2019, 1ª Semana
La segunda aproximación que vamos a explicar emplea el método sub de la
propia clase Operand. La idea, para los tres métodos, es restar al operando
llamante el operando parámetro. Después, dependiendo de cada método, se
tendría que comprobar el signo del resultado:
• Para greater, el signo ha de ser 1 (el llamante es mayor que el parámetro,
luego la diferencia es positiva).
• Para equal, el signo ha de ser 0 (la única posibilidad es que el llamante
sea exactamente igual al parámetro, con lo que la diferencia sería 0).
• Para lower, el signo ha de ser -1 (con lo que el llamante es menor que el
parámetro y su diferencia sería negativa).
1. Sea el siguiente árbol binario:

a) (1 Punto) Indique cuál sería la secuencia resultante de generar el iterador de este


árbol para los siguientes recorridos:
PREORDER, POSTORDER, BREADTH, INORDER

• El recorrido en preorden (PREORDER) visita primero la raíz, luego el hijo


izquierdo y por último el derecho: 1, 2, 4, 5, 7, 3, 6, 8.
• El recorrido en postorden (POSTORDER) visita primero el hijo izquierdo,
luego el derecho y por último la raíz: 4, 7, 5, 2, 8, 6, 3, 1.
• El recorrido en anchura (BREADTH) visita los nodos por niveles, siempre de
izquierda a derecha: 1, 2, 3, 4, 5, 6, 7, 8.
• El recorrido en inorden (INORDER) visita primero el hijo izquierdo, luego
la raíz y por último el hijo derecho: 4, 2, 7, 5, 1, 3, 8, 6.
b) (1 Punto) Se quiere enriquecer los iteradores de los árboles binarios añadiendo
cuatro nuevos tipos de recorridos:
RevPREORDER, RevPOSTORDER, RevBREADTH, RevINORDER
que generarán la secuencia inversa a los recorridos correspondientes indicados
en el apartado anterior (por ejemplo, RevPREORDER ha de generar la secuencia
inversa al recorrido PREORDER). Describa cómo realizaría estos nuevos
recorridos sin necesidad de realizar el recorrido ya existente e invertir la
secuencia obtenida. Justifique su respuesta.

No olvide consignar su nombre y DNI en todas las hojas que entregue


Estrategias de Programación y Estructuras de Datos. Junio 2019, 1ª Semana
Una forma sencilla de obtener las secuencias inversas es, precisamente, la que no
se permite en el enunciado: obtener la secuencia directa y luego invertirla. Esto,
aparte de indicarse explícitamente que no se debe hacer, supone recorrer dos
veces todo el árbol: una para obtener la secuencia y otra para invertirla.
Una posible solución consiste en visitar los nodos al revés que en los recorridos
directos:
• RevPREORDER: primero visitar el hijo derecho, luego el izquierdo y
luego la raíz.
• RevPOSTORDER: primero visitar la raíz, luego el hijo derecho y luego el
izquierdo.
• RevINORDER: primero visitar el hijo derecho, luego la raíz y luego el
hijo izquierdo.
• RevBREADTH: habría que visitar los nodos por niveles, desde el último
hasta el la raíz y de derecha a izquierda… lo que supone primero encontrar
el último nivel y visitarlo de derecha a izquierda, para luego ir subiendo.
Este proceso es muy complejo de implementar.
Una mejor solución consiste en darse cuenta de que en los recorridos directos los
nodos visitados se meten en una cola, de forma que se crea la secuencia correcta
del recorrido deseado. Si en lugar de emplear una cola, se utiliza una pila (o
incluso una lista en la que se inserte siempre en la primera posición), se podrían
utilizar los mismos algoritmos empleados para los recorridos directos, pero nos
proporcionarían los recorridos inversos pedidos.
2. Se desea un método concatStacks que nos permita concatenar dos pilas (de
cualquier tipo de elementos) en una única pila, de forma que la secuencia formada
por la pila resultado sea la concatenación de las secuencias formadas por cada una
de las pilas. Por ejemplo:
Pila a → (top) 1, 4, 5, 8
Pila b → (top) 3, 2, 6, 9, 7
concatStacks(a,b) → (top) 1, 4, 5, 8, 3, 2, 6, 9, 7
a) (1 Punto) Programe un método:
StackIF<E> concatStacks(StackIF<E> a, StackIF<E> b)
que realice la tarea descrita de forma externa a la clase Stack.
La idea consiste en darse cuenta de que primero habría que invertir la pila b,
luego la a y volcarlas (en ese orden: primero la b y luego la a) en una nueva pila
que contendrá el resultado.
Sin embargo, dado que no se nos está pidiendo que las dos pilas conserven su
valor, por lo cual podemos emplearlas para resolver el problema. De esta manera
podemos preguntarnos: ¿para qué vamos a modificar la pila b? Si nos limitamos
a invertir la pila a y luego volcarla sobre b ya tendremos el resultado buscado:

No olvide consignar su nombre y DNI en todas las hojas que entregue


Estrategias de Programación y Estructuras de Datos. Junio 2019, 1ª Semana
public <E> StackIF<E> concatStacks(StackIF<E> a, StackIF<E> b) {
StackIF<E> aux = new Stack<E>();
while ( !a.isEmpty() ) {
aux.push(a.getTop());
a.pop();
}
while ( !aux.isEmpty() ) {
b.push(aux.getTop());
aux.pop();
}
return b
}

Se deja como ejercicio la modificación del método anterior para que, además, las
pilas a y b conserven su valor al finalizar.
b) (1 Punto) Programe un método
void concatStack(StackIF<E> b)
dentro de la clase Stack, que modifique la pila llamante al concatenarle la pila
resultado. Deberá, para ello, usarse la estructura interna de las pilas.
Seguiremos la misma estrategia que en el apartado anterior, pero al revés: ahora
copiaremos el contenido de b en la pila llamante. Como, además, estamos dentro
de la clase Stack, podemos (y debemos, según el enunciado) utilizar la estructura
interna de las pilas: la secuencia de nodos en la que almacenamos los valores.
Para ser estrictos, dado que se nos pide que b sea un objeto de una clase que
implemente el interfaz StackIF<E>, la solución pasa por localizar el último
nodo de la secuencia donde almacenamos los valores de la pila llamante
(siempre que no sea vacía), para ir colocando a continuación (como si de una
cola se tratase) nuevos nodos con los valores contenidos en b, incrementando
adecuadamente el tamaño de la pila.
public void concatStackIF(StackIF<E> b) {
if (!b.isEmpty()) {
NodeSequence lastNode;
if (!this.isEmpty()) {
lastNode = this.getNode(this.size());
} else {
lastNode = new NodeSequence(b.getTop());
this.firstNode = lastNode;
b.pop();
}
while (!b.isEmpty()) {
NodeSequence newNode = new NodeSequence(b.getTop());
lastNode.setNext(newNode);
lastNode = newNode;
b.pop();
this.size++;
}
}
}

No olvide consignar su nombre y DNI en todas las hojas que entregue


Estrategias de Programación y Estructuras de Datos. Junio 2019, 1ª Semana
Otra solución sería forzar a que b también sea de la clase Stack, no sólo de una
clase que implemente StackIF. En esta situación se debería recorrer
completamente la secuencia de nodos de la pila a (si no es vacía) y hacer que el
último nodo apunte al primero de la pila b, de esa forma tendremos el resultado
buscado en la pila llamante de forma directa. Finalmente, se actualiza el tamaño
de la pila llamante.
public void concatStack(Stack<E> b) {
if ( !b.isEmpty()) {
if (!this.isEmpty()) {
NodeSequence lastNode = this.getNode(this.size());
lastNode.setNext(b.firstNode);
} else {
this.firstNode = b.firstNode;
}
this.size = this.size + b.size;
}
}
c) (0.5 Puntos) Compare la eficiencia temporal real (no asintótica) de ambas
implementaciones.
En primer lugar vamos a definir que la longitud de la pila a es de n elementos y
la de la pila b es de m elementos.
En el caso del apartado a (de forma externa a la clase Stack), vemos que hay que
invertir la pila a y volver a volcarla sobre b. Esto supone recorrer dos veces la
pila a, con lo que tendríamos un coste aproximado de 2n.
En el caso del apartado b, la primera de las soluciones implica recorrer toda la
secuencia de la pila a (coste n) y luego la pila b (coste m), así pues tendríamos
un coste aproximado de n+m. Si la pila b es más larga que a, este coste sería
peor que el de la implementación externa. Y si es más pequeña, sería menor.
La segunda solución del apartado b es mucho más eficiente que las otras dos, ya
que sólo tenemos que recorrer la secuencia de la pila a una única vez, con lo que
el coste sería aproximadamente de n.
3. Consideremos una clase Label que va a representar una etiqueta con un nombre
(String) y un conjunto de pares atributo-valor (ambos String también). Como
ejemplo de aplicación se puede pensar en un lenguaje de marcado como XML. Se
dispone de los siguientes métodos:
• String name(): devuelve el nombre de la etiqueta.
• String value(String attrib): devuelve el valor del atributo attrib. Si no
existiese, devolvería la cadena vacía. Por ejemplo, para una etiqueta que tenga un
atributo “link” cuyo valor sea “/img/mail.png”, la llamada value(“link”)
devolvería la cadena “/img/mail.png”.
Basándonos en esta clase, queremos crear una clase LTree que nos permita
manipular un árbol de etiquetas. La clase LTree extenderá GTree<Label>, con lo
que todos los métodos de GTree están disponibles en un LTree. Describa (no es
necesario programar) el funcionamiento de los siguientes métodos:

No olvide consignar su nombre y DNI en todas las hojas que entregue


Estrategias de Programación y Estructuras de Datos. Junio 2019, 1ª Semana
a) (1 Punto) Un método ListIF<LTree> getChildrenByName(String l), que
devuelva la lista de hijos directos del objeto LTree llamante, que almacenen una
etiqueta cuyo nombre sea l.
Dado que un LTree es, a su vez, un GTree, podemos hacer uso de los métodos
de esta clase. Así pues, el primer paso consistiría en obtener la lista de todos los
hijos del LTree llamante, la cual filtraríamos eliminando todos aquellos
elementos que almacenen una etiqueta cuyo nombre (al cual podemos acceder
mediante el método name()) sea igual al que se recibe como parámetro.
b) (1.5 Puntos) Un método ListIF<String> getLinks(), que devuelva el
contenido del atributo “link” (en caso de existir) de todas las etiquetas cuyo
nombre sea “e” que estén anidadas (a cualquier profundidad) dentro del objeto
LTree llamante.

Dado que nos están indicando que buscamos todas las etiquetas anidadas a
cualquier profundidad del LTree llamante, deberemos hacer un recorrido del
LTree. ¿Podemos utilizar un iterador? En esta ocasión si, ya que estamos
interesados en el contenido del LTree y no necesitamos su estructura.
¿Qué recorrido nos interesa? Pues, realmente, cualquiera. No se indica que haya
que guardar ningún orden específico, por lo que elegir un recorrido u otro no
afectará a la solución.
Así pues, podemos generar un iterador (con cualquier recorrido) del LTree y
luego recorrerlo para ir construyendo la lista resultado. Para ello en cada etiqueta
comprobaremos si su nombre es “e” o no. Si no lo es, pasamos a la siguiente,
pero si lo es, comprobamos si tiene un atributo “ link” (con una llamada al
método value(“link”)). En caso de obtener una cadena no vacía, insertaremos
en la lista resultado un nuevo elemento con el valor de dicho atributo.
c) (1 Punto) Un método ListIF<String> getAttrs(…) que generalice el método
getLinks() de forma que se pueda especificar el nombre de las etiquetas y el
atributo buscado. Indique cuál sería su lista de parámetros y escriba la llamada a
getAttrs que haría el mismo trabajo que getLinks().

La generalización es muy sencilla, ya que el funcionamiento será idéntico al


descrito en el apartado b, salvo que ahora deberemos recibir por parámetro el
nombre de la etiqueta buscada y el del atributo en el que estamos interesados. Así
pues, la cabecera del método debería ser:
ListIF<String> getAttrs(String label, String attrib)

mientras que en lugar de comprobar si el nombre es “ e”, comprobaremos si


coincide con el String recibido en label y buscaremos el atributo cuyo nombre
sea el recibido en attrib. Por lo que la llamada:
getAttrs(“e”,“link”)

haría el mismo trabajo que getLinks().

No olvide consignar su nombre y DNI en todas las hojas que entregue

También podría gustarte