Escolar Documentos
Profissional Documentos
Cultura Documentos
RESUELTOS
PROGRAMACIÓN
III
Recopilación de Exámenes de Programación III
Solución:
• Se trata de un problema de optimización: No solo hay que encontrar un recubrimiento, sino que
éste ha de ser de tamaño mínimo.
• De entre un conjunto de vértices (candidatos) hay que seleccionar un subconjunto que será la
solución. Solo hay que encontrar la función de selección adecuada (si existe) para resolver el
problema mediante un algoritmo voraz.
El esquema de divide y vencerás es descartable, pues no hay forma obvia de dividir el problema en
subproblemas idénticos cuyas soluciones puedan combinarse en una solución global. El esquema de
vuelta atrás es un esquema muy general y casi siempre muy costoso que no debemos usar si podemos
dar un algoritmo voraz que resuelva el problema.
Esta notación algorítmica puede escribirse así (libro de Brassard página 214):
fun voraz (C: conjunto) dev (S: conjunto)
S ← ∅
mientras ¬ solucion (S) ∧ C ≠ ∅ hacer
x ← elemento que maximiza objetivo (x)
C ←C \ {x}
si completable (S ∪ {x})
entonces S ← S ∪ {x}
fsi
dev S
ffun
donde:
La forma más “intuitiva” de garantizar que el recubrimiento sea mínimo es tomar vértices de los que
salgan muchas aristas, esto es, elegir vértices con mayor grado.
La función de selección debe escoger el candidato con más vértices de los que aun están en el conjunto
de candidatos. Para ello, cada vez que seleccionemos un vértice tendremos que disminuir en uno el
grado de cada uno de los vértices candidatos con el. Hay que seguir los siguientes pasos:
1. Elegir el vértice de mayor grado.
2. Recorrer su lista asociada (aquellos vértices con los que está conectado) y restar 1 al grado de
cada uno de ellos en el campo correspondiente en el vector de vértices.
De esta forma, cuando el grado de todos los candidatos sea cero todas las aristas del grafo tocan al
conjunto de selección y será, por tanto, un recubrimiento.
Según este criterio, las funciones del esquema principal tendrán el siguiente significado:
Estructuras de datos
vertice = tupla
indice: entero // Posición en el vector
grado: entero // Grado del vértice
adyacentes: apuntador a nodo_adyacente
nodo_adyacente = tupla
adyacente: entero
siguiente: apuntador a nodo_adyacente
Solución: El conjunto S será una solución cuando el grado de todos los elementos que restan en C será
cero. Será en pseudocódigo así:
fun solución (C: Conjunto de vértices) dev (b:booleano)
b ← cierto
para c en C hacer
si G[i].grado ≠ 0 hacer b ← (G[i].grado =0)
fpara
dev b
ffun
Tal como comentábamos anteriormente, las soluciones aportadas en estos ejercicios hay algunas que
no cuadran, debido a sintaxis, debido a que no es “entendible”, etc etc. En este caso, es una de ellas, ya
que dentro del bucle “para” hay un bucle “si” que no comprendo muy bien que hace, por lo que
calculamos la variable b (que es un booleano) para averiguar si es solución o no. Cabe destacar que la
solución no es mía personal, la aporto otro compañero en los cursos virtuales, por ello gracias ;)
Por último, la función disminuir-grado resta 1 al grado de todos los elementos conectados con el
vértice elegido:
El tamaño del problema viene dado por el número de vértices del grafo. El número de veces que se
ejecuta el bucle voraz es, en el peor de los casos, n. Dentro del bucle se realizan dos operaciones:
encontrar el vértice de mayor grado, que tiene un coste lineal y disminuir en uno el grado de los
demás, que tiene también coste lineal.
De modo que el coste del bucle es Ο(n) y el coste del algoritmo es Ο(n2)
Escuela Universitaria de Informatica de la UNED
Ingeniera Tecnica de Sistemas e Ingeniera Tecnica de Gestio n
Programacion III - Convocatoria de Febrero
Examen segunda semana - Curso 1995-96
fn;2
CA = B@ fn
fn;1
CA
a b c fn;1 fn
Problema 1
1
Descripcion del esquema usado e identicacion con el problema.
Nos ahorramos la explicacion, que podeis encontrar en el Brassard/Bratley, pag. 89.
Estructuras de datos.
El conjunto de ciudades y carreteras viene representado por un grafo no orientado con
pesos. Podemos implementarlo como una matriz de adyacencia, como una lista de listas
de adyacencia, etc.
Ademas necesitaremos otra matriz que acumule las distancias mnimas entre ciudades y
que sirva como resultado.
Como este problema esta en relacion muy estrecha con la practica del curso 96-97, no ofrecemos
todava la solucion.
Problema 3
3
Para resolver el apartado a) de forma eciente basta con dividir los f c alumnos en c
subconjuntos de f elementos escogidos al azar, y a continuacion debe ordenarse cada uno
de esos subconjuntos. Cada uno de ellos sera una columna en la clase. Como algoritmo
de ordenacion puede escogerse cualquiera de los estudiados nosotros utilizaremos el algo-
ritmo divide y venceras de fusion, por ser mas eciente asintoticamente en el caso peor (es
O(n log n)).
Al colocar una segunda pizarra adyacente a la primera, los alumnos de cada la deben
estar, a su vez, ordenados entre s. Para que esten ordenadas las columnas y las las, es
necesario ordenar a todos los alumnos de menor a mayor, y colocarlos en la clase de forma
que el mas bajito ocupe el pupitre que esta en la interseccion de las dos pizarras, y el mas alto
en el vertice opuesto de la clase. Por lo tanto, para obtener la disposicion nal de los alumnos
en el apartado b) debe hacerse una ordenacion de f c elementos. Pero si aprovechamos la
disposicion anterior no es necesario, esta vez, aplicar ningun algoritmo de ordenacion: basta
con realizar una fusion de los c subconjuntos ya ordenados (equivaldra al ultimo paso de un
algoritmo de ordenacion por fusion en el que el factor de division fuera c). As, la ordenacion
nal puede obtenerse en tiempo lineal.
Estructuras de datos.
La unica estructura de datos que necesitamos es una matriz de enteros de tama~no c f que
almacene las alturas de los alumnos. Tambien podemos utilizar un vector de tama~no c f ,
sabiendo que cada f elementos representan una columna.
6
Estudio del coste.
apartado a
La ordenacion por fusion tiene un coste que cumple:
T (n) = 2T (n=2) + cte n
ya que el algoritmo de fusion tiene un coste O(n) (consta de dos bucles consecutivos). De
esa igualdad se obtiene un coste O(n log n. Como se realizan c ordenaciones de f elementos
cada una, el coste total es O(cf log f ). Mediante una ordenacion total habramos resuelto
tambien el problema, pero con un coste O(cf log cf ) (ya que el tama~no del problema sera
c f ).
apartado b
Se resuelve mediante una llamada al algoritmo de fusion, que tiene un coste lineal como
el tama~no del problema es c f , el coste es O(c f ). El coste es mucho menor que en el caso
en que no aprovecharamos la ordenacion parcial que se obtiene en el apartado a.
Problema 4
Se planea conectar entre s todos los pueblos de una cierta region mediante carreteras
que sustituyan los antiguos caminos vecinales. Se dispone de un estudio que enumera
todas las posibles carreteras que podran construirse y cual sera el coste de construir
cada una de ellas. Encontrar un algoritmo que permita seleccionar, de entre todas
las carreteras posibles, un subconjunto que conecte todos los pueblos de la region
con un coste global mnimo.
7
Para resolverlo podemos usar cualquiera de los dos algoritmos voraces estudiados que
resuelven este problema: el de Kruskal o el de Prim.
<Cuidado! No debe confundirse este problema con el de encontrar los caminos mnimos entre un
vertice y el resto. Ver problema 1
8
Escuela Universitaria de Informatica de la UNED
Ingeniera Tecnica de Sistemas e Ingeniera Tecnica de Gestio n
Programacion III - Convocatoria de Septiembre 96
Examen de reserva
Problema 2 (5 puntos). Se planea conectar entre s todos los pueblos de una
cierta region mediante carreteras que sustituyan los antiguos caminos vecinales.
Se dispone de un estudio que enumera todas las posibles carreteras que podran
construirse y cual sera el coste de construir cada una de ellas. Encontrar un
algoritmo que permita seleccionar, de entre todas las carreteras posibles, un sub-
conjunto que conecte todos los pueblos de la region con un coste global mnimo.
denados cada una, se pretende mezclarlas a pares hasta lograr una unica cinta
ordenada. La secuencia en la que se realiza la mezcla determinara la eciencia
del proceso. Dise~nar un algoritmo que busque la solucion optima minimizando el
numero de movimientos.
Por ejemplo: 3 cintas: A con 30 registros, B con 20 y C con 10
1. Mezclamos A con B (50 movimientos) y el resultado con C (60 movimien-
tos), con lo que realizamos en total 110 movimientos
2. Mezclamos C con B (30 Movimientos) y el resultado con A (60). Total =
90 movimientos
>Hay alguna forma mas eciente de ordenar el contenido de las cintas?
Problema 2 (5 puntos). El juego del 31 utiliza las cartas de la baraja espa~nola:
1,2,3,4,5,6,7,10(sota),11(caballo) y 12(rey) con los 4 palos: oros, copas, espadas
y bastos. Dise~nar un algoritmo que calcule todas las posibles formas de obtener
31 utilizando a lo sumo 4 cartas y 2 palos distintos en cada combinacion.
Problema 1 (5 puntos). Dado un conjunto de n cintas no vacías con ni registros ordenados cada uno,
se pretende mezclarlos a pares hasta lograr una única cinta ordenada. La secuencia en la que se realiza
la mezcla determinara la eficiencia del proceso. Diséñese un algoritmo que busque la solución óptima
minimizando el número de movimientos.
1. Mezclamos A con B (50 movimientos) y el resultado con C (60 movimientos), con la que realiza en
total 110 movimientos.
2. Mezclamos C con B (30 movimientos) y el resultado con A (60 movimientos), con la que realiza en
total 90 movimientos.
Solución:
• Por un lado, se tienen un conjunto de candidatos (las cintas) que vamos eligiendo uno a uno hasta
completar determinada tarea.
• Por otro lado, el orden de elección de dichos elementos determina la optimalidad de la solución,
de manera que para alcanzar una solución óptima es preciso seleccionar adecuadamente al
candidato mediante un criterio determinado. Una vez escogido, habremos de demostrar que nos
lleva a una solución óptima.
El criterio de elección de las cintas para alcanzar una solución óptima será el de elegir en cada
momento aquella con menor número de registros.
Demostración de optimalidad:
Esta notación algorítmica puede escribirse así (libro de Brassard página 214):
fun voraz (C: conjunto) dev (S: conjunto)
S ← ∅
mientras ¬ solucion (S) ∧ C ≠ ∅ hacer
x ← elemento que maximiza objetivo (x)
C ←C \ {x}
si completable (S ∪ {x})
entonces S ← S ∪ {x}
fsi
dev S
ffun
Hemos de particularizar las siguientes funciones:
Estructuras de datos
Se utilizarán vectores de n valores naturales para representar los conjuntos. Por ejemplo, para el
conjunto C se define la variable c como vector de naturales, siendo c[i] = ni la expresión de que la
cinta i consta de ni registros. El conjunto S puede representarse de manera análoga. Para representar la
ausencia de un elemento puede usarse cualquier marcador (por ejemplo, el valor ∅).
Esta solución está modificada respecto de la solución aportada en el libro de problemas. Se han
añadido una línea (la de i←i+1) y se ha modificado la línea c[i]←0. Con esto trato de decir, que no es
seguro que esté correcta la respuesta, sólo que pienso que había algunas erratas.
La única función (de selección) por desarrollar es aquella que obtiene en cada momento la cinta con
menor número de registros de entre las cintas no usadas todavía y almacenadas en el vector de cintas.
La función devuelve la cinta, pero no la elimina del conjunto de candidatos. Los argumentos sin c,
vector de cintas y cinta que es un vector de ni registros.
Demostración de optimalidad
La demostración corresponde con la de minimización del tiempo en el sistema, dada ya en ejercicios
antes, por lo que evitamos escribirla de nuevo.
La función objetivo (para nosotros seleccionar) tiene coste de Ο(n) y el bucle principal (“mientras”) se
repite n veces, por lo que el coste es Ο(n2).
Escuela Universitaria de Informatica de la UNED
Ingeniera Tecnica de Sistemas e Ingeniera Tecnica de Gestio n
Programacion III - Convocatoria de Febrero de 1997
Examen segunda semana
Queremos grabar
P . canciones de duraciones
n t1 : : : tn en una cinta de audio de
duracion T < =1
n
i
ti
Dise~nar un algoritmo que distribuya un cliente a cada motorista de forma que se minimice el
coste total (en tiempo) de atender a los tres clientes.
Problema 2 (5 puntos). Sea un juego de tablero para dos personas, en el que se turnan para
mover las piezas del tablero segun unas reglas determinadas. Daremos por conocidos:
Una estructura de datos jugada que nos da dos tipos de informacion: en un registro tablero,
por un lado, la situacion de las chas en el tablero. Y en un registro turno, por otro lado,
quien debe mover a continuacion (o, en caso de ser una posicion nal, quien es el ganador),
con la siguiente convencion: 1 signica que le toca mover al jugador que comenzo. 2, al
segundo jugador. 3, que la partida acabo con triunfo del primer jugador, y 4, que la partida
acabo con triunfo del segundo jugador.
Una funcion
Problema 2 (5 puntos). En la compleja red de metro de Japon, la cantidad que se paga por
un billete es proporcional a la distancia que se recorre. Por tanto, es necesario instalar en cada
estacion un panel informativo que informe del precio del billete a cualquier otra estacion de la red.
Describir un algoritmo que deduzca la informacion de todos esos paneles, basando el calculo en la
suposicion de que el viajero se trasladara de una estacion a otra por el camino mas corto.
X X X X X X
X X X X X X
X X X X X X X X X X X X X X
X X X O X X X ===> X X X X X X X
X X X X X X X X X X O X X X
X X X X O X
X X X X X X
El tablero de juego se puede representar mediante una matriz 7*7 con tres valores posibles:
fun esValido(D,i,j,v)
// Ya se ha comprobado que D[i,j]=1
destX ← i+2*dX[v] ; destY ← j+2*dY[v]
comX ← i+dX[v] ; comY ← j+dY[v]
si 1 ≤ destX ≤ 7 ^ 1 ≤ destY ≤ 7^ D[destX,destY]=0 ^ D[comX,comY]=1
entonces devolver cierto
si no devolver falso
fin fun
proc hacerMovimiento(D,mov)
D[mov.origen.x,mov.origen.y] ← 0
D[mov.destino.x,mov.destino.y] ← 1
D[mov.comido.x,mov.comido.y] ← 0
fin proa
proc deshacerMovimiento(D,sol[etapa])
D[mov.origen.x,mov.origen.y] ← 1
D[mov.destino.x,mov.destino.y] ← 0
D[mov.comido.x,mov.comido.y] ← 1
fin proa
Problema 2 (5 Puntos) Se tiene un mecano de 8 piezas. Las piezas se acoplan entre s mediante
tornillos, formando distintos juguetes dependiendo de como se combinen. Un juguete completo es
aquel formado por las 8 piezas. El gasto de tornillos de cada acoplamiento es el indicado en la
siguiente tabla: (Un ; indica que las piezas no encajan)
p1 p2 p3 p4 p5 p6 p7 p8
p1 - 7 12 - 4 - - -
p2 7 - 2 6 1 - 1 -
p3 12 2 - 4 - 10 - 3
p4 - 6 4 - - 3 2 -
p5 4 1 - - - 20 10 -
p6 - - 10 3 20 - 5 -
p7 - 1 - 2 10 5 - -
p8 - - 3 - - - - -
Se pide construir un algoritmo que calcule la combinacion de piezas que forman un juguete
completo minimizando el gasto de tornillos.
Problema 2 (5 puntos). Se tiene un mecano de 8 piezas. Las piezas se acoplan entre sí mediante
tornillos, formando distintos juguetes dependiendo de cómo se combinen. Un juguete completo es
aquel formado por las 8 piezas. El gasto de tornillos de cada acoplamiento es el indicado en la
siguiente tabla: (Un - indica que las piezas no encajan)
Se pide construir un algoritmo que calcule la combinación de piezas que forman un juguete completo
minimizando el gasto de tornillos.
Se trata de un problema de optimización con restricciones. Por tanto, podría ser un esquema voraz o un
esquema de ramificación y poda. Sin embargo descartamos el esquema voraz porque no es posible
encontrar una función de selección y de factibilidad tales que una vez aceptado un candidato se
garantice que se va alcanzar la solución óptima. Se trata, por tanto, de un algoritmo de ramificación y
poda.
nodo=tupla
asignaciones: vector[1..N];
último_asignado: cardinal;
filas_no_asignadas: lista de cardinal;
coste: cardinal;
Las funciones generales del esquema general que hay que instanciar son:
1. a. solución(nodo): si se han realizado N asignaciones (último_asignado==N)
2. b. acotar(nodo,costes): nodo.coste + “mínimo coste de las columnas no asignadas”
3. c. compleciones(nodo,costes): posibilidades para la siguiente asignación (valores posibles para
asignaciones[último_asignado+1])
4.
Función asignación(costes[1..N,1..N]) dev solución[1..N]
Montículo:=montículoVacío();
nodo.último_asignado=0;
nodo.coste=0;
cota:=acotar(nodo,costes);
poner((cota,nodo),Montículo);
mientras no vacío(Montículo) hacer
(cota,nodo):=quitarPrimero(Montículo);
si nodo.último_asignado==N entonces
devolver nodo.asignaciones;
si no para cada hijo en compleciones(nodo,costes) hacer
cota:=acotar(hijo,costes);
poner((cota,hijo),Montículo);
fsi;
fmientras
devolver ∅;
Problema 1 (5 puntos). La nave Mir tiene que reensamblar sus paneles modulares debido a una
sobrecarga. Hay 6 paneles que se ensamblan unos con otros formando una estructura única y de la
combinación de ensamblaje depende el gasto de energía. La tabla adjunta muestra el coste en amperios
de cada unión:
P1 P2 P3 P4 P5 P6
P1 5 9 4 2 1 1
P2 6 0 1 1 3 5
P3 1 9 5 5 2 5
P4 1 4 2 3 5 6
P5 3 6 7 7 1 3
P6 1 3 5 6 2 8
Se pide diseñar un algoritmo que forme una estructura con todos los módulos minimizando el coste
total tanto de uniones como del coste de las mismas.
Problema 2 (5 puntos). Dado un mapa político de países, diseñar un algoritmo que coloree con 3
colores los países de manera que 2 países fronterizos no tengan el mismo color.
¿Tiene este problema solución en todos los casos? Pon ejemplos si los hay.
Problema 1 (5 puntos). La nave Mir tiene que reensamblar sus paneles modulares debido a una
sobrecarga. Hay 6 paneles que se ensamblan unos con otros formando una estructura única y de la
combinación de ensamblaje depende el gasto de energía. La tabla adjunta muestra el coste en amperios
de cada unión:
P1 P2 P3 P4 P5 P6
P1 5 9 4 2 1 1
P2 6 0 1 1 3 5
P3 1 9 5 5 2 5
P4 1 4 2 3 5 6
P5 3 6 7 7 1 3
P6 1 3 5 6 2 8
Se pide diseñar un algoritmo que forme una estructura con todos los módulos minimizando el coste
total tanto de uniones como del coste de las mismas.
Se trata de un problema de optimización con restricciones. Por tanto, podría ser un esquema voraz o un
esquema de ramificación y poda. Sin embargo descartamos el esquema voraz porque no es posible
encontrar una función de selección y de factibilidad tales que una vez aceptado un candidato se
garantice que se va alcanzar la solución óptima. Se trata, por tanto, de un algoritmo de ramificación y
poda.
nodo=tupla
asignaciones: vector[1..N];
último_asignado: cardinal;
filas_no_asignadas: lista de cardinal;
coste: cardinal;
Las funciones generales del esquema general que hay que instanciar son:
1. a. solución(nodo): si se han realizado N asignaciones (último_asignado==N)
2. b. acotar(nodo,costes): nodo.coste + “mínimo coste de las columnas no asignadas”
3. c. compleciones(nodo,costes): posibilidades para la siguiente asignación (valores posibles para
asignaciones[último_asignado+1])
4.
Función asignación(costes[1..N,1..N]) dev solución[1..N]
Montículo:=montículoVacío();
nodo.último_asignado=0;
nodo.coste=0;
cota:=acotar(nodo,costes);
poner((cota,nodo),Montículo);
mientras no vacío(Montículo) hacer
(cota,nodo):=quitarPrimero(Montículo);
si nodo.último_asignado==N entonces
devolver nodo.asignaciones;
si no para cada hijo en compleciones(nodo,costes) hacer
cota:=acotar(hijo,costes);
poner((cota,hijo),Montículo);
fsi;
fmientras
devolver ∅;
Coste:
En este caso únicamente podemos hallar una cota superior del coste del algoritmo por descripción del
espacio de búsqueda. En el caso peor se generan (k-1) hijos por cada nodo del nivel k, habiendo n. Por
tanto, el espacio a recorrer siempre será menor que n!.
Problema 2 (5 puntos). Dado un mapa político de países, diseñar un algoritmo que coloree con 3
colores los países de manera que 2 países fronterizos no tengan el mismo color.
¿Tiene este problema solución en todos los casos? Pon ejemplos si los hay.
Esquema General:
Supongamos ahora que lo que deseamos es obtener todas las formas distintas de colorear un grafo.
Entonces el algoritmo anterior podría ser modificado de la siguiente manera:
PROCEDURE Colorear2(k:CARDINAL);
(* busca todas las soluciones *)
BEGIN
X[k]:=0;
REPEAT
INC(X[k]);
IF Aceptable(k) THEN
IF k<n THEN Colorear2(k+1)
ELSE ComunicarSolucion(X)
END
END
UNTIL (X[k]=m)
END Colorear2;
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Problema 1 (5 puntos).
Diseñar un algoritmo que tome un mapa político y coloree el mapa con el mínimo
número de colores posible de manera que dos países fronterizos no tengan el mismo
color.
Problema 2 (5 puntos).
Una liga de n equipos ei juega a un juego donde solo se pierde o se gana, pero no se
empata. Diseñar un algoritmo que realice una pseudo-ordenación (e1,e2 . - - ,en) a partir
de la tabla de resultados de lo equipos de manera que e1 haya ganado a e2, e2 a e3, etc. El
coste debe ser a lo sumo O(nlogn)
Problema 1 (5 puntos). Diseñar un algoritmo que tome un mapa político y coloree el mapa con el
mínimo número de colores posible de manera que dos países fronterizos no tengan el mismo color.
Esquema General:
fun vuelta - atras(ensayo)
si valido (ensayo) {es una solucion}
entonces dev (ensayo)
si no para cada hijo ∈ compleciones (ensayo)
si condiciones de poda (hijo) hacer vuelta - atras(hijo) fsi
fpara
fsi
ffun
PROCEDURE Colorear3(k:CARDINAL);
(* busca la solucion optima *)
VAR numcolores:CARDINAL;
BEGIN
X[k]:=0;
REPEAT
INC(X[k]);
IF Aceptable(k) THEN
IF k<n THEN Colorear3(k+1)
ELSE
numcolores:=NumeroColores(X);
IF minimo>numcolores THEN
mejor:=X;
minimo:=numcolores
END
END
END
UNTIL (X[k]=m)
END Colorear3;
La función NumeroColores es la que calcula el número de colores utilizado en una solución. Por la
forma en la que hemos ido construyendo las soluciones no queda garantizado que los colores
utilizados posean números consecutivos, de forma que es necesario comprobar todos los colores para
saber cuales han sido usados en una solución concreta:
PROCEDURE NumeroColores(X:SOLUCION):CARDINAL;
VAR i,j,suma:CARDINAL; sigo:BOOLEAN;
BEGIN
suma:=0;
FOR j:=1 TO m DO (* recorremos todos los colores *)
i:=1;
sigo:=FALSE;
WHILE (i<n) AND sigo DO
IF X[i]=j THEN (* encontrado el color j *)
INC(suma);
sigo:=FALSE
END
END
END;
RETURN suma;
END NumeroColores;
En estos algoritmos es importante hacer notar que la constante m que indica el número máximo de
colores a utilizar ha de ser mayor o igual a cuatro, pues se sabe que con cuatro colores basta siempre
que el grafo corresponda a un mapa. Ahora bien, conviene también observar que no todo grafo conexo
representa a un mapa planar; por ejemplo un grafo de cinco vértices completamente conexo, es decir,
que tenga todos sus vértices conectados entre sí, no puede corresponder a un mapa en el plano.
Problema 2 (5 puntos). Una liga de n equipos ei juega a un juego donde solo se pierde o se gana,
pero no se empata. Diseñar un algoritmo que realice una pseudo-ordenación (e1,e2 . - - ,en) a partir de
la tabla de resultados de lo equipos de manera que e1 haya ganado a e2, e2 a e3, etc. El coste debe ser a
lo sumo O(nlogn)
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 3 (2 puntos). ¿Que variante del problema de la mochila admite una solución
voraz y ¿por qué? ¿Qué variante no admite solución voraz? Poner un ejemplo del
segundo caso en el que la solución voraz no nos lleve a la solución óptima.
El algoritmo Quicksort emplea un tiempo promedio de nlogn, en el peor caso de n2. El algoritmo por
fusión mergesort utiliza un tiempo de nlogn (siempre observando la precaución de equilibrar los
subcasos a tratar). Pese a esto, en la práctica Quicksort es más rápido en un tiempo c constante.
Además el algoritmo mergesort requiere de un espacio extra para tratar los distintos casos sin perder
eficiencia (hacer la ordenación in situ lleva asociado un incremento de la constante oculta bastante
alto).
Cuestión 2. Comenta las condiciones de poda que has utilizado en la realización de la práctica del
Nim.
Cuestión 3.¿Qué variante del problema de la mochila admite solución voraz, y porqué?¿Qué variante
no admite solución voraz? Poner un ejemplo del segundo caso en el que la solución voraz no nos lleve
a la solución óptima. (Respuesta: Fundamentos de algoritmia, pag 227, contraejemplo en pag. 300).
La variante del problema de la mochila que admite solución voraz es la variante continua, donde
podemos fragmentar los objetos. Esta variante admite solución voraz porque encontramos una función
de selección que nos permite escoger del candidato a cada paso de forma que obtengamos una
solución óptima. Dicha función consiste en escoger los objetos por orden decreciente (de mayor a
menor) según su relación valor/peso, lo que nos lleva a una solución óptima.
La variante que no admite solución optima es la que no nos permite fragmentar los objetos, veamos
esto con un ejemplo. Dada la siguiente relación de objetos valor-peso para una mochila de capacidad
10 (W=10 , peso máximo de la mochila) .
a b c
wi 6 5 5
vi 8 5 5
Según el algoritmo voraz de la variante continua tomaríamos los objetos según su orden decreciente en
función de la relación valor/peso. De este modo tendríamos: a (vi/wi = 1,333) , b (vi/wi= 1) y
c(vi/wi=1) . La sucesión a, b y c.
Sin embargo como en esta ocasión no podemos fragmentar los objetos al introducir en la mochila el
objeto a de peso 6 ya no podemos introducir ninguno más, el valor conseguido será entonces de 6;
mientras que si introducimos primero el objeto b, queda aún espacio para el objeto c, con lo que en
esta ocasión hemos utilizado el peso total máximo de la mochila y el valor conseguido es de 10.
Siendo esta la solución óptima. Vemos con este ejemplo como el criterio seguido en la variante
continua del problema no puede aplicarse en el caso en el que los objetos no puedan fragmentarse.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (1 puntos). Poner algún ejemplo en el que un enfoque de Divide y Vencerás nos
lleve a un coste exponencial de forma innecesaria.
Cuestión 3 (2 puntos). En una exploración de un árbol de juego con dos oponentes a nivel
de profundidad 4 (contando la raíz como nivel 1) y siguiendo la estrategia del MINIMAX nos
encontramos con la siguiente puntuación:
donde los corchetes indican agrupación de nodos por nodo padre común. Se pide propagar las
puntuaciones y elegir la rama más adecuada a partir de la raíz sabiendo que el nodo raíz
corresponde al jugador A, a mayor valor, mejor jugada para A, y que se alternan para mover.
¿En que se diferencia esta estrategia de juego de la utilizada para resolver la práctica?.
Problema (5 Puntos). La Base Aérea de Gando (Gran Canaria) posee una flota variada de n
cazas de combate ci (con i ∈ {1..n}). Cada caza tiene que salir del bunker y superar un tiempo de
rodadura ki más un tiempo de despegue ti para estar en el aire y operativo. Durante este proceso
la nave es vulnerable. Suponiendo que se produce un ataque sorpresa, construir un algoritmo
que averigüe el orden de despegue de las aeronaves de manera que se minimice el tiempo medio
durante el cual son vulnerables. Supongamos ahora que cada aeronave ci posee un índice
acumulativo bi de importancia estratégica siempre que despegue antes de la ranura temporal hi
(con i ∈ {1..n}). Si queremos maximizar la importancia estratégica una vez que hayan
despegado todas ¿Qué tipo de problema es éste? ¿Con que esquema se resuelve?. Explica en un
párrafo breve el funcionamiento del algoritmo.
Cuestión 1.Poner algún ejemplo en el que un enfoque divide y vencerás nos lleve a un coste exponencial de
forma innecesaria. (Respuesta: Fundamentos de Algoritmia, pag.83).
Un ejemplo claro de esto es utilizar la definición recursiva de la función de Fibonacci sin más, de este modo el
siguiente algoritmo conlleva un coste exponencial, existiendo formas de obtener la solución en un coste inferior
utilizando un enfoque distinto al de divide y vencerás.
Fun Fib(n)
si (n < 2) entonces devolver n
sino
Devolver Fib(n - 1) + Fib(n - 2)
Fsi
Ffun
Cuestión 2.¿Puedes explicar brevemente qué papel desempeña un montículo en el algoritmo de ramificación y
poda? ¿Cuál es el criterio general para realizar la poda en este algoritmo? (Respuesta Fundamentos de
Algoritmia, pag. 354, y 348).
El montículo es la forma ideal de mantener la lista de nodos que han sido generados pero no explorados en su
totalidad en los diferentes niveles del árbol, tomando como valor el de la función de cota. De este modo
tendremos ordenados los nodos según la cota, de manera que el elemento en la cima será el más factible de ser
explorado a continuación.
El criterio consiste en calcular una cota del posible valor de aquellas soluciones que el grafo pudiera tomar más
adelante, si la cota muestra que cualquiera de estas soluciones será necesariamente peor que la mejor solución
hallada hasta el momento entonces no seguimos explorando esa parte del grafo.
Cuestión 3.En una exploración de un árbol de juego con dos oponentes a nivel de profundidad 4 (Contando la
raiz como nivel 1) y siguiendo la estrategia MINIMAX nos encontramos con la siguiente puntuación:
Donde los corchetes indican agrupación de nodos por nodo padre común. Se pide propagar las puntuaciones y
elegir la rama más adecuada a partir de la raíz sabiendo que el nodo raíz corresponde al jugador A, a mayor
valor, mejor jugada para A, y que se alternan en mover. ¿En que se diferencia esta estrategia de juego de la
utilizada para resolver la práctica? (Alg. Minimax pag. 354)
Se trata de un problema de optimización con restricciones. Por tanto, podría ser un esquema voraz o un esquema
de ramificación y poda. Sin embargo descartamos el esquema voraz porque no es posible encontrar una función
de selección y de factibilidad tales que una vez aceptado un candidato se garantice que se va alcanzar la
solución óptima. Se trata, por tanto, de un algoritmo de ramificación y poda.
tipos
nodo = reg
sol[1..n]de 1..n
k:0..n
eficacia:real
eficacia-opt:real {prioridad}
asignado[1..n]de bool
freg
ftipos
Cuestión 1.A la hora de hacer una exploración ciega en un grafo ¿qué criterios nos pueden decidir por
una búsqueda en profundidad o en anchura? (Respuesta: Fundamentos de Algoritmia, pag 340).
Fun Fibiter(n)
Fun Fibrec(n)
i ← 1; j ← 0;
si (n < 2) entonces devolver n para k ← 1 hasta n hacer
sino j ← j + i;
devolver Fibrec(n - 1) + Fibrec(n - 2) i ← j − i;
Fsi Fpara
Ffun devolver j;
Ffun
Y el principal problema es que calcularía varias veces los mismos valores, para evitar esto existe otra
opción dada por el algoritmo iterativo, que requiere de un tiempo de orden n.
import java.io.*;
public class fibrec {
public static long fib(int n) {
if (n <= 1) return n;
else return fib(n-1) + fib(n-2);
}
public static void main(String[] args) throws IOException {
System.out.print("numero: ");
BufferedReader b=new BufferedReader (new InputStreamReader (System.in));
String numero2 =b.readLine ();
int N=Integer.parseInt (numero2);
double timeInicial = System.currentTimeMillis();
for (int i = 1; i <= N; i++){
System.out.print(i + ": " + fib(i));
double timeFinal = System.currentTimeMillis();
System.out.println ("\tTiempo: "+(timeFinal-timeInicial)/1000 + " s.");
}
}
}
import java.io.*;
public class fibiter {
public static void main(String[] args) throws IOException{
System.out.print("numero: ");
BufferedReader b=new BufferedReader (new InputStreamReader (System.in));
String numero2 =b.readLine ();
double N=Double.parseDouble (numero2);
double f = 0, g = 1;
double timeInicial = System.currentTimeMillis();
for (int i = 1; i <= N; i++) {
f = f + g;
g = f - g;
System.out.print(i+": "+f);
double timeFinal = System.currentTimeMillis();
System.out.println("\tTiempo: "+(timeFinal-timeInicial)/1000 + " s.");
}
}
}
fibrec
1: 1 Tiempo: 0.0 s.
2: 1 Tiempo: 0.0 s.
3: 2 Tiempo: 0.0 s.
fibiter 4: 3 Tiempo: 0.0 s.
1: 1.0 Tiempo: 0.0 s. 5: 5 Tiempo: 0.0 s.
2: 1.0 Tiempo: 0.0 s. .
3: 2.0 Tiempo: 0.0 s. .
4: 3.0 Tiempo: 0.0 s. 33: 3524578 Tiempo: 0.266 s.
5: 5.0 Tiempo: 0.0 s. 34: 5702887 Tiempo: 0.422 s.
. 35: 9227465 Tiempo: 0.672 s.
. 36: 14930352 Tiempo: 1.094 s.
. 37: 24157817 Tiempo: 1.766 s.
1456: 8.64010861026715E303 Tiempo: 0.266 s. 38: 39088169 Tiempo: 2.844 s.
1457: 1.3979989397902865E304 Tiempo: 0.266 s. 39: 63245986 Tiempo: 4.625 s.
1458: 2.2620098008170014E304 Tiempo: 0.266 s. 40: 102334155 Tiempo: 7.485 s.
1459: 3.660008740607288E304 Tiempo: 0.266 s. 41: 165580141 Tiempo: 12.11 s.
1460: 5.922018541424289E304 Tiempo: 0.266 s. 42: 267914296 Tiempo: 19.61 s.
Existe una tercera opción con la que tendríamos un algoritmo de fibonacci de orden O ( logn )
fun fibo (n:nat) dev f:nat fun potF (n:nat) dev (a,b:nat)
si n ≤ 1 entonces f := n casos
sino n=0 → (a,b):= (1,0)
(a,b):= potF(n-1) n=1 → (a,b):= (0,1)
f := a+b n>1 ^ par (n) →
fsi (a,b):= potF(ndiv2)
ffun (a,b):= (a*a+b*b, a*b+b*(a+b))
n<1 ^ impar (n) →
(a,b):= potF(ndiv2)
(a,b):= (b*a+(a+b)*b, b*b+(a+b)*(a+b)))
fcasos
ffun
Cuestión 3.¿Cuál es la relación entre la ordenación por selección y la ordenación por montículo?
¿Cómo se refleja en la eficiencia de los dos algoritmos?
Problema (5 puntos). Sea T[1..n] un vector de n elementos. La única comparación que se permite
entre esos elementos es el test de igualdad. Llamemos elemento mayoritario de T, si existe, a aquel
que aparece estrictamente más de n/2 veces en T. Diseñar un algoritmo que encuentre el elemento
mayoritario de un vector (si existe) en un tiempo que sea, a lo sumo, O(nlogn). Sugerencia: el
elemento mayoritario de un vector debe serlo también, al menos, de una de sus dos mitades.
ESQUEMA GENÉRICO.
función divide y vencerás (problema)
si suficientemente simple (problema)
entonces devolver solución simple (problema)
sino hacer
{p1 … pk} ← descomponer (problema)
para cada pi hacer
si ← divide_y_vencerás (pi)
fpara
devolver combinación (s1, ..., sk)
fsi
ffunción
Si el vector tiene un elemento mayoritario, este tiene que ser mayoritario en al menos una de las
mitades del vector. Utilizando esta idea, podemos mirar de forma recursiva si existe un elemento
mayoritario en cada una de las dos mitades del vector y si aparece en más de la mitad de las
posiciones, será el elemento mayoritario buscado. El algoritmo que busca el mayoritario de V [ c.. f ]
es el siguiente:
fun mayoritario (V [1..n ] de elemento, c, f : nat ) dev existe : bool, mayor : elemento
si c = f entonces existe, mayor := cierto,V [ c ]
sino
m := ( c + f ) div2
existe1 , mayor1 := mayoritario (V,c,m )
existe2 , mayor2 := mayoritario (V,m+1, f )
existe := falso
si existe1 entonces {comprobamos el primer candidato}
existe, mayor := comprobar (V,mayor1 ,c, f ) ,mayor1
fsi
si ¬existe ∧ existe2 entonces {comprobamos el segundo candidato}
existe, mayor := comprobar (V,mayor2 ,c, f ) ,mayor2
fsi
fsi
ffun
fun comprobar (V [1..n ] de elemento, x : elemento, c, f : nat ) dev valido : bool
veces := 0
para i = c hasta f hacer
si V [i ] = x entonces veces := veces +1 fsi
fpara
valido := veces > ( f - c +1) div2
ffun
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
T ( n ) ∈ Θ ( n log n )
Cuestión 1. En el algoritmo quicksort, ¿qué implicaciones tiene para la eficiencia el escoger como
pivote la mediana exacta del vector?.(Respuesta en: Fundamentos de Algoritmia, pag. 266).
El algoritmo Quicksort tiene un tiempo promedio de Ο ( nlogn ) . En la práctica es más rápido que la
ordenación por montículo (heapsort) y la ordenación por fusión (mergesort) en un tiempo constante.
Al ser un algoritmo recursivo sería ideal poder dividir el caso en subcasos de tamaños similares de
manera que cada elemento sea de profundidad logn , es decir, poder equilibrar los subcasos. Sin
embargo, aunque seleccionemos la mediana del vector en el peor caso (todos los elementos a ordenar
son iguales) el orden será cuadrático.
Para obtener alguna ventaja utilizando como pivote la mediana tendremos que hacer algunas
modificaciones al algoritmo original. Utilizaremos una nueva función pivote que divida en tres
secciones el vector, de modo que dado un pivote p una parte conste de elementos menores que él, otra
de elementos iguales y finalmente otra parte con los más grandes que p. Tras hacer esto se harían las
llamadas recursivas correspondientes al algoritmo, una para con el sub-vector de elementos menores
que p, y otra para el de los mayores que p. Esta modificación consigue que, utilizando la mediana
como pivote el orden del algoritmo sea Ο ( nlogn ) incluso en el peor caso. Pero el coste que implica
esta modificación hace que el tiempo constante crezca haciendo mejor el algoritmo de ordenación por
montículo que el Quicksort en todos los casos, lo que hace que la modificación no sea factible.
Cuestión 2. Los algoritmos de Prim y Kruskal calculan, de distintas formas, el árbol de expansión
mínimo de un grafo. ¿Cuál de los dos es más eficiente para un grafo con alta densidad de aristas?.
Razonar la repuesta. (Respuesta en: Fundamentos de Algoritmia, pag. 223).
Cuestión 3. Explicar cómo funciona el algoritmo que dota a un vector de la propiedad de montículo en
tiempo lineal (no es necesario demostrar que ese es el coste). (Respuesta en: Fundamentos de
Algoritmia, pag. 190)
Como V[4]>V[2] k = 4
Pero V[5]>V[2], k=5 intercambia V[5],V[2] V=[4,10,7,7,3,1]
k=5 y j=5, termina el bucle.
i=1 hundir (V,1)
k=1, j=1
Como V[4]>V[2] k = 4
NO(V[6]>V[4]) con lo que no hay cambio y termina el bucle. Hemos obtenido finalmente el
montículo. Si dibujamos los vectores como montículos, observamos que el proceso es el descrito
anteriormente, vamos al nivel más bajo y obtenemos los montículos correspondientes. Y así
sucesivamente hasta llegar a la raíz.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 2. (2 puntos). Suponemos que para el juego del ajedrez hemos programado
una función estática perfecta de evaluación eval(u) para cada situación de juego u. Se
supone conocida otra función, compleciones(u) que devuelve la lista de las jugadas
legales a partir de la posición del tablero u. Explica, incluyendo pseudocódigo si es
preciso, como programar un algoritmo que juegue al ajedrez. ¿Qué pasa si (como ocurre
en la realidad) la función eval(u) no existe?¿Qué papel desempeña entonces la estrategia
MINIMAX?
Diseñar ahora el algoritmo para que calcule la solución con menor número de
movimientos. Explica los cambios que hay que hacer al anterior algoritmo.
Cuestión 1.Dado el siguiente montículo [10,6,3,5,2,3,2] se pide insertar el valor 6 describiendo toda la
secuencia de cambios en el mismo.
Se compara T[8] con su padre, que viene dado por T[8/2]=4, como es mayor se intercambiaría
resultando [10,6,3,6,2,3,2,5]. El valor de k queda actualizado a 4.
Se vuelven a comparar T[4] y T[2], como son iguales no se hace nada y j queda igual que k, luego
termina el bucle.
Cuestión 2. Suponemos que para el juego del ajedrez hemos programado una función estática perfecta
de evaluación eval(u) para cada situación de juego u. Se supone conocida otra función, compleciones
(u) que devuelve la lista de las jugadas legales a partir de la posición del tablero u. Explica, incluyendo
pseudocódigo si es preciso, como programar un algoritmo que juegue al ajedrez. ¿Qué pasa si (como
ocurre en la realidad) la función eval(u) no existe?¿Qué papel desempeña entonces la estrategia
MINIMAX? (Respuesta: Fundamentos de Algoritmia, pag.355)
Gracias a la información perfecta suministrada por la función eval(u) podremos saber en cada
momento cuál es la mejor jugada posible. De este modo si jugaran por ejemplo las blancas dado un
estado u, tomaríamos como mejor movimiento el que maximice el valor de eval(u) (suponiendo que
eval(u) nos devuelve el valor más grande cuanto mejor sea la jugada para las blancas), sin importarnos
qué decisión puedan tomar las negras tras nuestro movimiento, ya que eval(u) nos asegura que nuestro
movimiento es el más acertado.
Sin embargo no existe una función eval(u) perfecta en el caso del ajedrez, deberemos entonces buscar
una función eval aproximada con una relación coste/precisión lo mejor posible. En este caso, eval(u)
no nos confirma que el mejor movimiento considerado por ella sea en realidad el más adecuado. Y es
aquí donde entra la estrategia MINIMAX, considerando que si para las blancas será la mejor jugada la
marcada como mayor valor de eval(u), las negras ejecutarán aquella que minimice dicho valor. De este
modo si queremos anticipar un número determinado de jugadas, tomaremos al anterior criterio,
minimizar el valor de eval(u) paras las negras y maximizarlo paras las blancas.
Cuestión 3. Un vector T contiene n elementos. Se quieren encontrar los m mayores elementos del
vector y se supone que n >>m. Describe una forma eficiente de hacer esto sin ordenar el vector y
calcula qué coste tiene.
Podemos utilizar un enfoque similar a como hacemos en la ordenación por fusión, pero en este caso
iremos generando un sub-vector con aquellos elementos mayores que m. Dividimos por tanto el vector
original de forma equilibrada y progresivamente, hasta llegar a un tamaño base adecuado (lo
suficientemente pequeño) para aplicar en el la búsqueda de todo elemento menor que m, cada vez que
encontremos un elemento menor que m lo almacenaremos en un vector resultado, que posteriormente
se fusionará con los encontrados en las distintas llamadas. Igualmente podemos utilizar otro algoritmo
de Ordenación pero sin ordenar el vector, si no almacenar en otro vector los índices en el orden
correspondiente, por ejemplo.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1.Mostrar mediante un ejemplo que el algoritmo de Dijkstra no siempre encuentra el camino
mínimo si existe una distancia negativa. (Respuesta: Fundamentos de Algoritmia pag. 224)
El algoritmo de Dijkstra nos devuelve los caminos mínimos desde el origen, y su pseudo código es:
Probaremos este algoritmo para el siguiente grafo:
Observando el grafo apreciamos los caminos más cortos desde 1 a cualquier nodo son:
1-2 con coste 2, 1-3 con coste 4, 1-3-5 con coste 9, 1-3-5-6 con coste 0 y 1-3-5-6-4 con coste 1,
veremos si el algoritmo de Dijkstra nos devuelve esta solución.
Observamos como la solución devuelta no es la correcta ya que los costes no son los mínimos, con lo
que los caminos serían 1-2 (coste 2), 1-2-4 (coste 3) aquí está el fallo, porque una vez que se añade el
nodo al conjunto S establece el camino mínimo y ya no puede modificarse como ocurre aquí, 1-3
(coste 4), 1-3-5(coste 9) y 1-3-5-6 (coste 0).
Cuestión 2. Poner un ejemplo de un vector de 10 elementos que suponga un ejemplo de caso peor para
el algoritmo de ordenación Quicksort. Ordenar el vector mediante este algoritmo detallando cada uno
de los pasos. (Respuesta Fundamentos de Algoritmia, pag 261)
Proc pivote(T[i...j],var L )
Proc quicksort(T[i...j])
p ← T[i]
Si j - i es suficientemente pequeño entonces
k ← i ; L ← j +1
insertar (T[i...j] )
repetir k ← k +1 hasta que T[k] > p o k >= j
Sino
fin_repite
Pivote (T[i...j],L)
repetir L ← L − 1 hasta que T[L] <= p
Quicksort (T[i..L - 1])
fin_repite
Quicksort(T[L +1.. j])
mientras k < L hacer
Fsi
Intercambiar T[k] y T[L]
Fin_proc
repetir k ← k +1 hasta que T[k] > p
fin_repite
repetir L ← L+1 hasta que T[L] <= p
fin_repite
fmientras
intercambiar T[i] y T[L]
Fin_proc
T[1..10]={1,2,3,4,5,6,7,8,9,10}
Pivote(T[1..10], L)
Luego i=1 y j=10
p=1, k=1, L=11
Llegamos al 3er bucle k=2 y L=1 (k>L), no existe por tanto intercambio alguno, excepto la última
instrucción Intercambiar T[i] por T[L], como L apunta a 1, e i es uno T[L] queda igual.
Ignoramos la primera llamada al ser de un tamaño 0, la segunda llamada tendría el mismo efecto que
la primera vez, en este caso en el procedimiento pivote L descendería hasta llegar al T[2], y k quedaría
en el tercer elemento; no se provoca cambio alguno, y vuelve a hacerse una tercera llamada a
Quicksort, una de ellas para un caso de tamaño 0: Quicksort(T[2..1]), y otra para el resto del vector,
Quicksort(T[3...10]).
Como vemos el proceso se repite hasta llegar al último elemento, produciéndose tantas llamadas como
elementos posee el vector.
Cuestión 3. Dado un montículo T[1...n] programar completamente en pseudocódigo una función
recursiva flotar(T,i) para flotar el elemento de la posición i del vector T. Explica como usar esta
función para insertar un elemento en el montículo. (Respuesta. Fundamentos de Algoritmia, pag. 188)
Tomar el algoritmo iterativo de la pag 188 y transformar en recursivo.
PROBLEMA (5 puntos). Se tiene que organizar un torneo con n participantes (con n potencia de 2).
Cada participante tiene que competir exactamente una vez con todos los posibles oponentes. Además
cada participante tiene que jugar exactamente 1 partido al día. Se pide construir un algoritmo que
permita establecer al calendario del campeonato para que concluya en n-1 días. Sugerencia: dos grupos
disjuntos de m jugadores pueden jugar entre ellos en m días mediante rotación. Ejemplo: {a,b,c} contra
{d,e,f} juegan: Día 1: ad, be y cf. Día 2: ae, bf, y cd. Y finalmente Día 3: af, bd y ce.
Se pide programar un algoritmo que resuelva el juego. Explicar ademas (si las hay) las
diferencias que habra que introducir a este algoritmo si se exigiera resolver el juego en el
menor numero de movimientos.
La resolucion de cada problema debe incluir, por este orden: Eleccion razonada del esquema
algortmico. Breve descripcion del esquema usado e identicacion con el problema. Estructuras
de datos requeridas. Algoritmo completo a partir del renamiento del esquema general. Estudio
del coste.
Segun el esquema elegido hay que especicar, ademas: Voraz: demostracion de optimalidad.
Divide y vencer as: preorden bien fundado entre los ejemplares del problema. Exploracion en
grafos: descripcion del grafo de busqueda asociado.
RESPUESTAS EXAMEN Programación III. Septiembre 2001 (Original)
Cuestión 1.Comenta de qué formas se puede mejorar la eficiencia del algoritmo de Dijkstra mediante
el uso de estructuras de datos apropiadas. (Respuesta. Fundamentos de Algoritmia. Pag 227)
Utilizando una matriz de adyacencia y matrices para representar los datos el algoritmo se sitúan en
Ο ( n 2 ) (con un tiempo Ο ( n ) para inicializar la matriz de adyacencia y un tiempo Θ ( n 2 ) en los bucles
que conforman el algoritmo voraz).
Si resulta que el número de aristas a es mayor que el número de nodos al cuadrado, n 2 , resulta
apropiado utilizar en vez de una matriz de adyacencia, una lista de adyacencia, evitando examinar
entradas innecesarias -dónde no existe arista- , ahorrando así tiempo en el bucle más interno del
algoritmo. Por otro lado podemos utilizar un montículo invertido para representar el camino mínimo
que se irá generando (que llamaremos D), esto hace que buscar un nodo que minimice el costo
requiera sólo eliminar la raíz del montículo lo que conlleva un gasto de O(log n) , siendo la
inicialización del montículo Θ ( n ) . Igualmente las operaciones empleadas en el bucle más interno del
algoritmo reducirán su coste (al ser D un montículo), situándose en O(log n) .
Si se produce la eliminación de la raíz del montículo n-2 veces (el bucle se ejecuta n-2 veces) y hay
que flotar un máximo de a nodos (siendo a el número de aristas) obtenemos un tiempo total de
Θ((a + n) log n) . Si el grafo es conexo, es decir, a >= n - 1 el tiempo corresponde a Θ(a log n) . Si el
grafo es denso será preferible la implementación con matrices, si el grafo es disperso es mejor la
implementación con montículos.
Cuestión 2. Explica porqué una estructura de montículo suele ser adecuada para representar el
conjunto de candidatos de un algoritmo voraz.
En un algoritmo voraz iremos eligiendo el candidato más apropiado a cada paso para la hallar la
solución según el valor de una función de selección. Para agilizar esa elección podemos tener dichos
candidatos almacenados en un montículo, de forma que el valor considerado para su mantenimiento
sea el valor de dicha función de selección. De este modo la elección de candidato consistirá en ir
escogiendo la cima de dicho montículo y actualizar este cuando así proceda, operaciones estas que
resultan más eficientes en los montículos que en otros tipos de estructura de datos.
Cuestión 3. Explica en que consiste un problema de planificación con plazo fijo. Pon un ejemplo con
n=4 y resuélvelo aplicando el algoritmo correspondiente. (Respuesta: Fundamentos de Algoritmia
pag.233)
Dado un conjunto de n tareas, cada una de las cuales requiere un tiempo unitario, en cualquier instante
T=1,2,... podemos ejecutar únicamente una tarea. Cada tarea nos producirá un beneficio mayor que
cero, sólo en el caso en el que su ejecución sea en un instante anterior a di. Habrá que buscar la
secuencia de tareas más apropiada de manera que el beneficio obtenido sea mayor.
(Bastaría con aplicar el algoritmo que aparece en la pag. 240, ilustrado con un ejemplo para seis tareas,
como se ve la solución bastaría con eliminar las tareas 5 y 6 y el resultado sería igual con lo que ya
tenemos un ejemplo con n=4
i 1 2 3 4
beneficio 20 15 10 7
tiempo 3 1 1 3
Observar que tenemos las tareas ya ordenadas según su beneficio de mayor a menor, si no fuera así
para utilizar el algoritmo convendría proceder a esta ordenación)
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 3. En los algoritmos de vuelta atrás explica la diferencia entre una condición
de poda y una condición de retroceso.
El algoritmo Quicksort emplea un tiempo promedio de nlogn, en el peor caso de n2. El algoritmo por
fusión Mergesort utiliza un tiempo de nlogn (siempre observando la precaución de equilibrar los
subcasos a tratar). Pese a esto, en la práctica Quicksort es más rápido en un tiempo c constante.
Además el algoritmo Mergesort requiere de un espacio extra para tratar los distintos casos sin perder
eficiencia (hacer la ordenación in situ lleva asociado un incremento de la constante oculta bastante
alto).
hundir ( i,V )
Si k > i entonces
Intercambiar (V[i] y V[k] )
crear_monticulo (V,i − 1)
hundir ( k , V )
fsi
Fin_proc
fproc
Cuestión 3. En los algoritmos de vuelta atrás explica la diferencia entre una condición de poda y una
condición de retroceso.
El algoritmo de vuelta atrás básico consiste en una búsqueda exhaustiva en el árbol, si el recorrido del
árbol no tiene éxito porque la solución parcial hasta ese momento no puede ser completada se produce
un retroceso, igualmente si ya existiera una solución mejor a la que está siendo actualmente calculado
volveríamos atrás.
Si queremos limitar más aún nuestro espacio de búsqueda podemos utilizar condiciones de poda, dado
un determinado problema intentamos encontrar información que nos permita decidir detener la
búsqueda (volviendo consecuentemente atrás) si hay claros indicios de que el estado actual no nos
conducirá a una solución.
En vuelta atrás no hay condición de poda, sólo comprobación de que las soluciones
parciales son k-prometedoras antes de continuar la exploración del grafo.
Lourdes Araujo.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Tarea i 1 2 3 4 5 6
Instante di 4 3 1 1 2 2
Beneficios gi 10 30 20 30 50 20
Tarea i 1 2 3 4 5 6
Instante di 4 3 1 1 2 2
Beneficios gi 10 30 20 30 50 20
Tarea i 5 2 4 3 6 1
Instante di 2 3 1 1 2 4
Beneficios gi 50 30 30 20 20 10
Cuestión 2 (1 puntos). Explica la diferencia entre una condición de poda y una condición de
retroceso en el esquema de búsqueda vuelta atrás.
El algoritmo de vuelta atrás básico consiste en una búsqueda exhaustiva en el árbol, si el recorrido del
árbol no tiene éxito porque la solución parcial hasta ese momento no puede ser completada se produce
un retroceso, igualmente si ya existiera una solución mejor a la que está siendo actualmente calculado
volveríamos atrás.
Si queremos limitar más aún nuestro espacio de búsqueda podemos utilizar condiciones de poda, dado
un determinado problema intentamos encontrar información que nos permita decidir deterger la
búsqueda (volviendo consecuentemente atrás) si hay claros indicios de que el estado actual no nos
conducirá a una solución.
Cuestión 3 (2 puntos). Programar en pseudocódigo un algoritmo recursivo para la operación de
hundir un elemento en un montículo.
Problema (5 puntos). Una caja con n bombones se considera “aburrida” si se repite un mismo tipo de
bombón (denominado bombón pesado) más de n/2 veces. Programar en pseudocódigo un algoritmo
que decida si una caja es “aburrida” y devuelva (en su caso) el tipo de bombón que le confirme dicha
propiedad. El coste debe ser a lo sumo O(n log n). NOTA: Si una caja tiene un bombón pesado,
entonces necesariamente también lo es al menos una de sus mitades.
(Resuelto Universidad de Málaga pag 121, y EDMA 331).
ESQUEMA GENÉRICO.
Si el vector tiene un elemento mayoritario, este tiene que ser mayoritario en al menos una de las
mitades del vector. Utilizando esta idea, podemos mirar de forma recursiva si existe un elemento
mayoritario en cada una de las dos mitades del vector y si aparece en más de la mitad de las
posiciones, será el elemento mayoritario buscado. El algoritmo que busca el mayoritario de V [ c.. f ]
es el siguiente:
fun mayoritario (V [1..n ] de elemento, c, f : nat ) dev existe : bool, mayor : elemento
si c = f entonces existe, mayor := cierto,V [ c ]
sino
m := ( c + f ) div2
existe1 , mayor1 := mayoritario (V,c,m )
existe2 , mayor2 := mayoritario (V,m+1, f )
existe := falso
si existe1 entonces {comprobamos el primer candidato}
existe, mayor := comprobar (V,mayor1 ,c, f ) ,mayor1
fsi
si ¬existe ∧ existe2 entonces {comprobamos el segundo candidato}
existe, mayor := comprobar (V,mayor2 ,c, f ) ,mayor2
fsi
fsi
ffun
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
T ( n ) ∈ Θ ( n log n )
Escuela Universitaria de Informática de la UNED
Ingenierı́a Técnica de Sistemas e Ingenierı́a Técnica de Gestión
X O X X O O O X
1 2 3 4 5
1 - 50 30 100 10
2 - - - - -
3 - 5 - -30 -
4 - 20 - - -
5 - - - 10 -
Cuestión 1 (2 puntos). En el algoritmo de ordenación por montículo (heapsort). ¿Cuáles son las
mejores y peores disposiciones iniciales de los ejemplos que hay que ordenar en cuanto al tiempo de
ejecución? Razonar la respuesta y poner ejemplos.
En el peor caso, la profundidad a la que hay que empujar las raíces respectivas es la máxima, y por tanto la
complejidad de esta segunda parte del algoritmo es O(nlogn). ¿Cuándo ocurre esto? Cuando el elemento es
menor que todos los demás. Pero esto sucede siempre que los elementos a ordenar sean distintos, por la
forma en la que se van escogiendo las nuevas raíces.
En el caso mejor, aunque el bucle se sigue repitiendo n–1 veces, las raíces no descienden, por ser mayores o
iguales que el resto de los elementos del montículo. Así, la complejidad de esta parte del algoritmo es de orden
O(n). Pero este caso sólo se dará si los elementos del vector son iguales, por la forma en la que originariamente
se construyó el montículo y por cómo se escoge la nueva raíz en cada iteración (el último de los elementos, que
en un montículo ha de ser de los menores).
Cuestión 2 (2 puntos). Aplicar el algoritmo de Dijkstra al grafo dirigido representado por la siguiente
matriz adyacencia.
1
10 50
1 2 3 4 5 30
5
1 50 30 100 10 2
2 100
3 5 -30 10 20
4 20 5
5 10
4 3
-30
Tomando el nodo 1 como nodo origen. ¿Encuentra los caminos mínimos? Si la respuesta es negativa
¿Cuáles serian los caminos mínimos y porque no los encuentra el algoritmo de Dijkstra? ¿Qué pasaría
si se invierte el sentido de la arista que une el nodo 3 con el 2?.
El fallo está en el camino 1-5-4 que tiene como resultado 20, pero resulta no ser un camino mínimo al
haber el camino 1-3-4 de valor 0. Se supone que una vez que se añade un nodo al conjunto S no se
puede modificar la distancia.
Cuestión 3 (1 punto). De los algoritmos de ordenación que has estudiado. ¿Cuál es el más eficiente
en términos de coste asintótico temporal en el caso peor?. Razonar la respuesta.
Quicksort:
Mergesort:
Montículo:
PROBLEMA (5 puntos). Escribir un algoritmo que descubra cómo encontrar el camino más corto
desde la entrada hasta la salida de un laberinto (suponer que hay sólo una entrada y sólo una salida)
Las variables mov_fil y mov_col contienen los posibles movimientos, y son inicializadas por el
procedimiento MovimientosPosibles que mostramos a continuación:
VAR mov_fil,mov_col:ARRAY [1..4] OF INTEGER;
PROCEDURE MovimientosPosibles;
BEGIN
mov_fil[1]:=1; mov_col[1]:=0; (* sur *)
mov_fil[2]:=0; mov_col[2]:=1; (* este *)
mov_fil[3]:=0; mov_col[3]:=-1; (* oeste *)
mov_fil[4]:=-1; mov_col[4]:=0; (* norte *)
END MovimientosPosibles;
En este caso hemos introducido una variante muy importante: el uso de una cota para podar ramas del
árbol de expansión. Si bien ésta es una técnica que se utiliza sobre todo en los algoritmos de
Ramificación y Poda (y de ahí su nombre), el uso de cotas para podar puede ser aplicado a cualquier
tipo de árboles de expansión.
En el problema que nos ocupa calculamos la primera solución y para ella se obtiene un valor. En este
caso es el número de movimientos que ha realizado el algoritmo hasta encontrar la salida, que es el
valor que queremos minimizar. Pues bien, disponiendo ya de un valor alcanzable podemos “rechazar”
todos aquellos nodos cuyos recorridos superen este valor, sean soluciones parciales o totales, pues no
nos van a llevar hacia la solución óptima. Estas podas ahorran mucho trabajo al algoritmo, pues evitan
que éste realice trabajo innecesario.
La variable recorridominimo, que es la que hace las funciones de cota, se inicializa a
MAX(CARDINAL) al principio del programa principal que invoca al procedimiento Laberinto2(1,1,1).
Como norma general para los algoritmos de Vuelta Atrás, y puesto que su complejidad es
normalmente exponencial, debemos de saber aprovechar toda la información disponible sobre el
problema o sus soluciones en forma de restricciones, pues son éstas la clave de su eficiencia. En la
mayoría de los casos la diferencia entre un algoritmo Vuelta Atrás útil y otro que, por su tardanza, no
pueda utilizarse se encuentra en las restricciones impuestas a los nodos, único parámetro disponible al
programador de estos métodos para mejorar la eficiencia de los algoritmos resultantes.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Procedimiento P(n){
var i, j : enteros;
j ← 1;
si n ≤ 1 entonces
terminar;
sino {
para i ← 1 hasta 7 hacer P ( n div 2 )
para i ← 1 hasta 4n 3 hacer j ← j + 1;
}
}
Cuestión 1 (2 puntos). Dado un vector T [1..n ] que alberga un montículo en T [1..i ] , programar una
función recursiva que flote el elemento i + 1 .
Cuestión 2 (2 puntos). Calcular la ecuación de recurrencia y hallar el coste del siguiente algoritmo:
Procedimiento P(n){
var i, j : enteros; ⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
j ← 1; ⎪⎩aT ( n / b ) + cn , si n ≥ b
k
si n ≤ 1 entonces ⎧Θ ( n k )
⎪ , si a < b k
terminar; ⎪
T ( n ) ∈ ⎨Θ ( n k logn ) , si a = b k
sino { ⎪
para i ← 1 hasta 7 hacer P ( n div 2 ) (
⎪⎩Θ n b )
log a , si a > b k
desde 1 hasta 4n3. Por lo que el grado k del polinomio será 3 ( k = 3 ). La ecuación de recurrencia
quedaría:
Pivote
2 3 8 1 9 4 2 2 6 5 4 3 7 4
2 3 4 1 9 4 2 2 6 5 4 3 7 8
2 3 4 1 3 4 2 2 6 5 4 9 7 8
2 3 4 1 3 4 2 2 4 5 6 9 7 8
2 1 4 3 3 4 2 2 4 5 6 9 7 8
1 2 4 3 3 4 2 2 4 5 6 9 7 8
1 2 2 3 3 4 2 4 4 5 6 9 7 8
1 2 2 2 3 4 3 4 4 5 6 9 7 8
1 2 2 2 3 3 4 4 4 5 6 9 7 8
1 2 2 2 3 3 4 4 4 5 6 7 9 8
1 2 2 2 3 3 4 4 4 5 6 7 8 9
PROBLEMA (5 puntos). Se tienen dos polinomios de grado n representados por dos vectores [ 0..n ]
de enteros, siendo la posición i de ambos vectores la correspondiente al termino x del polinomio. Se
pide diseñar un algoritmo que multiplique ambos polinomios, valorándose especialmente que sea con
( )
un coste más eficiente que Ο ( n 2 ) , en concreto hay una solución fácil de hallar con coste Ο nlog2 3 .
Solución:
No es un problema de optimización así que descarto el esquema voraz y el de ramificación y poda,
tampoco es una búsqueda por lo tanto no es posible utilizar un esquema de vuelta atrás. El problema se
puede dividir en subproblemas así que utilizaré un esquema de divide y vencerás.
La solución de orden cuadrático. En este caso, se descompone en mitades P(x) = Axn/2+B y Q(x) =
Cxn/2 +D con A,B,C,D polinomios de grado n/2 sacando factor común xn/2 como se detalla en las
expresiones. De esta forma se ve claramente que P·Q es (Axn/2 +B)·( Cxn/2+D) = AC xn + (AD+BC)xn/2
+ BD lo que la solución conlleva 4 multiplicaciones de grado n/2. El coste sería en este caso
cuadrático. Sin embargo hay una manera de organizar las operaciones mediante la cual, no es
necesario calcular AD+BC mediante 2 productos, sino sólo con uno, aprovechando que ya tenemos
realizados los productos BD y AC. En este último caso basta con observar que (A+B)·( C+D) =
AC+BC+AD+BD y que BC+AD = (A+B)·( C+D) – AC – BD con lo que es posible realizar el cálculo
con 3 productos en lugar de 4, ya que el coste de las sumas, si consideramos las multiplicaciones como
lineales, sería constante. De manera que (A+B)·( C+D) sería uno de los productos, y AC y BD los
otros dos.
Para realizar el algoritmo representamos cada número en un vector de n posiciones y el resultado será
de tamaño 2n, suponemos que n es potencia de 2. El algoritmo de multiplicación es para números
positivos, aunque algunos valores intermedios puedan ser negativos, para los valores intermedios se
considerará el vector ampliado a la posición 0 para mantener el bit de signo.
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b
log a
) , si a > b k
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (2 puntos). ¿Para qué se pueden utilizar montículos en el algoritmo de Kruskal? ¿Qué
mejoras introduce en términos de complejidad?
Cuestión 2 (1 puntos). Dado el grafo de la figura, aplicar el algoritmo de Dijkstra para hallar los
caminos más cortos desde el nodo 1 hasta cada uno de los otros nodos, indicando en cada paso: nodos
seleccionados, nodos no seleccionados, vector de distancias y vector de nodos precedentes.
Nodo
Distancias desde 1
Precedente
PASO Nodo Seleccionado Nodo No Seleccionado 2 3 4 5 2 3 4 5
INICIO
1
2
3
Cuestión 3 (2 puntos). Se desea implementar una función para desencriptar un mensaje numérico. La
función desencriptar recibe tres enteros: el mensaje cifrado c, la clave privada s y la clave pública z; y
devuelve el mensaje original a. El mensaje original se recompone con la fórmula:
Problema (5 puntos). Teseo se adentra en el laberinto en busca de un minotauro que no sabe dónde
está. Se trata de implementar una función ariadna que le ayude a encontrar el minotauro y a salir
después del laberinto. El laberinto debe representarse como una matriz de entrada a la función cuyas
casillas contienen uno de los siguientes tres valores: 0 para “camino libre”, 1 para “pared” (no se
puede ocupar) y 2 para “minotauro”. Teseo sale de la casilla (1,1) y debe encontrar la casilla ocupada
por el minotauro. En cada punto, Teseo puede tomar la dirección Norte, Sur, Este u Oeste siempre que
no haya una pared. La función ariadna debe devolver la secuencia de casillas que componen el camino
de regreso desde la casilla ocupada por el minotauro hasta la casilla (1,1).
Cuestión 1 (2 puntos). ¿Para qué se pueden utilizar montículos en el algoritmo de Kruskal? ¿Qué mejoras
introduce en términos de complejidad? (Respuesta. Fundamentos de Algo. Pag 220 y 223)
Para tener en la raíz del montículo la arista más corta. Esto permite efectuar una inicialización en un tiempo
Θ ( a ) . Esto es ventajoso si se encuentra el árbol de recubrimiento mínimo en un momento que quede por
probar un número considerable de aristas. El algoritmo original perdería bastante tiempo ordenando aristas
inútiles.
Cuestión 2 (1 puntos). Dado el grafo de la figura, aplicar el algoritmo de Dijkstra para hallar los caminos más
cortos desde el nodo 1 hasta cada uno de los otros nodos, indicando en cada paso: nodos seleccionados, nodos
no seleccionados, vector de distancias y vector de nodos precedentes.
Nodo
Distancias desde 1
Precedente
Nodo
PASO
Seleccionado
Nodo No Seleccionado 2 3 4 5 2 3 4 5
INICIO {1} {2,3,4,5} 20 ∞ 5 7 1 1 1 1
1 {1,4} {2,3,5} 20 12 5 7 1 4 1 1
2 {1,4, 5} {2,3} 20 12 5 7 1 4 1 1
3 {1,4,5,3} {2} 19 12 5 7 3 4 1 1
Cuestión 3 (2 puntos). Se desea implementar una función para desencriptar un mensaje numérico. La función
desencriptar recibe tres enteros: el mensaje cifrado c, la clave privada s y la clave pública z; y devuelve el
mensaje original a. El mensaje original se recompone con la fórmula:
a := c s mod z
Sabiendo que no se dispone del operador de potencia, implementar la función utilizando el esquema divide y
vencerás.
Problema (5 puntos). Teseo se adentra en el laberinto en busca de un minotauro que no sabe dónde
está. Se trata de implementar una función ariadna que le ayude a encontrar el minotauro y a salir
después del laberinto. El laberinto debe representarse como una matriz de entrada a la función cuyas
casillas contienen uno de los siguientes tres valores: 0 para “camino libre”, 1 para “pared” (no se
puede ocupar) y 2 para “minotauro”. Teseo sale de la casilla (1,1) y debe encontrar la casilla ocupada
por el minotauro. En cada punto, Teseo puede tomar la dirección Norte, Sur, Este u Oeste siempre que
no haya una pared. La función ariadna debe devolver la secuencia de casillas que componen el camino
de regreso desde la casilla ocupada por el minotauro hasta la casilla (1,1).
Solución:
Como no se indica nada al respecto de la distancia entre casillas adyacentes, y ya que se sugiere
utilizar únicamente una matriz, es lícito suponer que la distancia entre casillas adyacentes es siempre
la misma (1, sin pérdida de generalidad). Por otra parte, no se exige hallar el camino más corto entre la
entrada y el minotauro, sino que el enunciado sugiere, en todo caso, que el algoritmo tarde lo menos
posible en dar una de las posibles soluciones (y ayudar a salir a Teseo cuanto antes).
Tras estas consideraciones previas ya es posible elegir el esquema algorítmico más adecuado. El
tablero puede verse como un grafo en el que los nodos son las casillas y en el que como máximo
surgen cuatro aristas (N, S, E, O). Todas las aristas tienen el mismo valor asociado (por ejemplo, 1).
En primer lugar, el algoritmo de Dijkstra queda descartado. No se pide el camino más corto y si se
hiciera, las particularidades del problema hacen que el camino más corto coincida con el camino de
menos nodos y, por tanto, una exploración en anchura tendrá un coste menor siempre que no se visiten
nodos ya explorados, como mucho se recorrerá todo el tablero una vez (coste lineal con respecto al
número de nodos versus coste cuadrático para Dijkstra).
En segundo lugar, es previsible esperar que el minotauro no esté cerca de la entrada (estará en un nivel
profundo del árbol de búsqueda) por lo que los posibles caminos solución serán largos.
Como no es necesario encontrar el camino más corto, sino encontrar un camino lo antes posible,
una búsqueda en profundidad resulta más adecuada que una búsqueda en anchura. En el peor
de los casos en ambas habrá que recorrer todo el tablero una vez, pero ya que buscamos un
nodo profundo, se puede esperar que en media una búsqueda en profundidad requiera explorar
menos nodos que una búsqueda en anchura.
En tercer lugar, es posible que una casilla no tenga salida por lo que es necesario habilitar un
mecanismo de retroceso.
Por último, es necesario que no se exploren por segunda vez casillas ya exploradas
anteriormente.
Vamos a utilizar el esquema de vuelta atrás modificado para que la búsqueda se detenga en la
primera solución y para que devuelva la secuencia de ensayos que han llevado a la solución en
orden inverso (es decir, la secuencia de casillas desde el minotauro hasta la salida).
Para almacenar las casillas bastará un registro de dos enteros x e y. Vamos a utilizar una lista
de casillas para almacenar la solución y otra para las compleciones. Para llevar control de los
nodos visitados bastará una matriz de igual tamaño que el laberinto pero de valores booleanos.
Suponemos “laberinto” inicializado con la configuración del laberinto y ”visitados” inicializado con
todas las posiciones a falso.
tipoCasilla = registro
x,y: entero;
fregistro
tipoLista
La función compleciones comprobará que la casilla no es una pared y que no está fuera del
laberinto
Todas las operaciones son constantes salvo la llamada recursiva a vuelta-atrás. En cada nivel,
pueden realizarse hasta 4 llamadas. Sin embargo, las llamadas no se realizan si la casilla ya ha
sido visitada. Esto quiere decir que, en el caso peor, sólo se visitará una vez cada casilla. Como
las operaciones para una casilla son de complejidad constante, la complejidad será
O(ANCHO*LARGO), lineal con respecto al número de casillas.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 3 (3 puntos). Aplicar el algoritmo de planificación con plazo fijo para las
actividades ai maximizando el beneficio gi en el plazo di. Detallar todos los pasos con
claridad.
Tarea ai 1 2 3 4 5 6 7 8
Beneficios gi 20 10 7 15 25 15 5 30
Instante di 4 5 1 1 2 3 1 2
⎡ a b ⎤ ⎡ f n −1 ⎤ ⎡ f n ⎤
⎢1 0⎥ ⋅ ⎢ f ⎥ = ⎢ f ⎥
⎣ ⎦ ⎣ n − 2 ⎦ ⎣ n −1 ⎦
Suponiendo que el rango de los valores a ordenar es limitado, es decir, sabemos que por
ejemplo el rango de números a ordenar va de 0 a 2000, podemos generar un vector de 2000
elementos de tipo entero que almacenará almacenará el número de ocurrencias de cada valor
de la lista a ordenar en la posición del vector correspondiente.
Si.
Tarea ai 1 2 3 4 5 6 7 8
Beneficios gi 20 10 7 15 25 15 5 30
Instante di 4 5 1 1 2 3 1 2
Resultado de la ordenación:
i 1 2 3 4 5 6 7 8
Tarea ai 8 5 1 4 6 2 3 7
Beneficios gi 30 25 20 15 15 10 7 5
Instante di 2 2 4 1 3 5 1 1
i=2, d[2]=2. Seleccionamos K(1)
0 3 4 5 1 2 3 4 5
0,1,2 3 4 5 2 1 0 0 0 j[]
0 3 5 1 2 3 4 5
0,1,2,3,4 5 2 1 5 3 0 j[]
0 1 2 3 4 5
0,1,2,3,4,5 2 1 5 3 6 j[]
Problema (5 Puntos)
Se pide diseñar completamente un algoritmo que calcule en tiempo logerítmico el valor
de fn de la sucesión definida como fn=afn-1+bfn-2 con f0=0 y f1=1. Sugerencia: Pudiera
ser de utilidad razonar el problema utilizando la siguiente igualdad entre matrices:
a b f n−1 f n
=
1 0 f n−2 f n−1
Solución
La solución trivial se basa en resolver recursivamente el caso n resolviendo los n
términos anteriores, lo cual supondría un coste lineal que no es lo que nos piden en el
enunciado del problema.
Planteamiento
a b
Si llamamos F a la matriz , vamos a intentar encontrar una fórmula que nos
1 0
permita expresar la solución del problema en función de los casos base que se
proporcionan en el enunciado que son f0=0 y f1=1.
fn f
= F • n−1
f n−1 f n−2
f n−1 f
= F • n−2 f n f f
⇒ = F 2 • n−2 = ..... = F n−1 • 1
f n−2 f n−3 f n−1 f n−3 f0
..........................
f2 f1
= F •
f1 f 0
Con esta simplificación, es posible calcular el valor de fn con sólo calcular Fn-1 y hacer
una multiplicación por el vector de casos base.
n 2
F 2 si n es par
( )
2
F n = F ndiv 2 F n mod 2 = 2
2
n −1
F
F si n es impar
Como puede observarse, reducimos el problema n a uno de n/2 que nos permite obtener
una eficiencia de O(log n) frente a O(n) como ocurría si aplicábamos el algoritmo
inicial.
1- Tamaño umbral y solución simple: En nuestro caso, para n=1 y n=0 existen
soluciones triviales y, por tanto, nuestro tamaño umbral lo podemos poner en
n=1.
2- Descomposición: Fn siempre lo vamos a descomponer en un único subproblema
Fn/2. En este caso, el algoritmo de divide y vencerás realiza una reducción en
lugar de una descomposición.
3- Combinación: La función de combinación en nuestro caso será la que hemos
desarrollado al final del apartado anterior.
Estructuras de Datos.
En nuestro caso utilizaremos enteros y matrices de 2x2.
Algoritmo Completo.
Suponiendo que conocemos a, b, f0 y f1.
fun f(n:entero) dev entero
a b
F .
1 0
caso n=0: dev f0
n=1: dev f1
verdadero: hacer
S . exp_mat(F,n-1)
f1
S
s .
f
0
dev s[1]
fcaso
ffun
si n <= 1 entonces
dev solucion_simple(M,n)
si no hacer
p . n div 2
r . n mod 2
T . exp_mat(M,p)
dev combinacion(T,r,M)
fsi
ffun
1 0
Donde M1=M y M0=
0 1
fun solucion_simple (M:vector[2,2], n:entero) dev vector[2,2]
si n=1 dev M
1 0
si no dev
0 1
fsi
ffun
a, c . R , k . R . {0}, n, b . N , b > 1
+ +
( )
Τ n k si a < b k
( )
T (n ) = Τ n k •log n si a = b k
( )
Τ n log b a si a > b k
De acuerdo a los valores extraídos del caso particular de nuestra función, podemos
concluir que la complejidad del problema solucionado será de O(log n) como nos
pedían en el enunciado.
Cuestión 1 (2 puntos). Hallar el coste de los siguientes algoritmos, siendo h(n,r,k) ∈ O(n) .
Sumando los términos nos sale T(n) = T(n/4) +T(n/4)+n (2n) +T(n/4), lo que equivale a
T(n) = 3T(n/4) + 2n2 Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b) + cnk y siendo 3 < 42 el coste es O(n2). a = 3, b = 4, k = 2
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
Cuestión 2 (2 puntos). Una matriz T contiene n elementos. Se desea encontrar los m elementos más
pequeños de T (con m<<n). Explicar cómo hacer esto de la manera más eficiente.
Un procedimiento parcial de ordenación por selección nos da los m elementos más pequeños con coste
O(m ⋅ n) . Una ordenación eficiente sin embargo lo haría en O(n ⋅ log ⋅ n) . Si m ≈ n , el coste vendría a
ser cuadrático, lo cual nos haría desechar el procedimiento de selección, sin embargo con m << n el
orden O(m ⋅ n) se puede considerar lineal, y en este caso el algoritmo de selección puede ser más
eficiente.
Cuestión 3 (1 punto). Explicar de qué manera se puede implementar mediante un esquema voraz el conocido
problema de la búsqueda del camino más corto hacia la salida a un laberinto descrito por una matriz rectangular
de casillas de tipos libre y ocupada, y otras dos de tipo entrada y salida. Compararlo en términos de coste con
otras soluciones.
Cada casilla se asimila a un nodo. Casillas libres adyacentes tendrían aristas dirigidas en ambas direcciones. El
peso sería unitario para cada arista. Las casillas de tipo ocupada no tienen aristas origen ni destino.
De esta forma, un algoritmo de Dijkstra puede hallar el camino más corto de un nodo llegada a todos los demás,
incluyendo el nodo salida.
En términos de coste, sin embargo es necesario tener en cuenta que si el laberinto es un cuadrado de lado n, el
grafo tendrá v = n 2 nodos y alrededor de a = 4n 2 aristas. En el análisis de coste de la resolución de Dijkstra si
v (número de nodos del grafo) es lo suficientemente grande hace cierta la expresión a << v 2 y por tanto
podemos aproximarnos al coste O((a + v) ⋅ log v)
Problema (5 puntos). Una operadora de telecomunicaciones dispone de 10 nodos conectados todos entre sí por
una tupida red de conexiones punto a punto de fibra óptica. Cada conexión c(i,j) entre el nodo i y j ( Con i,j
0{1..10}) tiene un coste asignado que sigue la fórmula c(i,j) = (i + j) MOD 8. La operadora quiere reducir
gastos, para lo cual está planificando asegurar la conectividad de su red de nodos minimizando el coste. Diseñar
un algoritmo que resuelva el problema (4 puntos) y aplicarlo a los datos del enunciado (1 punto).
Se trata de un grafo no dirigido de 10 nodos n1 , n2 ,..., n10 con una matriz simétrica de costes:
1 2 3 4 5 6 7 8 9 10
1 - 3 4 5 6 7 0 1 2 3
2 3 - 5 6 7 0 1 2 3 4
3 4 5 - 7 0 1 2 3 4 5
4 5 6 7 - 1 2 3 4 5 6
5 6 7 0 1 - 3 4 5 6 7
6 7 0 1 2 3 - 5 6 7 0
7 0 1 2 3 4 5 - 7 0 1
8 1 2 3 4 5 6 7 - 1 2
9 2 3 4 5 6 7 0 1 - 3
10 3 4 5 6 7 0 1 2 3 -
Se trata de conseguir minimizar el coste de los enlaces asegurando únicamente la conectividad de la red.
El enunciado describe un problema de optimización en el que se nos pide que el grafo sea conexo ("asegurar la
conectividad de la red") y contenga un árbol de expansión mínimo ("que el coste sea mínimo"), ya que la
conectividad se asegura no dejando subgrafos no conexos.
Elección del esquema: Con las condiciones descritas podemos usar algoritmos que resuelvan el problema del
árbol de expansión mínimo, dentro de la familia de los algoritmos voraces.
Descripción del esquema: Se elige cualquiera de los algoritmos expuestos en el temario Kruskal o Prim, por
ejemplo éste último.
función Prim(G : grafo) : T : conjunto de aristas
T ←∅
B ←1
mientras B ≠ N hacer {
buscar e = {u, v} de longitud minima tal que
u∈B y v∈ N \ B
T ← T ∪ {e}
B ← B ∪ {v}
}
dev T
ffunción
Hemos tomado 1 como nodo arbitrario. El conjunto B va a ir conteniendo los nodos del subgrafo ya conexo y el
conjunto T irá teniendo en cada iteración aquellas aristas del árbol de expansión mínimo que contiene los nodos
de B. El conjunto de candidatos es B, la condición de finalización es que B = N y la función de optimización es
elegir aquella arista del subgrafo B que conecte con algún nodo de N\B con menor coste.
Estructuras de datos: El grafo se representará mediante una matriz de costes. La estructura de datos tendrá un
método que implementa el cálculo de la distancia entre dos nodos.
En el caso de esta implementación la distancia entre dos nodos i y j es el valor de la matriz de distancias, y su
coste O(1).
Optimalidad: El algoritmo de Prim encuentra la solución óptima. Se puede demostrar por inducción
sobre T que añadir la arista más corta {e} que sale de T (Lema 6.3.1 del texto base) forma en
T ∪ {e} un árbol de recubrimiento mínimo que contendrá al final n-1 aristas y todos los nodos del
grafo G.
Aplicación al problema: Tenemos los siguientes conjuntos inicialmente B = {1} y la arista mínima
entre un nodo de B y otro de N\B es u = (1, 7) con valor 0.
3 4 5 1 3 7 6 2
Cuestión 2 (2 puntos). Con el criterio de tomar primero la moneda de mayor valor que sea menor o
igual que el cambio que queda por devolver, ¿existe un algoritmo voraz para el problema de devolver el
cambio con el menor número de monedas en cualquier sistema monetario? Justifique su respuesta.
(Respuesta: Fundamentos de Algoritmia, pag. 211 y Esquemas algorítmicos Pág. 12).
Cuestión 3 (1 puntos). ¿Se puede aplicar el procedimiento de búsqueda binaria sobre árboles con
estructura de montículo? Razonar la respuesta. (Respuesta: Fundamentos de Algoritmia, pag. 255, 179, 184).
Problema (5 puntos). Daniel se va de veraneo y tiene que decidir qué tesoros se lleva (el coche de
hojalata, el madelman, el pañuelo de María, etc). Su madre le ha dado una bolsa de Vb cm3 con la orden
expresa de que no se puede llevar nada que no quepa en la bolsa. Daniel tiene N objetos candidatos.
Cada objeto i ocupa un volumen Vi y tiene un valor sentimental Si para él. Se trata de llenar la bolsa,
maximizando el valor sentimental de los objetos que contiene. Evidentemente, Daniel no está dispuesto
a romper en pedazos sus tesoros.
3 4 5 1 3 7 6 2
Solución:
3 4 5 1 3 7 6 2
3 2 5 1 3 7 6 4
3 2 3 1 5 7 6 4
1 2 3 3 5 7 6 4
1 2 3 3 5 4 6 7
1 2 3 3 4 5 6 7
Cuestión 2 (2 puntos). Con el criterio de tomar primero la moneda de mayor valor que sea menor o
igual que el cambio que queda por devolver, ¿existe un algoritmo voraz para el problema de devolver el
cambio con el menor número de monedas en cualquier sistema monetario? Justifique su respuesta.
(Respuesta: Fundamentos de Algoritmia, pag. 211 y Esquemas algorítmicos Pág. 12).
No.
Para aplicar el esquema voraz, el problema ha de ser de optimización. Se debe distinguir un
conjunto de candidatos y que la solución pase por ir escogiéndolos o rechazándolos. La función de
selección debe asegurar que la solución alcanzada es la óptima. Si no, puede que hayamos
confundido el problema con uno de exploración de grafos. En el problema de las monedas, la
solución es óptima tomando el conjunto de monedas españolas (cuando se utilizaban pesetas, no euros
como ahora), es decir, C = {100, 25, 10 5 1}. Sin embargo, si tomamos las monedas inglesas en
peniques C = {30,24,12,6,3,1}, para un cambio de 48 peniques, nuestro algoritmo nos daría un conjunto
solución S = {30,12,6}, pero la solución óptima sería S = {24,24}.
Cuestión 3 (1 puntos). ¿Se puede aplicar el procedimiento de búsqueda binaria sobre árboles con
estructura de montículo? Razonar la respuesta. (Respuesta: Fundamentos de Algoritmia, pag. 255, 179, 184).
No. El motivo es que la ordenación de los nodos es distinta, a continuación haré una breve reseña de
cada uno:
Montículo:
Es un árbol binario esencialmente completo, cada nodo incluye un elemento de información denominado
valor del nodo y la propiedad de que el valor de cada nodo interno es mayor o igual que los valores de
sus hijos. Esto se llama propiedad de montículo. Se dice que un árbol binario es esencialmente completo
si todo nodo interno, con la posible excepción de un nodo especial, tiene exactamente dos hijos. El nodo
especial, si existe uno, está situado en el nivel 1 y posee un hijo izquierdo, pero no tiene hijo derecho.
Ejemplo de montículo:
10 7 9 4 7 5 2 2 1 6
Problema (5 puntos). Daniel se va de veraneo y tiene que decidir qué tesoros se lleva (el coche de
hojalata, el madelman, el pañuelo de María, etc). Su madre le ha dado una bolsa de Vb cm3 con la orden
expresa de que no se puede llevar nada que no quepa en la bolsa. Daniel tiene N objetos candidatos.
Cada objeto i ocupa un volumen Vi y tiene un valor sentimental Si para él. Se trata de llenar la bolsa,
maximizando el valor sentimental de los objetos que contiene. Evidentemente, Daniel no está dispuesto
a romper en pedazos sus tesoros.
1. Elección razonada del esquema algorítmico mas eficiente para resolver el problema.
Se trata de un problema de optimización, por lo tanto descarto divide y vencerás y vuelta atrás, ya que se
exige en el enunciado que no se pueden romper los tesoros, tengo que descartar el esquema voraz ya que
no se encuentra una función de selección que sea óptima para resolver el problema, se podría utilizar el
esquema voraz en el casos que se pudieran trocear los tesoros, eligiéndolos por orden decreciente de
vi/wi con un coste total de Θ ( nlogn ) . Por lo tanto elijo para resolver este problema el esquema de
Ramificación y Poda.
2. Estructuras de datos.
Montículo de mínimos
tipos
nodo = reg
sol[1..n]de 0..1
k:0..n
peso, beneficio:real
beneficio-opt:real {prioridad}
freg
ftipos
4. Algoritmo completo a partir del refinamiento del esquema general (1,5 puntos).
fun calculo-estimaciones (P[1..n], V[1..n] de real, M: real, k: 0..n, peso, beneficio:real) dev
(opt, pes: real)
hueco := M-peso;
pes := beneficio; opt := beneficio;
j := k+1
mientras j ≤ n ^ P[j] ≤ hueco hacer
{podemos coger el objeto j entero}
hueco := hueco-P[j]
opt := opt + V[j]; pes:= pes + V[j]
j := j+1 ;
fmientras
si j ≤ n entonces {quedan objetos por probar}
{fraccionamos el objeto j (solucion voraz)}
opt := opt + (hueco/P[j]*V[j]
{extendemos a una solución en la versión 0/1}
j := j+1
mientras j ≤ n ^ hueco > 0 hacer
si P[j] ≤ hueco entonces
hueco := hueco – P[j]
pes := pes + V[j]
fsi
j := j+1
fmientras
fsi
ffun
Cuestión 1 (2 puntos). ¿Qué significa que el tiempo de ejecución de un algoritmo está “en el
orden exacto de f(n)”?. Demostrar que T(n)=5·2n+n2 está en el orden exacto de 2n.
Cuestión 2 (2 punto). ¿Qué diferencias hay entre un montículo y un árbol binario de búsqueda?
Cuestión 3 (2 puntos). ¿Cuáles son los casos mejor y peor para el algoritmo de ordenación
rápida (Quicksort)? ¿Cuál es el orden de complejidad en cada uno de ellos? Razona tu
respuesta.
Problema (4 puntos). Hoy es un día duro para el taller Sleepy. Llegan las vacaciones y a las
8:00 de la mañana n clientes han pedido una revisión de su coche. Como siempre, todos
necesitan que les devuelvan el coche en el menor tiempo posible. Cada coche necesita un
tiempo de revisión ri y al mecánico le da lo mismo por cuál empezar: sabe que en revisar todos
los coches tardará lo mismo independientemente del orden que elija. Pero al jefe de taller no le
da lo mismo, la satisfacción de sus clientes es lo que importa: es mejor tener satisfechos al
mayor número de ellos. Al fin y al cabo, la planificación la hace él y, evidentemente, un cliente
estará más satisfecho cuanto menos tarden en devolverle el coche. Implementar un programa
que decida el orden en el que revisar uno a uno los coches para maximizar la satisfacción de los
clientes de Sleepy.
Cuestión 1 (2 puntos). ¿Qué significa que el tiempo de ejecución de un algoritmo está “en el
orden exacto de f(n)”?. Demostrar que T ( n ) = 5 ⋅ 2n + n 2 está en el orden exacto de 2n .
Θ ( f ( n )) = O ( f ( n )) ∩ Ω ( f ( n))
f ( n)
1. Si lim ∈ℜ+ , entonces f ( n ) ∈ Θ ( g ( n ) )
n →∞ g ( n)
f (n)
2. Si lim ∈ 0 , entonces f ( n ) ∈ O ( g ( n ) ) pero f ( n ) ∉ Θ ( g ( n ) )
n →∞ g ( n)
f ( n)
3. Si lim ∈ +∞ , entonces f ( n ) ∈ Ω ( g ( n ) ) pero f ( n ) ∉ Θ ( g ( n ) )
n →∞ g ( n)
5 ⋅ 2n log ( 2 )
3
lim =5
n →∞
2n log ( 2 )
3
Por lo tanto: f ( n ) ∈ Θ ( g ( n ))
Cuestión 2 (2 puntos). ¿Qué diferencias hay entre un montículo y un árbol binario de búsqueda?
20 ≥ 12 12 20 ≤ 34
34
34 ≥ 27
12 ≥ 6 6 18 27 35 34 ≤ 35
12 ≤ 18
Montículo:
Es un árbol binario esencialmente completo, cada nodo incluye un elemento de información denominado
valor del nodo y la propiedad de que el valor de cada nodo interno es mayor o igual que los valores de
sus hijos. Esto se llama propiedad de montículo.
Se dice que un árbol binario es esencialmente completo si todo nodo interno, con la posible excepción
de un nodo especial, tiene exactamente dos hijos.
El nodo especial, si existe uno, está situado en el nivel 1 y posee un hijo izquierdo, pero no tiene hijo
derecho.
Todas las hojas se encuentran en el nivel 0 ó bien están en los niveles 0 y 1 y ninguna hoja del nivel 1
está a la izquierda de un nodo interno del mismo nivel.
Ejemplo de montículo:
10 7 9 4 7 5 2 2 1 6
Cuestión 3 (1 puntos). ¿Cuáles son los casos mejor y peor para el algoritmo de ordenación rápida
(Quicksort)? ¿Cual es el orden de complejidad en cada uno de ellos? Razona tu respuesta. Página 261.
Si la matriz que hay que ordenar se encuentra inicialmente en orden aleatorio, entonces es probable que
la mayoría de los subejemplares que haya que ordenar estén suficientemente bien equilibrados.
En el caso peor si la matriz se encuentra ordenada implica una llamada recursiva a un caso de tamaño
cero y otra a un caso cuyo tamaño sólo se reduce en una unidad.
Para el mejor caso el orden de complejidad es O ( nlogn ) .
Para el peor caso el orden de complejidad es Ω ( n 2 ) .
Problema (4 puntos). Hoy es un día duro para el taller Sleepy. Llegan las vacaciones y a las 8:00 de la
mañana n clientes han pedido una revisión de su coche. Como siempre, todos necesitan que les
devuelvan el coche en el menor tiempo posible. Cada coche necesita un tiempo de revisión ri y al
mecánico le da lo mismo por cuál empezar: sabe que en revisar todos los coches tardará lo mismo
independientemente del orden que elija. Pero al jefe de taller no le da lo mismo, la satisfacción de sus
clientes es lo que importa: es mejor tener satisfechos al mayor número de ellos. Al fin y al cabo, la
planificación la hace él y, evidentemente, un cliente estará más satisfecho cuanto menos tarden en
devolverle el coche. Implementar un programa que decida el orden en el que revisar uno a uno los
coches para maximizar la satisfacción de los clientes de Sleepy.
Elección razonada del esquema algorítmico más eficiente para resolver el problema.
Se trata de un problema de optimización, por lo tanto descarto el esquema de divide y vencerás, para
este problema se puede encontrar una función de selección que lo resuelva, de esta manera descarto el
esquema de ramificación y poda, debemos elegir a los clientes por orden creciente de tiempo de
finalización, este problema se corresponde con el problema de minimización del tiempo en el sistema.
Esquema general:
fun voraz (C: conjunto) dev (S: conjunto)
S ← ∅
mientras ¬ solucion (S) ∧ C ≠ ∅ hacer
x ← seleccionar (C)
C ←C \ {x}
si completable (S ∪ {x})
entonces S ← S ∪ {x}
fsi
dev S
ffun
Estructuras de datos:
Tres vectores de enteros, un vector booleano y la función auxiliar ordenar índices.
Cuestión 1 (2 puntos). Programar Una función “potencia (n,m)” que halle nm, Se
supone que no existe la operación potencia y que el coste de una operación de
multiplicación es Ο (1) , mediante divide y vencerás con coste en Θ ( log n ) . (Respuesta:
1 2 3 4 5
1 . 4 5 ∞ 9
2 . . 6 ∞ 1
3 . . . 3 1
4 . . . . ∞
5 . . . . .
Problema (5 puntos). Se tiene una matriz de n x n cuyas casillas contienen valores del
conjunto C = {rojo, verde, amarillo} excepto una que es ‘blanco’. El único movimiento
permitido consiste en que cada casilla de valor ‘blanco’ puede intercambiarse por
cualesquiera de sus adyacentes no diagonales. Una solución es una tabla con una fila
llena de valores iguales. Desarrollar un algoritmo que dada una matriz inicial averigüe
cuantas soluciones se pueden encontrar aplicando movimientos permitidos a partir de
aquella.
Cuestión 1 (2 puntos). Programar Una función “potencia (n,m)” que halle nm, Se supone que no
existe la operación potencia y que el coste de una operación de multiplicación es Ο (1) , mediante
divide y vencerás con coste en Θ ( log n ) . (Respuesta: Fundamentos de Algoritmia, pag. 276 y EDMA 342).
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b
log a
) , si a > b k
( )
a = 1, b = 2, k = 0 → T ( n ) ∈ Θ n k log n → T ( n ) ∈ Θ ( log n )
Cuestión 2 (2 puntos). Se tienen 4 productos infraccionables p1, p2, p3, p4 en cantidades ilimitadas
cuyo beneficio es respectivamente 23, 12, 21, 11 € y cuyos pesos son respectivamente 2,4,3,5 Kgs.
Tenemos un camión que carga un máximo de 55 Kgs. El problema consiste en llenar un camión con la
carga de mayor valor. ¿Qué algoritmo nos permite resolver el problema? Aplicando al enunciado y
detallarlo paso por paso.
p1 p2 p3 p4
wi 2 4 3 5
vi 23 12 21 1
Ya que el enunciado impone que los productos sean infraccionables aplicaré el algoritmo de Vuelta
Atrás. Lo Primero que haré ya que el algoritmo me permite ordenar los objetos en cualquier orden los
ordenaré por orden decreciente de su valor/peso ya que tras hacer varias comprobaciones con más
ejemplos funciona igual pero reduzco el tamaño del árbol. Quedando de la siguiente manera:
p1 p3 p2 p4
wi 2 3 4 5
vi 23 21 12 1
vi wi 11,5 7 3 0,2
Inicialmente la solución parcial está vacía, el algoritmo de vuelta atrás explora el árbol como un
recorrido en profundidad, construyendo nodos y soluciones parciales a medida que avanza. Haré un
pequeño gráfico. Con la siguiente notación: cada cuadro es un nodo, los números a la izquierda del
punto y coma son los pesos de los objetos y los de la derecha el valor de la carga, que es lo que
pretendemos optimizar.
;0
2; 23
2,..,2; 598
2,..,2,3; 619
Inicio el recorrido en profundidad hasta que tengo 27 pesos con 2 que hace un total de 54, por esta
rama ya no puedo continuar ya que me pasaría del peso en una unidad 56 y me exigen 55, por lo tanto
retrocedo al nodo anterior y sigo la búsqueda por la siguiente rama que se corresponde con 26 pesos
con 2 y 1 peso con 3 que hacen un total de 55 Kg. tal como me piden y un valor de 619 € que resulta la
solución óptima para este ejemplo.
Cuestión 3 (2 puntos). Resolver mediante el algoritmo de Kruskal el árbol de expansión mínimo del
grafo definido por la siguiente matriz de adyacencias:
(Respuesta: Fundamentos de Algoritmia, pag. 217, 218).
1 2 3 4 5 1
1 . 4 5 ∞ 9
2 . . 6 ∞ 1
3 . . . 3 1
4 . . . . ∞ 9 4 5
4
5 . . . . .
2
3
1 6
5 1 3
(2,5),(3,5),(3,4),(1,2)
EVOLUCION EVOLUCION
PASO ARISTA
COMPONENTES SOLUCION
INICIO - {1}{2}{3}{4}{5} ∅
1 (2,5) {1}{2,5}{3}{4} {(2,5)}
2 (3,5) {1}{2,3,5}{4} {(2,5), (3,5)}
3 (3,4) {1}{2,3,4,5} {(2,5), (3,5),( 3,4)}
4 (1,2) {1,2,3,4,5} {(2,5), (3,5),( 3,4),( 1,2)}
Proceso terminado porque sólo queda una única componente conexa
Problema (4 puntos). Se tiene una matriz de n x n cuyas casillas contienen valores del conjunto
C = {rojo, verde, amarillo} excepto una que es ‘blanco’. El único movimiento permitido consiste en
que cada casilla de valor ‘blanco’ puede intercambiarse por cualesquiera de sus adyacentes no
diagonales. Una solución es una tabla con una fila llena de valores iguales. Desarrollar un algoritmo
que dada una matriz inicial averigüe cuantas soluciones se pueden encontrar aplicando movimientos
permitidos a partir de aquella.
Cuestión 1 (1 punto). ¿En qué orden está el tiempo de ejecución del siguiente algoritmo?
Justifica tu respuesta.
Procedimiento h(n,i, j)
si n > 0 entonces
h(n - 1,i,6 - i - j);
escribir i " → " j;
h(n - 1,6 - i - j, j);
fsi;
Cuestión 3 (2 puntos). ¿Cuándo resulta más apropiado utilizar una exploración en anchura
que en profundidad?
Problema (5 puntos). Dado un grafo dirigido, finito, con ciclos y con todas las aristas de coste
unitario, implementar un algoritmo que devuelva el número de nodos que contiene el camino
más largo sin ciclos que se puede recorrer desde un determinado nodo.
Cuestión 1 (1 punto). ¿En qué orden está el tiempo de ejecución del siguiente algoritmo?
Justifica tu respuesta.
Procedimiento h(n,i, j)
si n > 0 entonces
h(n - 1,i,6 - i - j);
escribir i " → " j;
h(n - 1,6 - i - j, j);
fsi;
¿En que casos puede que la exploración en anchura no encuentre solución aunque ésta exista?
- Que exista una solución quiere decir que existe un camino desde el nodo inicial al nodo final.
No tiene sentido, entonces, hablar de que la “solución” esté en una componente conexa
diferente. Por otro lado, el caso en que un nodo genere infinitos hijos es un caso excepcional
que no debería darse. Por todo ello, y salvo esta última excepción, la exploración en anchura
siempre encuentra la solución, si ésta existe.
- Si existen ramas infinitas sin nodos finales puede que la exploración en profundidad quede
atrapada en una de ellas y no encuentre solución aunque ésta exista.
Problema (5 puntos). Dado un grafo dirigido, finito, con ciclos y con todas las aristas de coste
unitario, implementar un algoritmo que devuelva el número de nodos que contiene el camino
más largo sin ciclos que se puede recorrer desde un determinado nodo.
Hallar el camino más largo desde un nodo en un grafo finito puede verse como un problema de
determinar cuál es la profundidad máxima del árbol. Para ello, será necesario recorrer todos los
caminos sin ciclos del grafo, almacenando la longitud del más largo encontrado hasta el
momento. El esquema de búsqueda exhaustiva en profundidad nos permite resolver este
problema.
En la página 330 del libro de texto (Brassard, 1997) se presenta el esquema de recorrido en
profundidad:
procedimiento rp(nodo)
{nodo no ha sido visitado anteriormente}
visitado[nodo] := cierto;
para cada hijo en adyacentes[nodo] hacer
si visitado[nodo] = falso entonces
rp(hijo)
fsi
fpara
Así planteado, un nodo no se exploraría 2 veces aunque llegáramos a él desde una rama
distinta. Esto es incorrecto y se debe a que el vector de nodos visitados se plantea de forma
global. Veamos un ejemplo:
1
La exploración llega al nodo 1 y lo marca como visitado.
Análogamente sigue marcando como visitados los nodos 2, 3 y 4,
5 hallando un camino de longitud 4. Cuándo el control regresa al nodo 1
para explorar ramas alternativas, se explora el nodo 5, el 6, pero
2
cuando llega al nodo 3, se detiene la exploración porque 3 está
marcado como visitado. El nuevo camino encontrado tiene 4 nodos en
6
vez de 5 que sería lo correcto.
3
Este problema lo podemos corregir si el vector de visitados tiene un
ámbito local en vez de global, y se pasa una copia de padres a hijos
(paso de parámetro por valor).
4
Estructuras de datos
Necesitaremos una estructura para grafo. La más compacta y sencilla de usar es un vector en el
que la componente i tiene un puntero a lista de nodos adyacentes al nodo i. Por tanto, también
necesitaremos implementar una lista de nodos que puede ser, simplemente, una lista de
enteros.
Algoritmo completo
La llamada inicial se realiza con el nodo inicial, y los elementos del vector de visitados
inicializados a falso.
Sea n el número de nodos del grafo. Si suponemos un grafo denso (en el que todos los nodos
están interconectados entre sí), tenemos que la longitud del camino máximo será n. Cada
llamada recursiva, entonces, supondrá una reducción en uno del problema. Así mismo, el
número de llamadas recursivas por nivel también ira disminuyendo en uno puesto que en cada
nivel hay un nodo adyacente más que ya ha sido visitado. Por último, dentro del bucle se
realizan al menos n comparaciones. Por tanto, podemos establecer la siguiente recurrencia:
T(n)=(n-1)·T(n-1)+n
número de llamadas: a=n-1
reducción por sustracción: b=1
c·nk=n, k=1
Aplicando la recurrencia para el caso cuando a>1: T(n)∈O(an div b); T(n)∈O(nn)
Al mismo resultado se puede llegar razonando sobre la forma del árbol de exploración.
RESPUESTAS EXAMEN Programación III. Septiembre 2004 (Original)
Cuestión 1 (2 puntos). ¿Qué significa que el tiempo de ejecución de un algoritmo está “en el orden exacto
de f(n)”? Demostrar que T(n)= n 3 + 9·n 2 ·log(n) está en el orden exacto de n3 .
f (n)
lim ∈ ℜ+ , entonces f ( n ) ∈ Θ ( g ( n ) )
n →∞ g (n)
según la relación del apartado 3.3 (Ver p. 100 del texto base) y además aplicamos el teorema de L´Hopital
(p. 38) obtenemos que:
f (n) f ′( n)
lim = lim con lo que:
n →∞ g ( n ) n→∞ g ′ ( n )
= lim
( 6n+18 ) n
= lim
( 6n+18) = 1
n →∞ 6 n →∞ 6n
También será válido hallar las constantes c y d según la definición de la notación Theta (p.100)
Cuestión 2 (2 puntos). Implementar una versión recursiva de una función que tome un vector y le dé
estructura de montículo.
En primer lugar difieren en la forma de crear el camino mínimo. En el caso de Prim la solución es siempre
un ARM y en el otro caso, lo son las componentes conexas pero sin referencia al grafo inicial, salvo al
final del mismo.
En segundo lugar en términos de coste, el algoritmo de Kruskal requiere un tiempo que está en
Θ(a· logn) con a el número de aristas, por lo que en el caso peor si el grafo es denso y a se acerca a n 2 ,
entonces es menos eficiente que el de Prim que es cuadrático, ocurriendo lo contrario para grafos
dispersos.
Implementar una función que escriba la secuencia de operaciones con menor coste total que hay
que realizar sobre el vector consulta para que coincida exactamente con el vector texto. La
función devolverá el coste total de estas operaciones, es decir, la suma de los costes asociados
a cada operación realizada.
3. Estructuras de datos
• Vector de texto
• Vector de consulta
• Montículo de mínimos en el que cada componente almacene una solución parcial (nodo)
con su cota correspondiente.
• nodo=tupla
acciones: lista de Strings;
último_coincidente: cardinal;
long: cardinal;
coste: cardinal;
4. Algoritmo completo
inicializarSolución
La solución inicial debe tener un coste asociado mayor o igual que la solución final. De
acuerdo con el problema, basta con realizar sustituciones de los caracteres de la consulta
(coste=2*long_consulta) y realizar la inserción de los caracteres que faltan
(coste=3*(long_texto-long_consulta)). Además tenemos que construir la solución inicial: la
secuencia de sustituciones e inserciones.
válido(nodo)
Un nodo será válido (como solución) si se han hecho coincidir los long_texto caracteres
de texto. Para ello, vamos a ir haciendo coincidir la solución desde el principio del vector
hasta el final. por tanto, un nodo será válido (aunque no sea la mejor solución todavía) si:
nodo.último_coincidente==long_texto
es_mejor(cota, valor_sol_actual)
Como se trata de encontrar la solución de menor coste, una cota será mejor que el valor
de la solución actual si:
cota<valor_sol_actual
acotar(nodo)
Se trata de realizar una estimación que sea mejor o igual que la mejor solución que se
puede alcanzar desde ese nodo. De esta manera sabremos que si, aún así, la estimación
es peor que la solución actual, por esa rama no encontraremos nada mejor y se puede
podar. La mejor estimación será sumar al coste ya acumulado, el menor coste que podría
tener completar la solución: que el resto de caracteres de consulta coincidieran (coste=0)
y realizar tantas inserciones como caracteres nos falten (coste=2*(long_texto-nodo.long)).
Es decir, acotar(nodo) es:
nodo.coste+2*(long_texto-nodo.long)
compleciones(nodo)
Sabiendo que hasta nodo.último_coincidente todos los caracteres coinciden, se trata de
considerar las posibilidades para que el siguiente carácter de la consulta coincida con el
texto:
• No hacer nada si ya coinciden de antemano.
• Intercambiarlo por el siguiente si éste coincide con el texto.
• Sustituirlo por el correspondiente del texto
• Insertar el correspondiente del texto y correr el resto del vector, siempre que
la consulta no haya alcanzado el tamaño del texto.
Si se da el primer caso, no es necesario generar el resto de compleciones porque no se
puede encontrar una solución mejor con ninguna de las otras alternativas. Aún así, no
pasaría nada si se incluyen. Sin embargo, para el resto de alternativas, no hay garantías
de que una permita encontrar mejor solución que otra, aunque el coste de la acción
puntual sea menor. Por tanto, hay que incluir todas las alternativas.
En este caso únicamente podemos hallar una cota superior del coste del algoritmo por
descripción del espacio de búsqueda. En el caso peor se generan 3 hijos por nodo hasta llegar a
una profundidad de long_texto. Por tanto, el espacio a recorrer siempre será menor que 3long_texto.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (2 puntos). Defina qué es un árbol de recubrimiento mínimo, explique alguno de los
algoritmos propuestos en la asignatura para hallarlos, aplicándolo paso a paso a un ejemplo.
(Respuesta: Fundamentos de Algoritmia, pags. 215-223).
Cuestión 2 (2 puntos). Sea T [1..12] una matriz tal que T [i ] = i para todo i ≤ 12 .Crear un montículo en
tiempo lineal, especificando claramente cada paso y mostrando en todo momento el estado de la matriz T.
(Respuesta: Fundamentos de Algoritmia, pag. 189).
PROBLEMA (5 puntos). Sea una red de compartición de ficheros, similar a las que actualmente se
utilizan para intercambiar globalmente archivos por internet. Esta red se encuentra formada por n
servidores, siendo todos ellos capaces de distribuir un número n de archivos, de tamaño Ti Kilobytes, a
diferentes velocidades de transmisión. La velocidad de transmisión de datos de cada uno de los servidores
viene determinada por una tabla de velocidades de transmisión S, donde, Sij es la velocidad de transmisión
del servidor i para el archivo j (en K/seg). Se pide diseñar un algoritmo capaz de repartir la descarga de los
n archivos entre los n servidores disponibles, minimizando el tiempo global de descarga de todos los
archivos. La función deberá indicar el tiempo óptimo de descarga, así como los servidores desde los que
serán descargados los n archivos. Suponga que la descargase lleva a cabo de manera secuencial, lo que
significa que no es posible descargar más de un archivo al mismo tiempo.
Tome como ejemplo ilustrativo de los datos de entrada una red de compartición de 3 ficheros (A1, A2,
A3) en 3 servidores diferentes (S1, S2, S3). El tamaño de los 3 ficheros es T1=100K, T2=200K, T3=300K
y la velocidad de transmisión de los 3 servidores viene dado por la matriz:
Cuestión 1 (2 puntos). Defina qué es un árbol de recubrimiento mínimo, explique alguno de los
algoritmos propuestos en la asignatura para hallarlos, aplicándolo paso a paso a un ejemplo.
(Respuesta: Fundamentos de Algoritmia, pags. 215-223).
Dado un grafo, debemos obtener un nuevo grafo que sólo contenga las aristas imprescindibles para que
todos los nodos queden conectados y la suma de las longitudes de las aristas de del nuevo grafo debe ser
tan pequeña como sea posible, se aplica a problemas que tienen que ver con distribuciones geográficas
Hay dos algoritmos que podemos utilizar para resolver este tipo de problemas: Kruskal y Prim.
KRUSKAL:
Partimos del conjunto de aristas T que inicialmente está vacío, se selecciona en cada paso la arista de
menor etiqueta que no provoque ciclo, es decir en orden creciente de longitud de sus aristas. El algoritmo
se detiene cuando solo queda una componente conexa.
EVOLUCION EVOLUCION
PASO ARISTA
COMPONENTES SOLUCION
INICIO - {1}{2}{3}{4}{5}{6}{7} ∅
1 (1,2) {1,2}{3}{4}{5}{6}{7} {(1,2)}
2 (2,3) {1,2,3}{4}{5}{6}{7} {(1,2), (2,3)}
3 (4,5) {1,2,3}{4,5}{6}{7} {(1,2), (2,3),( 4,5)}
4 (6,7) {1,2,3}{4,5}{6,7} {(1,2), (2,3),( 4,5),( 6,7)}
5 (1,4) {1,2,3,4,5}{6,7} {(1,2), (2,3),( 4,5),( 6,7),( 1,4)}
Se rechaza porque los extremos de la arista ya están en la misma comp. conexa
6 (2,5)
{1,2,3,4,5,6,7} {(1,2), (2,3),( 4,5),( 6,7),( 1,4),( 4,7)}
7 (4,7)
PRIM:
En este algoritmo el árbol de recubrimiento mínimo crece de forma natural, comenzando por una raíz
arbitraria, en cada fase se añade una nueva rama al árbol construido con el valor más pequeño posible, el
árbol se detiene cuando se han alcanzado todos los nodos.
EVOLUCION
PASO ARISTA EVOLUCION SOLUCION
COMPONENTES
INICIO - {1} ∅
1 (1,2) {1,2} {(1,2)}
2 (2,3) {1,2,3} {(1,2), (2,3)}
3 (1,4) {1,2,3,4} {(1,2), (2,3),(1,4)}
4 (4,5) {1,2,3,4,5} {(1,2), (2,3),(1,4),(4,5)}
5 (4,7) {1,2,3,4,5,7} {(1,2), (2,3),(1,4),(4,5),(4,7)}
6 (7,6) {1,2,3,4,5,6,7} {(1,2), (2,3),(1,4),(4,5),(4,7),(7,6)}
Estos algoritmos difieren en la forma de crear el camino mínimo. En el caso de Prim la solución es
siempre un AEM y en el otro caso, lo son las componentes conexas pero sin referencia al grafo inicial,
salvo al final del mismo.
En segundo lugar en términos de coste, el algoritmo de Kruskal requiere un tiempo que está en
Θ(a ⋅ logn) con a el número de aristas, por lo que en el caso peor si el grafo es denso y a se tiende a
n ( n - 1) 2 , con un tiempo que está en Θ ( n 2logn ) entonces es menos eficiente que el de Prim que es
cuadrático, para un grafo disperso a tiende a n por lo que Kruskal requiere un tiempo que está en
Θ ( nlogn ) y el algoritmo de Prim es menos eficiente en este caso. Los dos algoritmos se pueden
implementar con montículos, en este caso los dos algoritmos están en Θ ( a ⋅ logn )
Cuestión 2 (2 puntos). Sea T [1..12] una matriz tal que T [i ] = i para todo i ≤ 12 .Crear un montículo en
tiempo lineal, especificando claramente cada paso y mostrando en todo momento el estado de la matriz T.
(Respuesta: Fundamentos de Algoritmia, pag. 189).
Después de realizar estas operaciones se puede observar que el vector tiene estructura de montículo de
mínimos.
Cuestión 3 (2 puntos). Se dispone de n productos diferentes, infraccionables y en cantidades ilimitadas.
Cada tipo de producto tiene asociado un peso y un beneficio concreto. Deseamos cargar un camión, que
puede transportar un peso máximo PMAX, maximizando el beneficio de los productos transportados por
este. ¿Sería posible aplicar un algoritmo voraz a este planteamiento? Responder razonadamente y plantear
un ejemplo que justifique la respuesta. (Respuesta: Fundamentos de Algoritmia, Págs. 227, 343, 353).
NO.
La variante del problema de la mochila que admite solución voraz es la variante continua, donde podemos
fragmentar los objetos. Esta variante admite solución voraz porque encontramos una función de selección
que nos permite escoger del candidato a cada paso de forma que obtengamos una solución óptima. Dicha
función consiste en escoger los objetos por orden decreciente (de mayor a menor) según su relación
valor/peso, lo que nos lleva a una solución óptima.
La variante que no admite solución óptima es la que no nos permite fragmentar los objetos, veamos esto
con un ejemplo. Dada la siguiente relación de objetos valor-peso para una mochila de capacidad 10
(W=10, peso máximo de la mochila) .
a b c
wi 6 5 5
vi 8 5 5
Según el algoritmo voraz de la variante continua tomaríamos los objetos según su orden decreciente en
función de la relación valor/peso. De este modo tendríamos: a (vi/wi = 1,333) , b (vi/wi= 1) y c(vi/wi=1) .
La sucesión a, b y c.
Sin embargo como en esta ocasión NO podemos fragmentar los objetos al introducir en la mochila el
objeto a de peso 6 ya no podemos introducir ninguno más, el valor conseguido será entonces de 8;
mientras que si introducimos primero el objeto b, queda aún espacio para el objeto c, con lo que en esta
ocasión hemos utilizado el peso total máximo de la mochila y el valor conseguido es de 10.
Siendo esta la solución óptima. Vemos con este ejemplo como el criterio seguido en la variante continua
del problema no puede aplicarse en el caso en el que los objetos no puedan fragmentarse.
PROBLEMA (5 puntos). Sea una red de compartición de ficheros, similar a las que actualmente se utilizan para
intercambiar globalmente archivos por internet. Esta red se encuentra formada por n servidores, siendo todos ellos
capaces de distribuir un número n de archivos, de tamaño Ti Kilobytes, a diferentes velocidades de transmisión. La
velocidad de transmisión de datos de cada uno de los servidores viene determinada por una tabla de velocidades de
transmisión S, donde, Sij es la velocidad de transmisión del servidor i para el archivo j (en K/seg). Se pide diseñar un
algoritmo capaz de repartir la descarga de los n archivos entre los n servidores disponibles, minimizando el tiempo
global de descarga de todos los archivos. La función deberá indicar el tiempo óptimo de descarga, así como los
servidores desde los que serán descargados los n archivos. Suponga que la descargase lleva a cabo de manera
secuencial, lo que significa que no es posible descargar más de un archivo al mismo tiempo.
Tome como ejemplo ilustrativo de los datos de entrada una red de compartición de 3 ficheros (A1, A2, A3) en 3
servidores diferentes (S1, S2, S3). El tamaño de los 3 ficheros es T1=100K, T2=200K, T3=300K y la velocidad de
transmisión de los 3 servidores viene dado por la matriz:
Se trata de un problema de optimización con restricciones. Por tanto, podría ser un esquema voraz o un
esquema de ramificación y poda. Sin embargo descartamos el esquema voraz porque no es posible
encontrar una función de selección y de factibilidad tales que una vez aceptado un candidato se garantice
que se va alcanzar la solución óptima. Se trata, por tanto, de un algoritmo de ramificación y poda.
nodo=tupla
asignaciones: vector[1..N];
último_asignado: cardinal;
filas_no_asignadas: lista de cardinal;
coste: cardinal;
Las funciones generales del esquema general que hay que instanciar son:
1. a. solución(nodo): si se han realizado N asignaciones (último_asignado==N)
2. b. acotar(nodo,costes): nodo.coste + “mínimo coste de las columnas no asignadas”
3. c. compleciones(nodo,costes): posibilidades para la siguiente asignación (valores posibles para
asignaciones[último_asignado+1])
4.
Función asignación(costes[1..N,1..N]) dev solución[1..N]
Montículo:=montículoVacío();
nodo.último_asignado=0;
nodo.coste=0;
cota:=acotar(nodo,costes);
poner((cota,nodo),Montículo);
mientras no vacío(Montículo) hacer
(cota,nodo):=quitarPrimero(Montículo);
si nodo.último_asignado==N entonces
devolver nodo.asignaciones;
si no para cada hijo en compleciones(nodo,costes) hacer
cota:=acotar(hijo,costes);
poner((cota,hijo),Montículo);
fsi;
fmientras
devolver ∅;
Función acotar(nodo,costes[1..N,1..N]) dev cota
cota:=nodo.coste;
para columna desde nodo.último_asignado+1 hasta N hacer
minimo=∞;
para cada fila en nodo.filas_no_asignadas hacer
si costes[columna,fila]<mínimo entonces
mínimo:=costes[columna,fila];
fsi
fpara
cota:=cota+mínimo;
fpara
devolver cota;
Coste:
En este caso únicamente podemos hallar una cota superior del coste del algoritmo por descripción del
espacio de búsqueda. En el caso peor se generan (k-1) hijos por cada nodo del nivel k, habiendo n. Por
tanto, el espacio a recorrer siempre será menor que n!.
Programación III
Primera Semana
Febrero 2005
Códigos de asignatura:
Sistemas: 402048; Gestión: 41204-
UNIVERSIDAD NACIONAL Duración: 2 horas
DE EDUCACIÓN A DISTANCIA Prueba Presencial Material permitido: NINGUNO
Cuestión 1 (2 puntos).
Se dispone de un conjunto de ficheros f1, f2, ..., fn con tamaños I1, I2, ..., In y de un disquete con una capacidad total
de almacenamiento de d<I1+I2+...+In. a) Suponiendo que se desea maximizar el número de ficheros almacenados y
que se hace uso de un planteamiento voraz basado en la selección de ficheros de menor a mayor tamaño, ¿el
algoritmo propuesto siempre obtendría la solución óptima?. En caso afirmativo demostrar la optimalidad y en caso
negativo ponga un contraejemplo; b) En caso de que quisiéramos ocupar la mayor cantidad de espacio en el
disquete independientemente del número de ficheros almacenados, ¿una estrategia voraz basada en la selección
de ficheros de mayor a menor tamaño obtendría en todos los casos la solución óptima?. En caso afirmativo
demuestre la optimalidad, en caso negativo ponga un contraejemplo.
Solución:
Apartado A
El algoritmo voraz obtendría la solución óptima, es decir, un disquete con el mayor número posible de ficheros.
Demostración: Suponiendo que los programas se encuentran inicialmente ordenados de menor a mayor tamaño,
vamos a demostrar la optimalidad de la solución comparando una solución óptima con una solución obtenida por el
algoritmo voraz. Si ambas soluciones no fueran iguales, iremos transformando la solución óptima de partida, de
forma que continúe siendo óptima, pero asemejándola cada vez más con la obtenida por el algoritmo voraz. Si
consiguiéramos igualar ambas soluciones en un número finito de pasos, entonces podremos afirmar que la solución
obtenida por el algoritmo es óptima.
Notación: Una solución cualquiera viene representada por Z=(z1, z2, ..., zn)
donde zi=0 implica que el fichero fi no ha sido seleccionado como parte de la
n
solución. De este modo, ∑z
i =1
i indicará el número de ficheros seleccionados
Siendo X la solución devuelta por la estrategia voraz e Y la solución óptima al problema. Supongamos que la
estrategia voraz propuesta selecciona los k primeros ficheros (con 1 = k = n), recordamos que los ficheros se
encuentran inicialmente ordenados de menor a mayor tamaño. El fichero k+1 es rechazado puesto que ya no es
posible incluir un solo fichero más. De este modo, la solución X será (x1, x2, ..., xk,..., xn), donde . i . {1..k }.x i = 1 y
. i . {k + 1..n}.x i = 0 .
Comenzando a comparar X con Y de izquierda a derecha, supongamos que j = 1 sea la primera posición donde
xj . yj . En este caso, obligatoriamente j = k ya que en caso contrario la solución óptima incluiría todos los ficheros
escogidos por la estrategia voraz y alguno más, lo que se contradice con el hecho de que los ficheros del k+1 al n
se rechazan por la estrategia voraz porque no caben.
j
Este modo, xj . yj implica que yi=0, por lo que ∑y
i =1
i = j − 1 , que es menor que el número de ficheros seleccionados
j
por nuestra estrategia voraz como solución al problema ∑x
i =1
i = j . Como suponemos que Y es una solución óptima,
n n
∑
i =1
yi = ∑x
i =1
i = k , esto significa que existe un l > k = j tal que yl = 1, es decir, existe un fichero posterior para
compensar el que no se ha cogido antes. Por la ordenación impuesta a los ficheros, sabemos que lj = ll, es decir,
que si fl cabe en el disco, podemos poner en su lugar fj sin sobrepasar la capacidad total. Realizando este cambio
en la solución óptima Y, obtenemos otra solución Y’ en la cual y’j= 1 = xj, y’l=0 y para el resto y’i = yi. Esta nueva
solución es más parecida a X, y tiene el mismo número de ficheros que Y’, por lo que sigue siendo óptima.
Repitiendo este proceso, podemos ir igualando los ficheros en la solución óptima a los de la solución voraz X, hasta
alcanzar la posición k.
Apartado B
Fichero F1 F2 F3
Tamaño 40 30 15
Aplicando la estrategia voraz propuesta, únicamente podríamos almacenar el fichero F1, ocupando 40 de los 45 de
capacidad que tiene el disquete. Sin embargo, si hubiéramos seleccionado los ficheros F2 y F3 hubiera sido posible
maximizar el espacio ocupado en el disco.
Cuestión 2 (2 punto).
Exponga y explique el algoritmo más eficiente que conozca para realizar una planificación de tareas con plazo fijo
maximizando el beneficio.
Dada la tabla adjunta de tareas con sus beneficios (gi) y caducidades (di) asociados. Aplique paso a paso el
algoritmo propuesto, suponiendo que se desea realizar una planificación en un tiempo t=5.
i 1 2 3 4 5 6 7 8 9
gi 30 10 2 11 10 9 2 56 33
di 5 3 2 2 1 2 7 5 4
Solución:
Apartado A
El algoritmo más apropiado es el algoritmo secuencia2 explicado y desarrollado en pg 240-241 del texto base y que
ha sido utilizado en la práctica (Bloque 1). El alumno debe haberlo expuesto y explicado.
Apartado B
i 8 9 1 4 5 2 6 3 7
gi 56 33 30 11 10 10 9 2 2
di 5 4 5 2 1 3 2 2 7
Tabla de costes y caducidades ordenada
El proceso puede esquematizarse del siguiente modo:
0 1 2 3 4
0 1 2 3 4 5
Res[]
0 1 2 3 4 5
Selecciono tarea 8
0 1 2 3 4
0 1 2 3 4 0 1 2 3 4
Res[] 8
5
Selecciono tarea 9
0 1 2 3
0 1 2 3 0 1 2 3 4
Res[] 9 8
4
5
Selecciono tarea 1
0 1 2
0 1 2 0 1 2 3 4
Res[] 1 9 8
3
Selecciono tarea 4
0 1
0 1 0 1 2 3 4
Res[] 4 1 9 8
2
5
Cuestión 3 (1 puntos).
Explique las diferencias entre un recorrido en anchura y un recorrido en profundidad. Exponga un
algoritmo iterativo para cada uno de los recorridos explicando las estructuras de datos asociadas, así
como su coste. Solución págs 338 y 339.
Solución:
procedimiento ra (v)
Q ← cola-vacía
marca[v] ← visitado
poner v en Q
mientras Q no esté vacía hacer
u ← primero (Q)
quitar u de Q
para cada nodo w adyacente a u hacer
si marca [w]≠ visitado entonces marca [w] ← visitado
poner w en Q
El recorrido en anchura utiliza como estructura de datos una cola y el recorrido en profundidad una
pila, el coste de ambas está en Θ(max(a,n))
Problema (5 puntos).
Partiendo de un conjunto N={n1, n2, ..., nm} compuesto por m número positivos y de un conjunto O={+,-,*,/} con las
operaciones aritméticas básicas, se pide obtener una secuencia de operaciones factible para conseguir un número
objetivo P. Como restricciones al problema, debe tenerse en cuenta que: a) los números del conjunto N pueden
utilizarse en la secuencia de operaciones 0 o 1 vez, b) los resultados parciales de las operaciones pueden utilizarse
como candidatos en operaciones siguientes, c) las operaciones que den como resultado valores negativos o
números no enteros NO deberán tenerse en cuenta como secuencia válida para obtener una solución. Diseñe un
algoritmo que obtenga una solución al problema propuesto, mostrando la secuencia de operaciones para obtener el
número objetivo P. En caso de no existir solución alguna, el algoritmo deberá mostrar la secuencia de operaciones
que dé como resultado el valor más próximo, por debajo, del número objetivo P. Por ejemplo, siendo P=960 y
N={1,2,3,4,5,6}, la secuencia de operaciones que obtiene la solución exacta es: ((((6*5)*4)*2)*(3+1))=960. Si P=970,
el algoritmo no encontraría la solución exacta con el conjunto de números inicial y la secuencia más próxima por
debajo de P sería ((((6*5)*4)*2)*(3+1))=960.
Solución:
Obviamente no se trata de un problema que pueda ser resuelto por divide y vencerás, puesto que no es
posible descomponer el problema en subproblemas iguales, pero de menor tamaño, que con algún tipo de
combinación nos permita encontrar la solución del problema.
Tampoco se trata de un algoritmo voraz, puesto que no existe ninguna manera de atacar el problema de
manera directa que nos lleve a la solución sin necesidad de, en algún momento, deshacer alguna de las
decisiones tomadas.
Por tanto, se trata de un problema de exploración de grafos donde deberemos construir el grafo implícito al
conjunto de operaciones posibles con el fin de encontrar una solución al problema. Descartamos una
exploración ciega en profundidad o en anchura puesto que, como en la mayor parte de los casos va a existir
por lo menos una solución y además el enunciado del problema solicita una solución al problema y no
todas, es deseable que la exploración se detenga en el momento en el que encuentre alguna de ellas. Sólo
en el caso de que no exista solución al problema, a partir del conjunto N inicial, el recorrido deberá ser
completo. De acuerdo a este razonamiento, la estrategia más apropiada parece la de aplicar un esquema
del tipo backtracking o vuelta atrás. Como el alumno ya conoce, este tipo de algoritmos se basan en un
recorrido en profundidad o en anchura que no construye el grafo implícito de manera exhaustiva, puesto
que dispone de condiciones de corte que lo detienen en cuanto se encuentra una solución. En nuestro caso,
además, basaremos el algoritmo en un recorrido en profundidad y no en anchura puesto que en la mayor
parte de las ocasiones es necesario combinar prácticamente la totalidad de los elementos de N para
obtener la solución. La alternativa de aplicar un algoritmo de ramificación y poda no es válida en este caso
pues este tipo de algoritmos se caracteriza por obtener la solución óptima a un problema concreto. En este
caso no es necesario optimizar absolutamente nada, pues la solución es el número objetivo que nos
solicitan y no van a existir, por tanto, soluciones mejores o peores. Sólo en el caso de que no exista
solución, el problema nos pide la más aproximada por debajo, lo cual implica una exploración exhaustiva del
grafo implícito y, por tanto, el conocimiento por parte del algoritmo de cual ha sido la operación más
aproximada realizada hasta el momento.
Descripción del Esquema Algorítmico de Vuelta Atrás.
fun vuelta-atrás(e: ensayo)
si valido(e) entonces
dev e
sino
listaensayos . complecciones(e)
si condicionesdepoda(hijo) entonces
resultado . vuelta-atrás(hijo)
fsi
fmientras
dev resultado
fsi
ffun
Tipo Tensayo=
candidatos: vector de int
operaciones: vector de TpilaOperaciones
solucion: boolean
vacio: boolean
Operaciones Asociadas
---------------------
getCandidatos():vector
Devuelve el vector de candidatos para operar con él.
getOperaciones():vector
Devuelve el vector de operaciones(pilas) para operar con él.
getCandidato(indexCand int):int
Devuelve el candidato que se encuentra en la posición indexCand.
removeCandidato(indexCand int):void
Elimina el candidato que se encuentra en la posición indexCand.
setCandidato(candidato int,indexCand int):void
Inserta el candidato candidato en la posición indexCand del vector de candidatos.
getOperacion(indexOp int):TPilaOperaciones
Devuelve la operación(pila) que se encuentra en la posición indexOp.
removeOperacion(indexOp int):void
Elimina la pila de operaciones que se encuentra en indexOp.
setOperacion(operacion TpilaOperaciones, indexOp int):void
Inserta la pila de operaciones operación en la posición indexOp del vector de operaciones.
setSolucion(solucion boolean):void
Marca el ensayo como solución válida.
isSolucion():boolean
Devuelve un boolean que indica si el ensayo es o no solución.
isVacio():bolean
Devuelve un bolean que indica si el ensayo es o no vacio.
setVacio(vacio boolean)void
Marca el ensayo como vacio.
Tipo TpilaOperaciones=
pila: pila de String
Operaciones Asociadas
---------------------
pushNumber(value int):void
Añade un número a la pila. La lógica de la operación transforma el entero de entrada en una
cadena de caracteres para poder insertarlo en la pila.
pushOperator(oper char):void
Añade un operador a la pila. La lógica de la operación transforma el entero de entrada en una
cadena de caracteres para poder insertarlo en la pila.
El principal problema del ejercicio se encuentra en determinar cómo vamos a ir construyendo el grafo implícito y
cómo vamos a representar las operaciones que ya han sido llevadas a cabo. Para ello vamos a tener en cuenta lo
siguiente:
1. Nuestro algoritmo siempre va a trabajar sobre un conjunto de candidatos que recogerá inicialmente los
valores asociados y, posteriormente, los valores obtenidos al ir realizando cada una de las operaciones.
Esta es la función del elemento candidatos de Tensayo.
2. Para poder recordar qué operaciones se han llevado a cabo y mostrárselas al usuario al fin del algoritmo, es
necesario desplegar un almacén de información paralelo a candidatos que, para cada uno de los candidatos
recoja las operaciones que éste ha soportado hasta el momento. Con este fin se crea el elemento
operaciones, que no es más que un vector donde cada elemento representa una pila de operaciones y
operandos que, en notación postfija, representan el historial de operaciones de dicho elemento.
3. Finalmente, el elemento solución marca el ensayo como solución o no, dependiendo si alberga la solución
al problema.
Veamos con un ejemplo el funcionamiento de esta estructura de datos. Supongamos que nuestro conjunto inicial es
N={1, 2, 3, 4}. El ensayo correspondiente a esta situación inicial vendría descrito por:
Tipo Tensayo=
candidatos: vector de int
operaciones: vector de TpilaOperaciones
solucion: bolean
ENSAYO
------
candidatos:<1,2,3,4>
1 2 3 4
operaciones < >
solucion: false
Supongamos ahora que operamos el candidato 2 y el candidato 4 con el operador ‘+‘. El nuevo formato del ensayo
sería el siguiente:
ENSAYO
------
candidatos:<1,6,3>
2
1 + 3
operaciones < >
solucion: false
vacio: false
Nótese que:
1. Desaparece el elemento que ocupa la posición 3 del vector y su pila asociada.
2. El vector de candidatos ahora almacena el valor 6 allí donde se ha realizado la operación.
3. El vector de operaciones ha actualizado la pila de operaciones, reflejando la nueva situación en notación
postfija (2,4,+)
Supongamos que ahora operamos el candidato 6 con el candidato 3 utilizando el operador ‘-‘. El nuevo ensayo
quedaría:
ENSAYO
------
candidatos:<1,3>
4
+
1 -
operaciones < >
solucion: false
vacio: false
Finalmente si operamos el candidato 1 con el candidato 3 utilizando el operador ‘*’. El ensayo obtenido sería:
ENSAYO
------
candidatos:<3> 2
4
+
*
operaciones < >
solucion: false
vacio: false
Como podemos observar, el valor final obtenido combinando todos los valores iniciales, y de acuerdo a las
operaciones descritas, sería 3 y el historial completo de todas las operaciones realizadas podría mostrarse
deshaciendo la pila en formato postfijo generada (2,4,+,3,-,1,*).
Algoritmo Completo.
fun vuelta-atrás(e: Tensayo):Tensayo
si valido(e) entonces
solucion . e
solucion.setSolucion(true);
dev solucion
sino
listaensayos . complecciones(e)
si ←podar(hijo) entonces
solucion . vuelta-atrás(hijo)
sino
solucion . mejor(e)
fsi
fmientras
dev solucion
fsi
ffun
Nótese que solución es un ensayo, que inicialmente es vacío, y que contendrá la solución en caso de existir o la
serie de operaciones que más se aproximen en caso de que esto no ocurra. Solución se define externamente a la
función vuelta-atrás y, por eso, puede manipularse desde cualquiera de las funciones sin ser necesario enviarla a
éstas como parámetro.
Igualmente, la el valor objetivo P, también es utilizado globalmente por la función vuelta atrás.
valido(e Tensayo):boolean
Función que devuelve true si el ensayo que recibe como parámetro es solución al problema, es
decir, si contiene algún candidato cuyo valor sea P. Devuelve false en caso contrario.
complecciones(e Tensayo):lista
Función que devuelve la lista de hijos correspondientes a un ensayo concreto. La política de
generación de hijos que seguiremos será la siguiente: Para cada candidato, complecciones
genera todas las combinaciones posibles de éste con cada uno de los demás, haciendo uso del
conjunto de operaciones posibles.
podar(e Tensayo):bolean
Función que devuelve un boolean dependiendo si es posible continuar explorando por el ensayo
e que recibe como parámetro o no. La única condición de poda que impondremos será que alguno
de los candidatos calculados hasta el momento sobrepase el valor de P.
mejor(e Tensayo):Tensayo
Función que compara el ensayo e, que recibe como parámetro, con la solución calculada hasta
el momento. Devuelve a la salida aquel ensayo que contenga el candidato más próximo a la
solución solicitada.
si v1<v2 entonces
dev solucion
sino
dev e
fsi
ffun
fpara
dev value
ffun
hijo=obtieneHijo(e,’-’,c1,c2)
si (←hijo.isVacio()) entonces
vHijos.addElement(hijo)
fsi
hijo=obtieneHijo(e,’*’,c1,c2)
si (←hijo.isVacio()) entonces
vHijos.addElement(hijo)
fsi
hijo=obtieneHijo(e,’/’,c1,c2)
si (←hijo.isVacio()) entonces
vHijos.addElement(hijo)
fsi
fpara
fpara
dev vHijos
ffun
fun obtieneHijo(e: Tensayo, char operator, int c1Index, int c2Index):Tensayo
c1 . e.getCandidato(c1Index)
c2 . e.getCandidato(c2Index)
nuevoEnsayo . e
si (operator=’+’) entonces
res . c1+c2
sino
si (operator=’-‘) entonces
res . c1-c2
sino
si (operator=’*’) entonces
res . c1*c2
sino
si (operator=’/’) entonces
si (c2!=0) . (c1%c2=0) entonces
res . c1/c2
sino
res . -1
fsi
fsi
fsi
fsi
fsi
nuevoEnsayo . e
pila1=e.getOperacion(c1)
pila2=e.getOperacion(c2)
pila=generaNuevaPila(pila1,pila2,operator)
nuevoEnsayo.removeCandidato(c2)
nuevoEnsayo.setCandidato(res,c1)
nuevoEnsayo.removeOperacion(c2)
nuevoEnsayo.setOperacion(pila,c1)
dev nuevoEnsayo
sino
dev ensayoVacio
fsi
ffun
Nótese como la funcion generaNuevaPila recibe como parámetros las dos pilas ya existentes (pertenecientes a
cada uno de los candidatos), así como el operador que va a ser utilizado para combinar ambos candidatos y genera
como resultado la nueva pila correspondiente al candidato generado.
Programación III Prueba Presencial
Recurrencia:
c·nk si 0≤n<b
T(n)=
a·T(n-b)+c·nk si n≥b
Θ(nk) si a<1
T(n)∈ Θ(nk+1) si a=1
Θ(an div b) si a>1
Nota: Θ(2n div 2) no es equivalente a Θ(2n), puesto que se le está aplicando la raíz cuadrada y no
el producto de una constante.
Cuestión 3 (2 puntos). Demuestra por inducción que el algoritmo de Dijkstra halla los caminos
mínimos desde un único origen hasta todos los demás nodos del grafo.
Problema (5 puntos). Utiliza el esquema de divide y vencerás para implementar una función
que tome un vector de enteros y le dé estructura de montículo con el menor coste posible.
Función DivideVencerás(X)
si suficiente_pequeño(X) entonces
dev subalgoritmo_básico(X)
si no
Descomponer(X, X1..Xn)
para i=1 hasta n hacer
Yi:=DivideVencerás(Xi);
fpara
Recombinar(Y1..Yn, Y)
dev Y
fsi
3. Estructuras de datos (0 puntos)
No se necesita ninguna estructura adicional a parte del vector de entrada.
Como puede observarse, dividir el vector en dos mitades M[1..m/2], M[(m/2)+1..m] y tratar de
resolver cada una de ellas supone un error. Una solución que recorra completamente el vector y
proceda a hundir o flotar cada elemento siempre tendrá mayor coste.
Procedimiento hundir(T[1..n], i)
hmayor:=i
si (2i ≤ n) y (T[2i] > T[hmayor]) entonces
hmayor=2i
fsi
si (2i < n) y (T[2i+1] > T[hmayor]) entonces
hmayor=2i+1
fsi
si (hmayor > i) entonces
intercambiar(T[i], T[hmayor])
hundir(T[1..n], hmayor)
fsi
El coste del procedimiento hundir se puede plantear mediante una recurrencia con reducción del
problema por división, ya que hundir prosigue por uno de los dos subárboles y, por tanto, el
problema se ha reducido a la mitad.
c·nk si 0≤n<b
T(n)=
a·T(n/b)+c·nk si n≥b
Θ(nk) si a<bk
T(n)∈ Θ(nk log n) si a=bk
Θ(nlogb a) si a>bk
T(n)=2·T(n/2)+log n
Sin embargo, esta recurrencia no se puede resolver con la fórmula anterior puesto que la parte
no recursiva no tiene una complejidad polinomial. Para resolverlo, vamos a utilizar un cambio de
variable:
Sea h la altura del montículo de n nodos: h=log2 n. En cada llamada recursiva bajamos un nivel
por lo que el problema se puede expresar mediante una recurrencia con reducción del problema
por sustracción. Es decir, si en cada paso se baja un nivel, el problema se reduce en uno.
c·nk si 0≤n<b
T(n)=
a·T(n-b)+c·nk si n≥b
Θ(nk) si a<1
T(n)∈ Θ(nk+1) si a=1
Θ(an div b) si a>1
En el caso de hundir T(h)=T(h-1)+c, con a=1 y b=1, luego T(h)∈Θ(h), que deshaciendo el cambio
de variable lleva a un tiempo en función de n: T(n)∈Θ(log n) como habíamos demostrado antes.
T(h)=2·T(h-1)+h, donde a=2 y b=1 y, por tanto, T(h)∈Θ(2h). Deshaciendo el cambio de variable:
2h=2log2 n=n, y T(n)∈Θ(n), que es el menor coste posible para dar estructura de montículo a un
vector.
Cuestión 1 (2 puntos). Suponga que N personas numeradas de 1 a N deben elegir por votación a una
entre ellas. Sea V un vector en el que la componente V[i] contiene el número del candidato que ha elegido
el votante i. ¿Qué algoritmo utilizarías para determinar con un coste lineal si una persona ha obtenido más
de la mitad de los votos?
PROBLEMA (5 puntos). Sea V[1..N] un vector con la votación de unas elecciones. La componente V[i]
contiene el nombre del candidato que ha elegido el votante i. Implementa un programa cuya función
principal siga el esquema divide y vencerás, que decida si algún candidato aparece en más de la mitad de
las componentes (tiene mayoría absoluta) y que devuelva su nombre. Sirva como ayuda que para que un
candidato tenga mayoría absoluta considerando todo el vector (al menos N/2+1 de los N votos), es
condición necesaria pero no suficiente que tenga mayoría absoluta en alguna de las mitades del vector. La
resolución del problema debe incluir, por este orden:
Solución Original
Septiembre de 2005
Cuestión 1 (2 puntos). Suponga que N personas numeradas de 1 a N deben elegir por votación a una entre ellas.
Sea V un vector en el que la componente V[i] contiene el número del candidato que ha elegido el votante i. ¿Qué
algoritmo utilizarías para determinar con un coste lineal si una persona ha obtenido más de la mitad de los votos?
Podría utilizarse una estrategia similar a la que emplea para ordenación el algoritmo de la casilla (página 80 del
libro), pero trasladado al conteo de elementos. La siguiente función almacena en el vector votos el número de votos
que va sumando cada candidato votado. Devuelve un valor TRUE si hay alguno con mayoría indicando de qué
candidato se trata:
Cuestión 2 (1 punto). En el contexto de elegir un esquema algorítmico para resolver un problema de optimización
con restricciones, ¿cuándo se puede resolver mediante un esquema voraz y en qué casos sería necesario utilizar
un esquema de ramificación y poda?
Para resolverlo con un esquema voraz es necesario que exista una función de selección de candidatos y una
función de factibilidad que decida si se acepta o rechaza el candidato, de manera que la decisión es irreversible. Si
no es posible encontrar las funciones de selección y de factibilidad de manera que se garantice que la elección de
un candidato lleva a la solución óptima, entonces optaríamos por otro esquema como el de ramificación y poda.
Cuestión 3 (2 puntos). Sea T(n)=4n2-3n+2 el tiempo de ejecución de un algoritmo. Demuestra si es cierta o falsa
cada una de las siguientes afirmaciones (0.5 puntos cada una):
⎧ c entonces f ( n ) ∈ Θ( g ( n ))
f (n) ⎪
lím = ⎨ 0 entonces f ( n ) ∈ O ( g ( n )), f ( n ) ∉ Θ( g ( n ))
n→∞ g ( n )
⎪ + ∞ entonces f ( n ) ∈ Ω( g ( n )), f ( n ) ∉ Θ( g ( n ))
⎩
a) T(n) ∉ O(n2 ln n)
4n 2 − 3n + 2 8n − 3 8
lím 2
= lím = =0
n →∞ n ln n n → ∞ 2n ln n + n 2 ln n + 3
Luego pertenece al orden indicado y la cuestión a) es FALSA
b) T(n) ∉ O(n3)
4n 2 − 3n + 2 8n − 3 8
lím 3
= lím = =0
n →∞ n n →∞ 3n 2 6n
Por lo que T(n) pertenece a O(n3 ) y la cuestión b) es FALSA
4n 2 − 3n + 2 8n − 3 8
lím = lím = = +∞
n →∞ n ln n n → ∞ ln n + 1 1
n
con lo que c) es CIERTO
d) T(n) ∈ O(n2)
4n 2 − 3n + 2 8n − 3 8
lím 2
= lím = = 4 ∈ ℜ+
n →∞ n n → ∞ 2n 2
En este caso, al ser T(n) ∈ Θ(n2) se cumple también que T(n) ∈ O (n2) luego d) es CIERTO
Problema (5 puntos). Sea V[1..N] un vector con la votación de unas elecciones. La componente V[i] contiene el
nombre del candidato que ha elegido el votante i. Implementa un programa cuya función principal siga el esquema
divide y vencerás, que decida si algún candidato aparece en más de la mitad de las componentes (tiene mayoría
absoluta) y que devuelva su nombre. Sirva como ayuda que para que un candidato tenga mayoría absoluta
considerando todo el vector (al menos N/2+1 de los N votos), es condición necesaria pero no suficiente que tenga
mayoría absoluta en alguna de las mitades del vector. La resolución del problema debe incluir, por este orden:
Más formalmente:
La reducción del problema se realiza mediante división, cuyos casos son los siguientes:
Θ(nk) si a<bk
T(n) = Θ(nk log n) si a=bk
Θ(nlogba) si a>bk
donde:
n: tamaño del problema
a: número de llamadas recursivas
n/b: tamaño del subproblema
nk: coste de las instrucciones que no son llamadas recursivas
Cuestión 3 (2 puntos). Analizar y hallar el coste de los algoritmos siguientes (Considerar de orden
Ο ( n 2 ) la función h ( n, r , i ) )
Problema (5 puntos). El Sudoku es un pasatiempo consistente en rellenar con cifras del 1 al 9 una
cuadrícula de 81 (9x9) casillas distribuidas a su vez en 9 cajas de 3x3. El juego consiste en rellenar cada
caja de 9 casillas con cifras del 1 al 9 sin repetirlas. No se pueden repetir tampoco cifras en líneas o
columnas de la cuadrícula. Se pide diseñar un algoritmo que complete por nosotros este pasatiempo. La
tabla adjunta muestra un ejemplo resuelto.
6 4
12 7 4 4
g ( n) 22 n 2n ⋅ 2n 2n 2∞
lim = lim n = lim n = lim = lim =∞
n →∞ f ( n ) n→∞ 2 n →∞ 2 n →∞ 1 n →∞ 1
f ( n)
1. Si lim ∈ℜ+ , entonces f ( n ) ∈ O ( g ( n ) ) y g ( n) ∈ O ( f ( n ))
n →∞ g ( n)
f (n)
2. Si lim ∈ 0 , entonces f ( n ) ∈ O ( g ( n ) ) pero g ( n ) ∉ O ( f ( n ) )
n →∞ g ( n)
f ( n)
3. Si lim ∈ +∞ , entonces f ( n ) ∉ O ( g ( n ) ) pero g ( n ) ∈ O ( f ( n ) )
n →∞ g ( n)
Cuestión 3 (2 puntos). Analizar y hallar el coste de los algoritmos siguientes (Considerar de orden
Ο ( n 2 ) la función h ( n, r , i ) )
El procedimiento uno tiene una instrucción condicional de coste constante. Dentro de la condición
tiene:
o bien una instrucción constante, que no tenemos en cuenta para el cálculo,
o bien dos llamadas recursivas, ambas invocan la función con un tamaño n/2.
//En el segundo caso de la llamada recursiva hay otra instrucción simple añadida.
Un bucle en el que se repite n2 veces un cálculo consistente en una instrucción constante
La expresión queda T(n) = T(n/2) + T(n/2) + n2 (1) lo que equivale a:
T(n) = 2T(n/2) + n2. Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b)+cnk
lo que equivale a T(n) = 2T(n/2) + n2 y siendo 2 < 2 2 el coste es O(n2). a = 2, b = 2, k = 2
el segundo algoritmo:
está formado por una instrucción condicional de coste constante cuyo cuerpo incluye
secuencialmente:
(i) una llamada recursiva de tamaño n/2,
(ii) un bucle en el que se repite n veces un cálculo consistente en llamar dos veces a una función
h(n; r; i) de coste cuadrático, /*más una instrucción simple*/, y por último
(v) una instrucción simple, que no tenemos en cuenta para el cálculo, y otra llamada recursiva de
tamaño n/2.
Sumando los términos nos sale T(n) = T(n/2)+ n (2n2)+T(n/2), lo que equivale a
T(n) = 2T(n/2) + 2n3 , Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b) + cnk y siendo 2 < 2 3 el coste es O(n3). a = 2, b = 2, k = 3
Problema (5 puntos). El Sudoku es un pasatiempo consistente en rellenar con cifras del 1 al 9 una
cuadrícula de 81 (9x9) casillas distribuidas a su vez en 9 cajas de 3x3. El juego consiste en rellenar cada
caja de 9 casillas con cifras del 1 al 9 sin repetirlas. No se pueden repetir tampoco cifras en líneas o
columnas de la cuadrícula. Se pide diseñar un algoritmo que complete por nosotros este pasatiempo. La
tabla adjunta muestra un ejemplo resuelto.
Solución:
Es un problema de búsqueda no de optimización por lo tanto descarto el esquema voraz y el de
ramificación y poda, tampoco se puede ir dividiendo el problema en subproblemas más pequeños,
viendo esto me decanto por el esquema de vuelta atrás.
Esquema general:
En nuestro caso, tendremos que un ensayo es una matriz de 9x9 donde probaremos los distintos
candidatos hasta llegar a la solución (o no) tras recorrer todas las celdas de la matriz. Tomaremos el
nodo-raíz como el nodo padre principal del que partiremos, o lo que denominaremos 0-prometedor.
Los hijos serán los tableros con una posible solución valida siguiendo las reglas de Sudoku, que
formarán las condiciones de poda en nuestro juego.
Usaremos, siguiendo las condiciones que nos dicen en el enunciado los siguientes elementos:
- Array de 10 booleanos para indicar los elementos ocupados en cada grupo de celdas.
- Matriz de 9x9 de enteros, que será la matriz donde pondremos los distintos valores.
- Array de 10 elementos de booleanos, que significa los posibles valores que tomaremos.
- Array de 9 elementos de grupo de celdas, indicando elemento ocupado en fila, columna o cuadrante (o
bloque).
Tensayo = tupla
arrayTab : vector [0..8][0..8] de integer;
fil, col, blo: array[0..8] de GrupoCeldas;
ftupla
Con la estructura de datos que hemos expuesto podremos expresar la función de vuelta-atrás, que en
nuestro caso la hemos llamado resuelve. Nuestro ensayo será la matriz arrayCeldas. Tendremos el
pseudocódigo que nos resolvería nuestro problema, que en nuestro caso lo hemos denominado resolver,
correspondería a la función vuelta-atrás que es la siguiente:
Veremos el pseudocódigo para analizar si el tablero es valido, que denominaremos con esValido. Está
función devolverá true (1) si el tablero cumple las reglas del Sudoku y 0 en caso contrario. Tendríamos
lo siguiente:
En el pseudocódigo las funciones auxiliares serán iguales que vimos anteriormente. En nuestro caso
calculará la posición actual y comprobaremos que en el valor actual se verifique dichas reglas.
Por último, en la función principal tendremos que primero se tendrá que comprobar el tablero para ver si
cumple dichas reglas y luego resolverlo, es por tanto, que se tendrá que hacer lo siguiente:
si (esValido())
resolver(arrayTab);
fsi
Como hemos visto en nuestra estructura de datos, la matriz arrayTab guardará el tablero que
inicialmente se nos da para resolver. Los valores a resolver, que son los * se guardarán en el mismo
como 0. Como hemos visto en la función en pseudocódigo anterior, si el valor es mayor de 0 significa
que en los datos que se nos da está rellenado, o lo que equivale a un valor que no hace falta analizar,
sólo hace falta quitarlo de la lista de elementos o como antes expresamos marcar como ocupado.
Conceptualmente es similar.
Analizaremos para el caso peor, en el que habrá que recorrer todas las casillas sin llegar a ninguna
solución. O lo que es lo mismo en nuestro algoritmo, cuando no se llega a solución (esSolucion a false).
Es difícil hacer un análisis completo en términos de la computabilidad computacional, ya que al ser
recursivo es difícil saber el coste de la recursión, es decir, de la función recursiva de la que parte. No
podremos, por tanto, averiguar el coste exacto de la misma.
Lo único que tenemos constancia es del número de celdas a recorrer y de los valores (o candidatos) que
recorreremos en cada celda. Es, por tanto, que la cota superior del coste es nnúmero de celdas ,
siendo:
n: valores a comprobar en celda.
número de celdas: El total que antes hemos expresado, como 9*9 = 81.
Una primera aproximación seria coste T(n) ∈О(nnúmero de celdas), equivaliendo, por tanto, a T(n) ∈ О(981).
Una segunda aproximación y quizás la mas cercana a la realidad de nuestro algoritmo recursivo es
suponer todos los posibles nodos-hijos (o hijo-nodo) como un árbol, en la que tomaremos todos los
valores. El esquema sería como sigue:
0 Nodo padre
……. 9 hijos
1;1 1;2 1;9
9*9 hijos
Podremos seguir representando el árbol, hasta recorrer los posibles valores. Vemos que el primer
número pertenece al número de hijo, el segundo al valor que trataremos en el primer nivel de hijos.
En el segundo, el primer número será el de hijo, el segundo el del valor del padre (en este caso es el
nodo 1) y el tercer número el de los distintos valores.
Observamos que del nodo hijo con valor dos en el primer nivel no tiene ningún hijo, esto se hizo así por
problemas con el dibujo realizado. Supondremos por tanto, que tiene tantos hijos como el resto de nodos
hermanos.
Observamos por tanto, que aquí tendremos como cota superior T(n) ∈О(n!), al ser una progresión
aritmética. Empezaremos por 0 (90) hijos, seguiremos por 9 (91) hijos, para seguir por 81 (92) hijos, así
continuamente nos dará el factorial de n!
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (2,5 puntos). ¿En qué se diferencia una búsqueda ciega en profundidad y un
esquema de vuelta atrás? (0.5 puntos)
Cuestión 2 (1,5 puntos). Declara en Java o en Módula-2 las clases y/o estructuras de datos que
utilizarías en el problema del Sudoku (práctica de este año) para comprobar en un tiempo de ejecución
constante respecto a la dimensión n del tablero (nxn) que una casilla contiene un valor factible.
Cuestión 3 (2 puntos). Dado el siguiente grafo, rellena la tabla adjunta indicando paso a paso cómo
el algoritmo de Dijkstra encuentra todos los caminos de menor coste desde el nodo 1.
Problema (4 puntos). Una empresa de montajes tiene n montadores con distintos rendimientos según
el tipo de trabajo. Se trata de asignar los próximos n encargos, uno a cada montador, minimizando el
coste total de todos los montajes. Para ello se conoce de antemano la tabla de costes C[1..n,1..n] en la
que el valor cij corresponde al coste de que el montador i realice el montaje j. Se pide:
Cuestión 1 (2,5 puntos). ¿En qué se diferencia una búsqueda ciega en profundidad y un esquema
de vuelta atrás? (0.5 puntos)
La búsqueda ciega explora todas las ramas alternativas mientras que en un esquema de vuelta atrás se
establecen condiciones de poda que determinan si una rama puede alcanzar o no una solución final. Si
se determina que no es posible, entonces no se prosigue la búsqueda por dicha rama (se poda). Los
problemas aptos para un esquema de vuelta atrás permiten expresar los nodos intermedios como
soluciones parciales al problema. Aquellos nodos que no sean soluciones parciales no permiten
alcanzar una solución final y, por tanto, su rama se poda. Por ejemplo, el problema de “poner N reinas
en un tablero de NxN sin que se amenacen entre sí”, requiere ir solucionando la siguiente secuencia de
soluciones parciales:
Si por una rama no se puede resolver el problema para k, entonces evidentemente no se podrá resolver
para N, por muchos intentos de añadir reinas hagamos.
Significa que es solución parcial para las k primeras componentes de la solución y, por tanto, todavía
es posible encontrar una solución final (incluyendo las N componentes). En el problema de las N
reinas habríamos colocado k reinas sin que se coman entre sí.
¿Que hay que hacer para decidir si un vector es k-prometedor sabiendo que es una extensión de
un vector (k-1)-prometedor? (1 punto)
Si es (k-1)-prometedor quiere decir que es solución parcial para las primeras k-1 componentes y que,
por tanto, estas cumplen las restricciones necesarias entre sí y no hay que volver a verificarlas.
Entonces, para decidir si una extensión considerando la siguiente componente k conforma un nodo
k-prometedor, lo único que hay que hacer es verificar si esta nueva componente k cumple las
restricciones respecto a las otras k-1.
Cuestión 2 (1,5 puntos). Declara en Java o en Módula-2 las clases y/o estructuras de datos que
utilizarías en el problema del Sudoku (práctica de este año) para comprobar en un tiempo de ejecución
constante respecto a la dimensión n del tablero (nxn) que una casilla contiene un valor factible.
Existen varias alternativas, pero de nada sirve que verificar una casilla se realice en tiempo constante
si luego actualizar su valor se realiza en tiempo lineal. En la siguiente solución se declaran tres tablas
de booleanos que indican si ya hay o no un determinado valor en una determinada fila, columna o
región, respectivamente. Si no es así, entonces es factible poner el valor en la casilla. Tanto la función
de verificación como las de actualización tienen un coste computacional constante.
Cuestión 3 (2 puntos). Dado el siguiente grafo, rellena la tabla adjunta indicando paso a paso cómo el
algoritmo de Dijkstra encuentra todos los caminos de menor coste desde el nodo 1.
1. Determinar qué esquema algorítmico es el más apropiado para resolver el problema. Razonar la
respuesta. (0.5 puntos)
Se trata de un problema de optimización con restricciones. Por tanto, podría ser un esquema voraz o un
esquema de ramificación y poda. Sin embargo descartamos el esquema voraz porque no es posible
encontrar una función de selección y de factibilidad tales que una vez aceptado un candidato se
garantice que se va alcanzar la solución óptima.
nodo=tupla
asignaciones: vector[1..N];
último_asignado: cardinal;
filas_no_asignadas: lista de cardinal;
coste: cardinal;
Las funciones generales del esquema general que hay que instanciar son:
1. a. solución(nodo): si se han realizado N asignaciones (último_asignado==N)
2. b. acotar(nodo,costes): nodo.coste + “mínimo coste de las columnas no asignadas”
3. c. compleciones(nodo,costes): posibilidades para la siguiente asignación (valores posibles para
asignaciones[último_asignado+1])
4.
Función asignación(costes[1..N,1..N]) dev solución[1..N]
Montículo:=montículoVacío();
nodo.último_asignado=0;
nodo.coste=0;
cota:=acotar(nodo,costes);
poner((cota,nodo),Montículo);
mientras no vacío(Montículo) hacer
(cota,nodo):=quitarPrimero(Montículo);
si nodo.último_asignado==N entonces
devolver nodo.asignaciones;
si no para cada hijo en compleciones(nodo,costes) hacer
cota:=acotar(hijo,costes);
poner((cota,hijo),Montículo);
fsi;
fmientras
devolver ∅;
Coste:
En este caso únicamente podemos hallar una cota superior del coste del algoritmo por descripción del
espacio de búsqueda. En el caso peor se generan (k-1) hijos por cada nodo del nivel k, habiendo n. Por
tanto, el espacio a recorrer siempre será menor que n!.
4. Desarrollar el algoritmo completo (2.5 puntos)
Las funciones generales del esquema general que hay que instanciar son:
a. solución(nodo): si se han realizado N asignaciones (último_asignado==N)
b. acotar(nodo,costes): nodo.coste + “mínimo coste de las columnas no asignadas”
c. compleciones(nodo,costes): posibilidades para la siguiente asignación (valores
posibles para asignaciones[último_asignado+1])
Cuestión 1 (1 punto). En la práctica obligatoria del presente curso 2005/2006 se ha tenido que
diseñar y desarrollar un algoritmo para resolver el juego del Su-doku. Codifique en java o en modula-2
un algoritmo iterativo que recorra el cuadrado de 3x3 casillas que corresponda a una casilla que ocupa
las posiciones i,j del tablero.
Cuestión 2 (2 puntos). Sea el famoso problema de la mochila. Se dispone de n objetos y una mochila.
Para i= 1,2,…,n, el objeto i tiene un peso positivo wi y un valor positivo vi. La mochila puede llevar un
peso que no sobrepase W. El objetivo es llenar la mochila de tal manera que se maximice el valor de
los objetos almacenados, respetando la limitación de peso impuesta. Indique qué esquema o esquemas
considera más adecuados para resolver este problema en los siguientes casos:
1. Los objetos se pueden fraccionar, luego se puede decidir llevar una fracción xi del objeto i, tal
que 0 ≤ xi ≤ 1 para 1 ≤ i ≤ n.
2. Los objetos no se pueden fraccionar, por lo que un objeto puede o no ser añadido, pero en éste
último caso, sólo se añade 1.
Además de nombrar el esquema o esquemas, explica el porqué de su elección, los aspectos destacados
de cómo resolverías el problema y el coste asociado. No se piden los algoritmos.
Cuestión 3 (3 puntos). Un dentista pretende dar servicio a n pacientes y conoce el tiempo requerido
por cada uno de ellos, siendo ti , i= 1,2,…,n el tiempo requerido por el paciente i. El objetivo es
minimizar el tiempo total que todos los clientes están en el sistema, y como el nº de pacientes es fijo,
minimizar la espera total equivale a minimizar la espera media. Se pide:
1. Identificar una función de selección que garantice que un algoritmo voraz puede construir una
planificación óptima. (0.5 puntos)
2. Hacer una demostración de la optimalidad de dicha función de selección. (2.5 puntos)
Solución: están solucionados en el libro base de la asignatura, apartado 6.6.1, pág 231.
Problema (4 puntos). Dos socios que conforman una sociedad comercial deciden disolverla. Cada
uno de los n activos que hay que repartir tiene un valor entero positivo. Los socios quieren repartir
dichos activos a medias y, para ello, primero quieren comprobar si el conjunto de activos se puede
dividir en dos subconjuntos disjuntos, de forma que cada uno de ellos tenga el mismo valor. La
resolución de este problema debe incluir, por este orden:
Cuestión 1 (1 punto). En la práctica obligatoria del presente curso 2005/2006 se ha tenido que
diseñar y desarrollar un algoritmo para resolver el juego del Su-doku. Codifique en java o en modula-2
un algoritmo iterativo que recorra el cuadrado de 3x3 casillas que corresponda a una casilla que ocupa
las posiciones i,j del tablero.
Solución:
Se trataba de encontrar la relación entre las posiciones i,j del tablero y las posiciones de comienzo del
cuadrado de 3x3 que les corresponde en el tablero del Su-doku 9x9. Supongamos que el tablero tiene
como índices 0..8, 0..8 y que i y j son de tipo entero, la relación se puede establecer de la siguiente
manera:
int coordFilaInicioCuadrado = (i / 3) * 3; // división entera
int coordColumnaInicioCuadrado = (j / 3) * 3; // división entera
Cuestión 2 (2 puntos). Sea el famoso problema de la mochila. Se dispone de n objetos y una mochila.
Para i= 1,2,…,n, el objeto i tiene un peso positivo wi y un valor positivo vi. La mochila puede llevar un
peso que no sobrepase W. El objetivo es llenar la mochila de tal manera que se maximice el valor de
los objetos almacenados, respetando la limitación de peso impuesta. Indique qué esquema o esquemas
considera más adecuados para resolver este problema en los siguientes casos:
1. Los objetos se pueden fraccionar, luego se puede decidir llevar una fracción xi del objeto i, tal
que 0 ≤ xi ≤ 1 para 1 ≤ i ≤ n.
2. Los objetos no se pueden fraccionar, por lo que un objeto puede o no ser añadido, pero en éste
último caso, sólo se añade 1.
Además de nombrar el esquema o esquemas, explica el porqué de su elección, los aspectos destacados
de cómo resolverías el problema y el coste asociado. No se piden los algoritmos.
Solución:
En el caso a) se puede utilizar el esquema voraz ya que existe una función de selección que garantiza
obtener una solución óptima. La función de selección consiste en considerar los objetos en orden
decreciente de vi/wi. El coste está en O(n log n), incluyendo la ordenación de los objetos. Ver página
227 del libro base de la asignatura.
En el caso b) no se puede utilizar el esquema voraz ya que no existe una función de selección que
garantice obtener una solución óptima. Al ser un problema de optimización se puede utilizar el
esquema de ramificación y poda. Se podrían seleccionar los elementos en orden decreciente de vi/wi.
Así, dado un determinado nodo, una cota superior del valor que se puede alcanzar siguiendo por esa
rama se puede calcular suponiendo que la mochila la rellenamos con el siguiente elemento siguiendo
el orden decreciente de vi/wi. El coste en el caso peor sería de orden exponencial, ya que en el árbol
asociado al espacio de búsqueda, cada nodo tendrá dos sucesores que representarán si el objeto se
añade o no a la mochila, es decir, O(2n). Sin embargo, sería de esperar que, en la práctica, el uso de la
cota para podar reduzca el número de nodos que se exploran.
Cuestión 3 (3 puntos). Un dentista pretende dar servicio a n pacientes y conoce el tiempo requerido
por cada uno de ellos, siendo ti , i= 1,2,…,n el tiempo requerido por el paciente i. El objetivo es
minimizar el tiempo total que todos los clientes están en el sistema, y como el nº de pacientes es fijo,
minimizar la espera total equivale a minimizar la espera media. Se pide:
1. Identificar una función de selección que garantice que un algoritmo voraz puede construir una
planificación óptima. (0.5 puntos)
2. Hacer una demostración de la optimalidad de dicha función de selección. (2.5 puntos)
Solución: están solucionados en el libro base de la asignatura, apartado 6.6.1, pág 231.
T ( P ) = s1 + ( s1 + s2 ) + ( s1 + s2 + s3 ) + ...
= ns1 + ( n − 1) s2 + ( n − 2 ) s3 + ...
n
= ∑ ( n − k + 1) sk
k =1
Supongamos ahora que P no organiza a los clientes por orden de tiempos creciente de servicio.
Entonces se puede encontrar dos enteros a y b con a < b y sa > sb , si intercambiamos la posición de
estos dos clientes, obtendremos un nuevo orden de servicio P´ que es simplemente el orden de P
después de intercambiar los enteros pa y pb . El tiempo total transcurrido pasado en el sistema por
todos los clientes si se emplea la planificación P´ es:
n
T ( P′ ) = ( n - a +1) sb + ( n - b+1) sa + ∑ ( n - k +1) sk
k=1
k ≠ a,b
T ( P ) − T ( P′ ) = ( n - a +1)( sa − sb ) + ( n - b+1)( sb − sa ) =
( b − a )( sa − sb ) > 0
Problema (4 puntos). Dos socios que conforman una sociedad comercial deciden disolverla. Cada
uno de los n activos que hay que repartir tiene un valor entero positivo. Los socios quieren repartir
dichos activos a medias y, para ello, primero quieren comprobar si el conjunto de activos se puede
dividir en dos subconjuntos disjuntos, de forma que cada uno de ellos tenga el mismo valor. La
resolución de este problema debe incluir, por este orden: (PROBLEMA 6.16 U.M., EDMA 470)
Solución:
1. No se puede encontrar una función de selección que garantice, sin tener que reconsiderar decisiones,
una elección de los activos que cumpla con la restricción del enunciado, por ello no se puede aplicar el
esquema voraz. Tampoco se puede dividir el problema en subproblemas que al combinarlos nos lleven
a una solución. Al no ser un problema de optimización, el esquema de exploración de grafos más
adecuado es el esquema vuelta-atrás.
Vuelta atrás es un recorrido en profundidad de un grafo dirigido implícito. En él una solución puede
expresarse como una n-tupla [x1, x2, x3,… xn], donde cada xi, representa una decisión tomada en la
etapa i-ésima, de entre un conjunto finito de alternativas.
Descripción algorítmica del esquema:
función vuelta-atrás(e: ensayo)
si valido(e) entonces
dev e
sino
listaEnsayos ← complecciones(e)
mientras ¬ vacia(listaEnsayos) ∧ ¬resultado hacer
hijo ← primero(listaEnsayos)
listaEnsayos ← resto(listaEnsayos)
si condicionesDePoda(hijo) entonces
resultado ← vuelta-atrás(hijo)
fsi
fmientras
dev resultado
fsi
ffunción
En este caso, el espacio de búsqueda es un árbol de grado 2 y altura n+1. Cada nodo del
i-ésimo nivel tiene dos hijos correspondientes a si el i-ésimo activo va a un socio o al otro.
Para poder dividir el conjunto de activos en dos subconjuntos disjuntos, de forma que
cada uno de ellos tenga el mismo valor, su valor debe ser par. Así, si el conjunto inicial de
activos no tiene un valor par el problema no tiene solución.
En este caso, el coste viene dado por el número máximo de nodos del espacio de
búsqueda, esto es: T(n) ∈ O(2n)
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 2 (2 puntos). Escribe la salida al realizar la llamada “pinta(5)”, dado el siguiente código
(1,5 puntos):
6 5 1 2 3 4 7 8 9
Problema (4 puntos). Desarrollar un programa que compruebe si es posible que un caballo de ajedrez
mediante una secuencia de sus movimientos permitidos recorra todas las casillas de un tablero NxN a
partir de una determinada casilla dada como entrada y sin repetir ninguna casilla.
Se pide:
Solución:
En primer lugar debemos plantear el tiempo de ejecución T(n) en función de n. Para ello,
vamos a fijarnos en la instrucción barómetro (la instrucción que se ejecuta más veces) y vamos a
contar cuántas veces se ejecuta. Puesto que todas las instrucciones tienen un coste constante,
la complejidad del programa estará en el orden del número de veces que se ejecute la
instrucción barómetro.
n n /i n
n n
1
T ( n ) = ∑∑ 1 = ∑ = n·∑
i =1 j =1 i =1 i i =1 i
n
1
∑
i =1 i
es un sumatorio de n elementos menores o iguales que 1, por lo que su suma total será
menor que n. Por tanto, T(n) ≤ n·n, o en otras palabras, T(n) está acotado superiormente por n2:
n
1
Por otra parte, ∑i
i =1
es mayor que 1 y, por tanto T(n) ≥ n·1, o en otras palabras, T(n) está
1
n
∫1 x dx que corresponde al logaritmo natural de n. Por tanto, la serie crece tan rápidamente
como el ln(n). Así pues, se pueden encontrar dos constantes c y d tal que T(n) esté acotado
superiormente por c·n·log(n) e inferiormente por d·n·log(n). En conclusión, el orden exacto de
T(n) es n·log(n):
T(n)∈θ(n log n)
función pinta(int n)
si n>0 entonces
escribir “n”;
pinta(n-1);
escribir “n-1”;
fsi
Demuestra el coste computacional de la función “pinta” suponiendo que “escribir” tiene coste
constante (0.5 puntos).
Solución:
La salida sería: 5 4 3 2 1 0 1 2 3 4
Para calcular el coste planteamos una recurrencia con reducción del problema mediante
sustracción:
Recurrencia:
c·nk si 0≤n<b
T(n)=
a·T(n-b)+c·nk si n≥b
Θ(nk) si a<1
T(n)∈ Θ(nk+1) si a=1
Θ(an div b) si a>1
a=1; b=1; nk=1; k=0
Solución:
6 5 1 2 3 4 7 8 9
4 5 1 2 3 6 7 8 9
4 3 1 2 5 6 7 8 9
2 3 1 4 5 6 7 8 9
2 1 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
Si se toma como pivote el elemento que ocupa la posición central, en este caso el primer pivote
es el elemento que ocupa la posición quinta, que es el 3:
6 5 1 2 3 4 7 8 9
2 5 1 6 3 4 7 8 9
2 1 5 6 3 4 7 8 9
2 1 3 6 5 4 7 8 9
1 2 3 6 5 4 7 8 9
1 2 3 4 5 6 7 8 9
Solución:
Un ejemplo de movimientos del caballo puede ser:
• Válido: Un ensayo será válido (solución) si no queda casilla alguna por visitar.
• Compleciones: Se generarán tantos ensayos como casillas libres haya alcanzables directamente desde
la última casilla visitada.
• Condiciones de poda: Si sólo se generan ensayos sobre casillas alcanzables no hay ninguna condición
de poda que añadir.
Estructuras de datos
La función válido utiliza únicamente uno de ellos para comprobar que el número de casillas ocupadas
corresponde con el de casillas en el tablero. Ya veremos más adelante que esta comprobación es suficiente
gracias a que las posiciones exploradas del árbol son únicamente aquellas legales y que, por tanto, son
susceptibles de conducir a una solución.
NOTA: Se ha hecho una pequeña modificación en la función insertar (recordemos que previamente vimos algo
parecido con añadir) en la que se intercambian los parámetros para indicar el primero de ellos el elemento a
añadir (en este caso w) y el segundo donde se añade. Además, se ha modificado está función para que devuelva
una lista de ensayos, que no estaba incluida en la solución de dicho ejercicio. La única condición de retroceso
posible es la del que el caballo haya alcanzado una posición tal que se encuentre rodeado de casillas ya
recorridas y no pueda, por tanto, efectuar movimiento alguno sin repetir casilla. En este caso, el árbol no se
sigue explorando y el algoritmo debe retroceder y buscar otra rama. Estrictamente hablando lo anterior no es
una condición de poda. En el caso expuesto bastaría con generar todas las posibles compleciones (que serian 0
puestos que deben ser validas) y devolver una lista vacía para que la función retroceda.
NOTA (del ejercicio): Al hablar de condiciones de poda nos referimos a que anticipamos que no hay solución
por ese camino, no que hayamos llegado a un punto muerto. En ese sentido la condición de “caballo sin casillas
libres a su alrededor” no es una condición de poda, ya que no es un criterio objetivo que anticipe a un camino
erróneo. En el caso de este problema no hay en realidad condición de poda si no condición de retroceso y, por
tanto, esta función no tiene sentido en esta implementación.
Los movimientos posibles del caballo pueden verse en el dibujo anterior. Se puede ver que, si el caballo ocupa
una posición (a,b), las casillas alcanzables son (a+i,b+j) en las que i,j ∈{−2,−1,1,2} y tales que |i+j|=3. Con esta
consideración, la generación de las casillas validas se puede hacer mediante un bucle con la condición anterior
para formar los 8 nuevos movimientos posibles.
Para crear los nuevos tableros a partir de un movimiento válido se utiliza la función de generar ensayo. Esta
función crea una nueva variable ensayo y genera:
1. Un nuevo registro de última casilla ocupada con la posición obtenida del bucle.
2. Un nuevo tablero copiado del ensayo anterior al que se le añade la nueva posición en la que escribimos el
correspondiente valor del número de movimientos efectuados.
3. Incrementamos en 1 el valor del número de movimientos efectuados que contenía al anterior ensayo.
En la función principal, para recorrer la lista de compleciones, el esquema principal debe utilizar un bucle
mientras … hacer. La función principal queda como sigue:
Cuestión 1 (3 puntos). Hallar el coste de los siguientes algoritmos, siendo h(n,r,k) ∈ O(n2).
PROCEDIMIENTO PROCEDIMIENTO
uno (n,k:entero):entero; dos (n,k:entero):entero;
VAR i,r:entero; VAR i,r:entero;
COMIENZO COMIENZO
SI n < 6 ENTONCES DEVOLVER(1); SI n < 8 ENTONCES DEVOLVER(1);
SINO SINO
COMIENZO COMIENZO
r ← uno (n DIV 6, k-1); r ← dos (n DIV 8, k-1);
r ← r + uno (n DIV 6, k+1); r ← r + dos (n DIV 8, k+1);
r ← r + uno (n DIV 6, k+2); PARA i ← 1 HASTA n/2 HACER
PARA i ← 1 HASTA 2*n HACER COMIENZO
COMIENZO r ← h (n,r,i);
r ← h(n,r,i)+h(n,r-1,i); r ← r + h (n,r-1,i);
FIN FIN
DEVOLVER (r); r ← r + dos (n DIV 8, k+2);
FIN DEVOLVER (r);
FIN FIN
FIN
Cuestión 2 (1 punto). Explica cómo pueden ordenarse n valores enteros positivos en tiempo lineal,
sabiendo que el rango de dichos valores es limitado. Explica las ventajas e inconvenientes de ese
método.
Cuestión 3 (1 punto). ¿Puede un grafo tener dos árboles de recubrimiento mínimo diferentes? En caso
afirmativo poner un ejemplo. En caso negativo demostrarlo formalmente.
Problema (5 puntos). Un repartidor de pizzas tiene que entregar K pedidos de diferente valor de
recaudación como mucho hasta la ranura de tiempo concreta que tiene asignada en la tabla adjunta.
Pedido 1 2 3 4 5 6 7 8 9
Ranura 1 5 5 6 6 4 4 2 2
Recaudación 60 70 80 20 20 30 50 50 90
Cuestión 1 (3 puntos). Hallar el coste de los siguientes algoritmos, siendo h(n,r,k) ∈ O(n2).
PROCEDIMIENTO PROCEDIMIENTO
uno (n,k:entero):entero; dos (n,k:entero):entero;
VAR i,r:entero; VAR i,r:entero;
COMIENZO COMIENZO
SI n < 6 ENTONCES DEVOLVER(1); SI n < 8 ENTONCES DEVOLVER(1);
SINO SINO
COMIENZO COMIENZO
r ← uno (n DIV 6, k-1); r ← dos (n DIV 8, k-1);
r ← r + uno (n DIV 6, k+1); r ← r + dos (n DIV 8, k+1);
r ← r + uno (n DIV 6, k+2); PARA i ← 1 HASTA n/2 HACER
PARA i ← 1 HASTA 2*n HACER COMIENZO
COMIENZO r ← h (n,r,i);
r ← h(n,r,i)+h(n,r-1,i); r ← r + h (n,r-1,i);
FIN FIN
DEVOLVER (r); r ← r + dos (n DIV 8, k+2);
FIN DEVOLVER (r);
FIN FIN
FIN
El procedimiento uno tiene una instrucción condicional de coste constante. Dentro de la condición
tiene:
o bien una instrucción constante, que no tenemos en cuenta para el cálculo,
o bien tres llamadas recursivas, ambas invocan la función con un tamaño n/6.
//En el segundo y tercer caso de la llamada recursiva hay otra instrucción simple añadida.
Un bucle en el que se repite 2n veces un cálculo consistente en llamar dos veces a una función h(n;
r; i) de coste cuadrático.
La expresión queda T(n) = T(n/6) + T(n/6) + T(n/6) + 2n(n2 + n2 )lo que equivale a que
T(n) = 3T(n/6) + 4n3. Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b) + cnk.
lo que equivale a T(n) = 3T(n/6) + 4n3 y siendo 3 < 63 el coste es O(n3). a = 3, b = 6, k = 3
el segundo algoritmo:
está formado por una instrucción condicional de coste constante cuyo cuerpo incluye
secuencialmente:
(i) una llamada recursiva de tamaño n/8,
(ii) una instrucción simple, que no tenemos en cuenta para el cálculo,
(iii) otra llama recursiva de tamaño n/8,
(iv) un bucle en el que se repite n/2 veces un cálculo consistente en llamar dos veces a una función
h(n; r; i) de coste cuadrático, /*más una instrucción simple*/, y por último
(v) una instrucción simple que no tenemos en cuenta para el cálculo, y otra llamada recursiva de
tamaño n/8.
Sumando los términos nos sale T(n) = T(n/8)+T(n/8)+n/2* (2n2)+T(n/8), lo que equivale a
T(n) = 3T(n/8) + 2n3 / 2 y siendo 3 < 8 3 el coste es O(n3). a = 3, b = 8, k = 3
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
Cuestión 2 (1 punto). Explica cómo pueden ordenarse n valores enteros positivos en tiempo lineal,
sabiendo que el rango de dichos valores es limitado. Explica las ventajas e inconvenientes de ese
método.
Suponiendo que el rango de los valores a ordenar es limitado, es decir, sabemos que por ejemplo el
rango de números a ordenar va de 0 a 2000, podemos generar un vector de 2000 elementos de tipo
entero que almacenará el número de ocurrencias de cada valor de la lista a ordenar en la posición del
vector correspondiente.
3- Recorremos el vector generado mostrando los valores generados en el paso anterior (2) y omitiendo
aquellas posiciones del vector que contengan un 0.
Las ventajas de esta ordenación es que el coste en tiempo de búsqueda es constante, sin embargo se
requiere un determinado espacio de memoria que estará vacío.
Ejemplo: n={1,5,12,15}
V : vector[1..max(n(i))]
For (i = 1; i <= n.length; i ++){
V[n(i)] = n;
}
Indice 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
valor 1 5 12 15
Cuestión 3 (1 punto). ¿Puede un grafo tener dos árboles de recubrimiento mínimo diferentes? En caso
afirmativo poner un ejemplo. En caso negativo demostrarlo formalmente.
Si.
Pedido 1 2 3 4 5 6 7 8 9
Ranura 1 5 5 6 6 4 4 2 2
Recaudación 60 70 80 20 20 30 50 50 90
El coste de ordenar la tabla por orden decreciente de beneficio en el mejor tiempo posible es del orden
de O(n log n).
ESCUELA TECNICA DE INGENIERIA INFORMATICA DE LA UNED (SISTEMAS Y GESTION)
Programación III – Diciembre 2.006 Convocatoria Extraordinaria
Tiempo: 2 horas. Ningún Material.
Cuestión 2 (2 puntos). Explica cómo pueden ordenarse n valores positivos en tiempo lineal,
sabiendo que el rango de dichos valores es limitado.
Cuestión 3 (2 puntos). Dado el grado de la figura, aplica el algoritmo de Dijkstra para hallar los
caminos más cortos desde el nodo 1 hasta uno de los otros nodos, indicando en cada paso:
nodos seleccionados, nodos no seleccionados, vector de distancias y vector de nodos
precedentes.
10
13
2 3
26 7
1
5
5 8
4
Cuestión 1 (2 puntos). Cuenta el número de operaciones elementales que efectúa el siguiente fragmento de
código f en los casos mejor y peor, y a partir de estos resultados da su orden de complejidad, Ο((f)n), y su orden
exacto Θ((f)n).
Para obtener el tiempo de ejecución, calcularemos primero el número de operaciones elementales (OE) que se
realizan:
En la línea (1) se ejecutan 2 OE (una asignación y una comparación) en cada una de las iteraciones del bucle más
otras 2 al final, cuando se efectúa la salida del FOR.
Igual ocurre con la línea (2), también con 3 OE (una asignación, una suma y una comparación) por iteración, más
otras 3 al final del bucle.
En la línea (3) se efectúa una condición, con un total de 4 OE (una diferencia, dos accesos a un vector, y una
comparación).
Las líneas (4) a (6) sólo se ejecutan si se cumple la condición de la línea (3), y realizan un total de 9 OE: 3, 4 y 2
respectivamente.
Con esto:
En el caso mejor para el algoritmo la condición de la línea (3) será siempre falsa, y no se ejecutarán nunca las
líneas (4), (5) y (6). Así, el bucle más interno realizará (n–i) iteraciones, cada una de ellas con 4 OE (línea 3), más
las 3 OE de la línea (2). Por tanto, el bucle más interno realiza un total de
⎛ n ⎞ ⎛ n ⎞
⎜∑ (4 + 3) ⎟ + 3 = 7 ⎜ ∑ 1⎟ + 3 = 7(n − i ) + 3
⎝ j =i +1 ⎠ ⎝ j =i +1 ⎠
OE, siendo el 3 adicional por la condición de salida del bucle. A su vez, el bucle externo repetirá esas 7(n–i)+3
OE en cada iteración, lo que hace que el número de OE que se realizan en el algoritmo sea:
⎛ n ⎞ 7 3
T (n) = ⎜ ∑ (7(n − i ) + 3) + 2 ⎟ + 2 = n 2 + n − 1
⎝ i =1 ⎠ 2 2
En el caso peor, la condición de la línea (3) será siempre verdadera, y las líneas (4), (5) y (6) se ejecutarán en
todas las iteraciones. Por tanto, el bucle más interno realiza
⎛ n ⎞
⎜ ∑ (4 + 9 + 3) ⎟ + 3 = 16(n − i ) + 3
⎝ j =i +1 ⎠
OE. El bucle externo realiza aquí el mismo número de iteraciones que en el caso anterior, por lo que el número de
OE en este caso es:
⎛ n ⎞
T (n) = ⎜ ∑ (16(n − i ) + 3) + 2 ⎟ + 2 = 8n 2 + 10n + 2
⎝ i =1 ⎠
En el caso medio, la condición de la línea (3) será verdadera con probabilidad 1/2. Así, las líneas (4), (5) y (6) se
ejecutarán en la mitad de las iteraciones del bucle más interno, y por tanto realiza
⎛ n 1 ⎞ 23
⎜ ∑ (4 + 9) + 3 ⎟ + 3 = (n − i ) + 3
⎝ j =i +1 2 ⎠ 2
OE. El bucle externo realiza aquí el mismo número de iteraciones que en el caso anterior, por lo que el número de
OE en este caso es:
⎛ n ⎛ 23 ⎞ ⎞ 23 31
T ( n) = ⎜ ∑ ⎜ ( n − i ) + 3 ⎟ + 2 ⎟ + 2 = n 2 n + 2
⎝ i =1 ⎝ 2 ⎠ ⎠ 4 4
b) Como los tiempos de ejecución en los tres casos son polinomios de grado 2, la complejidad del algoritmo es
cuadrática, independientemente de qué caso se trate. Obsérvese cómo hemos analizado el tiempo de ejecución del
algoritmo sólo en función de su código y no respecto a lo que hace, puesto que en muchos casos esto nos llevaría
a conclusiones erróneas. Debe ser a posteriori cuando se analice el objetivo para el que fue diseñado el algoritmo.
En el caso que nos ocupa, un examen más detallado del código del procedimiento nos muestra que el algoritmo
está diseñado para ordenar de forma creciente el vector que se le pasa como parámetro, siguiendo el método de la
Burbuja. Lo que acabamos de ver es que sus casos mejor, peor y medio se producen respectivamente cuando el
vector está inicialmente ordenado de forma creciente, decreciente y aleatoria.
Cuestión 2 (2 puntos). Explica cómo pueden ordenarse n valores positivos en tiempo lineal, sabiendo
que el rango de dichos valores es limitado. (Pág 79)
Suponiendo que el rango de los valores a ordenar es limitado, es decir, sabemos que por ejemplo el
rango de números a ordenar va de 0 a 2000, podemos generar un vector de 2000 elementos de tipo
entero que almacenará el número de ocurrencias de cada valor de la lista a ordenar en la posición del
vector correspondiente.
Problema (4 puntos). Se desea grabar un CD de audio, de capacidad T, con canciones de una colección
n
de n elementos, cuyas duraciones t1,…, tn cumplen: ∑t
i =1
i > n . Diseña un algoritmo que permita
almacenar el máximo número de canciones en el espacio disponible. Puede suponerse que la colección
de canciones está ordenada por longitud de menor a mayor.
Solución:
1. Elección razonada del esquema algorítmico mas eficiente para resolver el problema (0,5 puntos).
Ver explicación en el ejercicio 1 de enero 2005, se trata de un problema de optimización, por lo tanto
descarto divide y vencerás y vuelta atrás. Me queda saber si puedo encontrar una función de selección
para decidirme por un esquema voraz. Eligiendo a los elementos por orden creciente de longitud,
obtenemos una solución óptima, así puedo descartar ramificación y poda.
fun voraz (C: conjunto) dev (S: conjunto)
S ← ∅
mientras ¬ solucion (S) ∧ C ≠ ∅ hacer
x ← Seleccionar (C)
C ←C \ {x}
si completable (S ∪ {x})
entonces S ← S ∪ {x}
fsi
dev S
ffun
2. Demuestra que el problema se puede resolver con el esquema elegido (0,5 puntos).
/**
* Calcula para cada fichero el cociente de la prioridad y el tamaño de cada
* fichero y los ordena de mayor a menor según este cociente. Posteriormente
* los introduce en el disco siguiendo este orden hasta que no quepan en el
* espacio restante.
*/
public void crearDisco() {
int capacidadRestante = capacidadDisco;
int contador = 0; //indice del array donde vamos almacenando los ficheros en disco
ordenar();
imprimirFicheros();
for (int i = 0; i < n; i++) {
if (ficheros[i].getTam() <= capacidadRestante) {
disco[contador++] = ficheros[i];
capacidadRestante -= ficheros[i].getTam();
}
}
/**
* Algoritmo que ordena el array ficheros por el método de la burbuja
*/
private void ordenar() {
Fichero temp;
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (ficheros[j].getRelacion() > ficheros[i].getRelacion()) {
temp = ficheros[i];
ficheros[i] = ficheros[j];
ficheros[j] = temp;
}
}
}
}
/**
* Muestra el contenido del disco por pantalla
*/
public void imprimir() {
for (int i = 0; i < capacidadDisco; i++) {
if (disco[i] != null) {
System.out.println(disco[i].toString());
}
}
}
/**
* Muestra el contenido del array de ficheros
*/
public void imprimirFicheros() {
for (int i = 0; i < n; i++) {
if (ficheros[i] != null) {
System.out.println(ficheros[i].toString());
}
}
}
}
public class Fichero {
private String identificador;
private float tam;
private int prioridad;
private float relacion; //Cociente prioridad-tamaño
En conclusión, el orden de complejidad del algoritmo completo vendrá marcado por la mayor de estas
complejidades parciales y corresponde a la ordenación del vector.
Problema (4 puntos). Hoy es un día duro para el taller Sleepy. Llegan las vacaciones y
a las 8:00 de la mañana n clientes han pedido una revisión de su coche. Como siempre,
todos necesitan que les devuelvan el coche en el menor tiempo posible. Cada coche
necesita un tiempo de revisión ri y al mecánico le da lo mismo por cuál empezar: sabe
que en revisar todos los coches tardará lo mismo independientemente del orden que
elija. Pero al jefe de taller no le da lo mismo, la satisfacción de sus clientes es lo que
importa: es mejor tener satisfechos al mayor número de ellos. Al fin y al cabo, la
planificación la hace él y, evidentemente, un cliente estará más satisfecho cuanto menos
tarden en devolverle el coche. Implementar un programa que decida el orden en el que
revisar uno a uno los coches para maximizar la satisfacción de los clientes de Sleepy.
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
Donde a = 1, b = 2, k = 0 → Θ ( log n )
Cuestión 3 (2 puntos). Dado un vector C [1..n ] de números enteros distintos, y un número entero S,
se pide plantear un programa de complejidad en Θ ( nlogn ) que determine si existen o no dos
elementos de C tales que su suma sea exactamente S. En caso de utilizar algoritmos conocidos no es
necesario codificarlos. (Resuelto en Estructuras de datos y métodos algorítmicos pag 338)
Utilizando la ordenación por fusión el coste del primer paso está en Θ ( nlogn ) , y el coste del segundo
paso está en Θ ( nlogn ) . En el peor caso se hacen n búsquedas binarias de coste Θ ( logn ) cada una de
ellas, por lo tanto el coste total es Θ ( nlogn ) .
funcion buscar − dos ( C [1..n ] de ent , S : ent ) dev existe : bool , x1 , x2 : ent
var V [1..n ] de ent
V := C ; ordenar (V )
j := 1; existe := falso;
mientras j ≤ n ∧ ¬existe hacer
x1 := V [ j ] ; x2 := S − x1 ;
existe, p := busqueda − binaria (V , x2 ,1, n )
existe := existe ∧ p ≠ j {no puede usarse el mismo numero dos veces}
j := j + 1;
fmientras
ffuncion
Problema (4 puntos). Hoy es un día duro para el taller Sleepy. Llegan las vacaciones y a las 8:00 de
la mañana n clientes han pedido una revisión de su coche. Como siempre, todos necesitan que les
devuelvan el coche en el menor tiempo posible. Cada coche necesita un tiempo de revisión ri y al
mecánico le da lo mismo por cuál empezar: sabe que en revisar todos los coches tardará lo mismo
independientemente del orden que elija. Pero al jefe de taller no le da lo mismo, la satisfacción de sus
clientes es lo que importa: es mejor tener satisfechos al mayor número de ellos. Al fin y al cabo, la
planificación la hace él y, evidentemente, un cliente estará más satisfecho cuanto menos tarden en
devolverle el coche. Implementar un programa que decida el orden en el que revisar uno a uno los
coches para maximizar la satisfacción de los clientes de Sleepy.
Elección razonada del esquema algorítmico más eficiente para resolver el problema.
Se trata de un problema de optimización, por lo tanto descarto el esquema de divide y vencerás, para
este problema se puede encontrar una función de selección que lo resuelva, de esta manera descarto el
esquema de ramificación y poda, debemos elegir a los clientes por orden creciente de tiempo de
finalización, este problema se corresponde con el problema de minimización del tiempo en el
sistema.
Esquema general:
fun voraz (C: conjunto) dev (S: conjunto)
S ← ∅
mientras ¬ solucion (S) ∧ C ≠ ∅ hacer
x ← elemento que maximiza objetivo (x)
C ←C \ {x}
si completable (S ∪ {x})
entonces S ← S ∪ {x}
fsi
dev S
ffun
Estructuras de datos:
Tres vectores de enteros, un vector booleano y la función auxiliar ordenar índices.
Cuestión 1 (2 puntos). ¿Cual es el tamaño del problema, n, que determina el orden de complejidad del
siguiente procedimiento? (0.5 puntos). Calcula el orden de complejidad del algoritmo en función de
dicho tamaño (1.5 puntos).
Cuestión 2 (2 puntos). Escribe en Java el algoritmo principal del esquema de ramificación y poda
utilizado en la práctica del presente curso (no es necesario implementar las funciones auxiliares).
Cuestión 3 (2 puntos). Aplica el algoritmo de Kruskal al siguiente grafo indicando claramente en cada
paso qué arista se selecciona, la evolución de las componentes conexas y la evolución de la solución.
NOMBRE:
APELLIDOS:
DNI:
Cuestión 1 (2 puntos)
¿ Cual es el tamaño del problema, n, que determina el orden de complejidad del
siguiente procedimiento? (0.5 puntos). Calcula el orden de complejidad del algoritmo
en función de dicho tamaño (1.5 puntos).
Solución
a) n = (ult - prim)+1
b) Contando las instrucciones tenemos que si se cumple la condición de la lı́nea (1)
el coste es cte.. Si se va por la lı́nea (3), puede irse por la la linea (6) (coste cte), por
la lı́nea (8) (coste t(n/2) + cte) o por la lı́nea (10) (coste t(n/2) + cte). Por tanto,
T(n) = T(n/2) + cte.
Planteamos la recurrencia con reducción del problema mediante división:
n: tamaño del problema a: número de llamadas recursivas n/b: tamaño del subpro-
blema cnk : coste de las instrucciones que no son llamadas recursivas
(
cnk si 0 ≤ n < b
T (n) =
aT (n/b) + cnk si n ≥ b
entonces
Θ(nk ) si a < bk
T (n) ∈ Θ(nk logn) si a = bk
Θ(nnlogb a )
si a > bk
Sin embargo, la solución anterior tiene el problema de que todos los nodos (incluidas
algunas soluciones) se guardan en memoria (en la cola de prioridad) aún sabiendo
que muchos no pueden proporcionar la solución óptima. Por tanto, es posible refinar
el algoritmo anterior para ir eliminando de la cola de prioridad todos los nodos que
podamos ir descartando. Para descartar nodos, primero tenemos que encontrar una
solución que se convierte en la solución de referencia. Todos los nodos ya existentes
con una estimación peor se eliminan de la cola (método depurar) y todos los nuevos
nodos con una estimación peor ya no se meten en la cola. Esta versión necesita menos
memoria, pero puede tener mayor coste computacional puesto que hay que recorrer
la cola cada vez que encontramos una nueva solución. Por otro lado, mantener la
cola tiene menos coste porque en general contendrá menor número de nodos.
public class RamificacionYPoda implements SearchAlgorithm {
public Solution searchSolution(Node nodoInicial) {
Queue<Node> cola = new PriorityQueue<Node>();
cola.add(nodoInicial);
// Soluci’on inicial
int costeMejor = Integer.MAX_VALUE;
while(!cola.isEmpty() ) {
Node nodo = cola.poll();
if( nodo.isSolution() ) {
int costeReal = nodo.getCost();
if( costeReal < costeMejor ) {
costeMejor = costeReal;
solucion = nodo.getSolution();
depurar(cola, costeMejor);
}
} else {
for(Node nodoHijo:nodo.generatePossibleChildren()) {
int cota=nodoHijo.calculateEstimatedCost();
if(cota < costeMejor) {
cola.offer( nodoHijo );
}
}
}
}
return solucion;
}
Cuestión 3 (2 puntos)
Aplica el algoritmo de Kruskal al siguiente grafo indicando claramente en cada paso
qué arista se selecciona, la evolución de las componentes conexas y la evolución de
la solución.
1
3 8
4
3 5 6 4
2
2 9
1 10
7 6
5
Problema (4 puntos)
Disponemos de un conjunto A de n números enteros (tanto positivos como negativos)
sin repeticiones almacenados en una lista. Dados dos valores enteros m y C, siendo
m < n se desea resolver el problema de encontrar un subconjunto de A compuesto
por exactamente m elementos y tal que la suma de los valores de esos m elementos
sea C.
La resolución de este problema debe incluir, por este orden:
1. Esquema general
fun vuelta-atras(e: ensayo) dev (booleano, ensayo)
si valido(e) entonces
dev (cierto,e)
sino
listaensayos <-- compleciones(e)
si esprometedora(hijo) entonces
(es_solucion,solucion) <-- vuelta-atras(hijo)
fsi
fmientras
dev (es_solucion, solucion)
fsi
ffun
si m → n =⇒ T (n) ∈ O(n!)
si m → 1 =⇒ T (n) ∈ O(n)
Alternativa B
1. Esquema general
fun vuelta-atras(e: ensayo, k ) dev (booleano, ensayo)
// e es k-prometedor
si K=limite entonces
dev (cierto,e)
sino
listaensayos <-- compleciones(e,k+1)
si esprometedora(hijo,k+1) entonces
(es_solucion,solucion) <-- vuelta-atras(hijo,k+1)
fsi
fmientras
dev (es_solucion, solucion)
fsi
ffun
En la llamada inicial a vuelta-atras, todas las posiciones de e.candidatos se
inicializan a true y k toma el valor 0.
2. Estructuras de datos
datos: vector de enteros //lista de numeros de entrada
Tipo ensayo=
candidatos: vector de booleanos //indica si el numero de cada posicion
// se incluye en la solucion
suma: entero // valor alcanzado hasta ese momento
num_sumados: entero // cantidad de valores true de los candidatos
3. algoritmo completo
funcion compleciones(e: ensayo, k:entero) dev lista_compleciones
lista_compleciones <-- lista_vacia
nuevo_ensayo <-- e // el valor asignado a la posicion K es true
nuevo_ensayo.suma <-- e.suma + datos[k]
nuevo_ensayo.num_sumados <-- e.num_sumados + 1
lista_compleciones <-- anadir(lista_compleciones, nuevo_ensayo)
dev lista_compleciones
si m → n =⇒ T (n) ∈ O(2n )
si m → 1 =⇒ T (n) ∈ O(n)
Cuestión 1 (1 punto). Describe el grafo asociado al espacio de soluciones del problema del n-puzzle
(Práctica de este curso).
Cuestión 2 (2,5 puntos). Supongamos que un polinomio se representa por un vector v[0..n] de longitud
n+1 donde el valor v[i] es el coeficiente de grado i. Describir claramente los cálculos y la descomposición
algorítmica necesaria para plantear un algoritmo, de orden mejor que cuadrático, que multiplique dos
polinomios P(x) y Q(x) mediante divide y vencerás. NOTA: La solución típica trivial de orden cuadrático
puntúa 0 puntos.
Cuestión 3 (2,5 puntos). Con respecto al algoritmo de ordenación por fusión (Mergesort).
1. Escribe el algoritmo.
2. Dibuja la secuencia de llamadas del algoritmo y la evolución del siguiente vector al ordenarlo.
Para ello ten en cuenta que hay que considerar que el problema es suficientemente pequeño cuando
el subarray es de tamaño 2.
(2,0,2,1,9,6,2,3,5,8)
Problema (4 puntos). Tenemos n objetos de volúmenes v1... vn, y un número ilimitado de recipientes
iguales con capacidad R (con vi ≤ R, ∀i ). Los objetos se deben meter en los recipientes sin partirlos, y sin
superar su capacidad máxima. Se busca el mínimo número de recipientes necesarios para colocar todos los
objetos.
1. Elección razonada del esquema algorítmico más apropiado para resolver el problema. Escriba dicho esquema general.(0,5 puntos).
2. Descripción de las estructuras de datos necesarias (0,5 puntos).
3. Algoritmo completo a partir del refinamiento del esquema general (2 puntos).
4. Estudio del coste del algoritmo desarrollado (1 punto).
RESPUESTAS EXAMEN Programación III. Septiembre 2007 (Original)
Cuestión 1 (1 punto). Describe el grafo asociado al espacio de soluciones del problema del n-puzzle
(Práctica de este curso).
Solución:
• Cada nodo se bifurca como máximo en los cuatro movimientos posibles del hueco (arriba, abajo,
Izquierda, y derecha), siempre que sea posible.
• La profundidad del árbol está limitada por el número de distintos tableros posibles ((nxn)!, siendo n el
tamaño del lado del tablero), ya que no se deben repetir.
Cuestión 2 (2,5 puntos). Supongamos que un polinomio se representa por un vector v[0..n] de longitud
n+1 donde el valor v[i] es el coeficiente de grado i. Describir claramente los cálculos y la descomposición
algorítmica necesaria para plantear un algoritmo, de orden mejor que cuadrático, que multiplique dos
polinomios P(x) y Q(x) mediante divide y vencerás. NOTA: La solución típica trivial de orden cuadrático
puntúa 0 puntos.
Solución:
Es relativamente simple dar con la solución de orden cuadrático. En este caso, se descompone en mitades
P(x) = Axn/2+B y Q(x) = Cxn/2 +D con A,B,C,D polinomios de grado n/2 sacando factor común xn/2 como
se detalla en las expresiones. De esta forma se ve claramente que P·Q es (Axn/2 +B)·( Cxn/2+D) = AC xn +
(AD+BC)xn/2 + BD lo que la solución conlleva 4 multiplicaciones de grado n/2. El coste sería en este caso
cuadrático. Sin embargo hay una manera de organizar las operaciones mediante la cual, no es necesario
calcular AD+BC mediante 2 productos, sino sólo con uno, aprovechando que ya tenemos realizados los
productos BD y AC. En este último caso basta con observar que (A+B)·( C+D) = AC+BC+AD+BD y que
BC+AD = (A+B)·( C+D) – AC – BD con lo que es posible realizar el cálculo con 3 productos en lugar de
4, ya que el coste de las sumas, si consideramos las multiplicaciones como lineales, sería constante. De
manera que (A+B)·( C+D) sería uno de los productos, y AC y BD los otros dos.
Este problema es similar al problema de la multiplicación de números enteros muy grandes del texto base,
capítulo 7.
Cuestión 3 (2,5 puntos). Con respecto al algoritmo de ordenación por fusión (Mergesort).
a) Escribe el algoritmo. (a) El algoritmo se puede encontrar en Brassard & Bratley, pag. 258
b) Dibuja la secuencia de llamadas del algoritmo y la evolución del siguiente vector al ordenarlo.
Para ello ten en cuenta que hay que considerar que el problema es suficientemente pequeño cuando el
subarray es de tamaño 2.
(2,0,2,1,9,6,2,3,5,8)
Problema (4 puntos). Tenemos n objetos de volúmenes v1... vn, y un número ilimitado de recipientes
iguales con capacidad R (con vi ≤ R, ∀i ). Los objetos se deben meter en los recipientes sin partirlos, y
sin superar su capacidad máxima. Se busca el mínimo número de recipientes necesarios para colocar
todos los objetos.
1. Elección razonada del esquema algorítmico más apropiado para resolver el problema. Escriba
dicho esquema general.(0,5 puntos).
2. Descripción de las estructuras de datos necesarias (0,5 puntos).
3. Algoritmo completo a partir del refinamiento del esquema general (2 puntos).
4. Estudio del coste del algoritmo desarrollado (1 punto).
Solución:
Resuelto en Estructuras de datos y métodos algorítmicos. N.Martí, Y. Ortega, J.A Verdejo. Pearson
education, 2004. Ejercicio 15.9.
Indicaciones:
1. Ramificación y poda.
Se trata de un problema de optimización, para el que no existe una función de selección que permita ir
seleccionando a cada paso el objeto que de lugar a la construcción parcial de la solución óptima. Por tanto
no es posible aplicar un algoritmo voraz. Tampoco existe una forma de dividir el problema en
subproblemas que se puedan resolver independientemente, por lo que tampoco es posible un esquema
divide y vencerás.
2.
vector de objetos:
Podemos representar el reparto de objetos entre recipientes mediante un vector en el que cada posición
indique a qué recipiente se ha asignado el objeto correspondiente.
objetos = vector[1..n] of entero
La solución es la cantidad entera S de recipientes empleados.
Montículo de mínimos en el que cada componente almacene una solución parcial (nodo) con su cota
correspondiente.
Solución:
En la solución del ejercicio 14.22, página 499 de Estructura de datos y métodos algorítmicos, se supone
que todo objeto cabe en un envase vacío y, por tanto, se necesitan un máximo de n envases. Las
soluciones se representan en tuplas de la forma (x1,…,xn) donde xi es el envase donde hemos colocado el
objeto i. Como los envases son indistinguibles, el primer objeto siempre se coloca en el primer envase
(x1 = 1) y para cada objeto de los restantes se puede usar uno de los envases ya ocupados, si cabe en
alguno, o coger uno vacío.
Optimista: una cota inferior adecuada es el número de envases ya utilizados en la solución parcial.
Pesimista: una cota superior muy sencilla es considerar un envase extra por cada objeto que nos queda
por empaquetar, pero resulta demasiado grosera. Podemos en cambio ir considerando cada objeto restante,
en el orden que se haya dado, e intentar meterlo en el primer envase utilizand y, en el caso en que no
quepa, intentarlo con el segundo envase, y así hasta agotar todas las posibilidades, en cuyo caso, se
añadirá un nuevo envase a la solución parcial.
En cada nodo, además de la información usual (solución parcial, etapa y prioridad), guardamos el número
de envases utilizados y las capacidades disponibles de cada envase utilizado.
tipos
nodo = reg
sol[1..n]de 1..n
k:1..n
envases:1..n {prioridad}
capacidad[1..n]de real
freg
ftipos
Además, como en la solución mediante la técnica de vuelta atrás (véase el ejercicio 14.22), la búsqueda
podrá acabarse si encontramos una solución con el valor
⎡ ∑ n vi ⎤
optimo = ⎢ i =1 ⎥
⎢⎢ E ⎥⎥
El algoritmo es el siguiente:
PROCEDIMIENTO PROCEDIMIENTO
uno (n,k:entero):entero; dos (n,k:entero):entero;
VAR i,r:entero; VAR i,r:entero;
COMIENZO
COMIENZO
SI n < 2 ENTONCES DEVOLVER(1);
SI n < 2 ENTONCES DEVOLVER(1); SINO
SINO COMIENZO
COMIENZO r ← dos (n DIV 2, k-1);
r ← uno (n DIV 2, k-1); r ← r + dos (n DIV 2, k+1);
r ← r + uno (n DIV 2, k+1); PARA i ← 1 HASTA n HACER
r ← r + uno (n DIV 2, k+2); COMIENZO
DEVOLVER (1); r ← h (n,r,i);
FIN r ← r + h (n,r-1,i);
FIN
FIN r ← r + dos (n DIV 2, k+2);
DEVOLVER (r);
FIN
FIN
Cuestión 2 (2 puntos). Dos amigos juegan a un sencillo juego de adivinación: uno de ellos piensa un
número natural positivo y el otro debe adivinarlo preguntando solamente si es menor o igual que otros
números. ¿Qué esquema algorítmico utilizaría para adivinar el número en tiempo logarítmico? Diseñe el
algoritmo y escríbalo en pseudocódigo. (Resuelto en Estructuras de datos y métodos algorítmicos pag 312)
Cuestión 3 (2 puntos). Declarar en Java las clases y/o estructuras de datos que utilizaría en el problema
de puzzle (práctica de este año).
Problema (4 puntos). La agencia matrimonial Celestina & Co. Quiere informatizar parte de la
organización de parejas entre sus clientes. Cuando un cliente llega a la agencia se describe a sí mismo y
cómo le gustaría que fuera su pareja. Con la información de los clientes la agencia construye dos matrices
M y H que contienen las preferencias de los unos por los otros, tales que la fila M[i,·] es una ordenación de mayor a
menor de las mujeres según las preferencias del i-ésimo hombre y la fila H[i,·] es una ordenación de mayor a menor
de los hombres según las preferencias de la i-ésima mujer. Por ejemplo, M[i,1] almacenaría a la mujer preferida por
el hombre i y M[i,2] a su segunda preferida. Dado el alto índice de divorcios, la empresa se ha planteado como
objetivo que los emparejamientos sean lo más estables posible evitando las siguientes situaciones:
1) Que dada una pareja (h’,m’) se de el caso de m’ prefiera un h sobre h’ y además h’ prefiera a un m sobre m’.
2) Que dada una pareja (h”,m”) se de el caso de h” prefiera un m sobre m” y además m prefiera a h sobre h”.
La agencia quiere que dadas las matrices de preferencia, un programa establezca parejas evitando las dos
situaciones descritas con anterioridad. La resolución de este problema debe incluir, por este orden:
1. Elección del esquema más apropiado, el esquema general y explicación de aplicación al problema. (1 punto).
2. Descripción de las Estructuras de datos necesarias. (0,5 puntos)
3. Algoritmo completo a partir del refinamiento del esquema general. (2 puntos)
4. Estudio del coste del algoritmo desarrollado. (0,5 puntos)
RESPUESTAS EXAMEN Programación III. Septiembre 2007 (Reserva)
con k = 0, a=3 y b=2 queda que 3 > 20 y por tanto la función T(n) tiene un coste Θ ( n log2 3 )
Sumando los términos nos sale T(n) = T(n/2) +T(n/2)+n (2n) +T(n/2), lo que equivale a
T(n) = 3T(n/2) + 2n2 Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b) + cnk y siendo 3 < 2 2 el coste es Θ(n2). a = 3, b = 2, k = 2
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
Cuestión 2 (2 puntos). Dos amigos juegan a un sencillo juego de adivinación: uno de ellos piensa un
número natural positivo y el otro debe adivinarlo preguntando solamente si es menor o igual que otros
números. ¿Qué esquema algorítmico utilizaría para adivinar el número en tiempo logarítmico? Diseñe el
algoritmo y escríbalo en pseudocódigo. (Resuelto en Estructuras de datos y métodos algorítmicos pag 312)
Solución:
Como el número a adivinar puede ser arbitrariamente grande, empezar por el 1 y seguir después en
secuencia hasta alcanzar el número propuesto no es un método práctico. En su lugar, necesitamos hacer
una búsqueda binaria, que nos permita reducir de forma más rápida el conjunto de candidatos. Sin
embargo, el algoritmo de búsqueda binaria funciona con vectores de tamaño fijo y conocido, por lo que lo
primero será encontrar alguna cota superior del número a adivinar, en principio una cota inferior es 1. Para
encontrar dicha cota habrá que seguir un método que genere números cada vez más grandes y de modo
que el incremento también aumente de forma rápida. A tal efecto, podemos utilizar, por ejemplo, las
potencias de 2.
Para adivinar el número utilizaremos una variación de la búsqueda binaria donde no necesitamos un
vector porque se trabaja con un intervalo de enteros positivos y además siempre vamos a tener éxito. Esta
nueva versión en lugar de recibir el número a buscar como argumento lo devuelve como resultado, para
ello utiliza una función interna menor-igual que devuelve cierto si y solo si el número a adivinar es
menor o igual que el propuesto como argumento.
Aunque la función no recibe argumento alguno, su resultado depende del número oculto a adivinar, el cual
determina el comportamiento de la función menor-igual. Si n es el número a adivinar, para determinar
el intervalo donde hacer la búsqueda binaria se necesitan m = ⎡log n ⎤ + 1 preguntas y el tamaño del
intervalo donde hacer la búsqueda binaria es 2m . Por tanto la cantidad total de preguntas a realizar, en el
caso peor está en Θ(log n) . Podríamos considerar otra base para las potencias, observando que cuanto
mayor sea dicha base antes se encontrará la cota superior. Sin embargo hay que tener en cuenta que en
general el intervalo que quedará para la búsqueda binaria será también mayor.
Cuestión 3 (2 puntos). Declarar en Java las clases y/o estructuras de datos que utilizaría en el problema
de puzzle (práctica de este año).
Problema (4 puntos). La agencia matrimonial Celestina & Co. Quiere informatizar parte de la
organización de parejas entre sus clientes. Cuando un cliente llega a la agencia se describe a sí mismo y
cómo le gustaría que fuera su pareja. Con la información de los clientes la agencia construye dos matrices
M y H que contienen las preferencias de los unos por los otros, tales que la fila M[i,·] es una ordenación de mayor a
menor de las mujeres según las preferencias del i-ésimo hombre y la fila H[i,·] es una ordenación de mayor a menor
de los hombres según las preferencias de la i-ésima mujer. Por ejemplo, M[i,1] almacenaría a la mujer preferida por
el hombre i y M[i,2] a su segunda preferida. Dado el alto índice de divorcios, la empresa se ha planteado como
objetivo que los emparejamientos sean lo más estables posible evitando las siguientes situaciones:
1) Que dada una pareja (h’,m’) se de el caso de m’ prefiera un h sobre h’ y además h’ prefiera a un m sobre m’.
2) Que dada una pareja (h”,m”) se de el caso de h” prefiera un m sobre m” y además m prefiera a h sobre h”.
La agencia quiere que dadas las matrices de preferencia, un programa establezca parejas evitando las dos
situaciones descritas con anterioridad. La resolución de este problema debe incluir, por este orden:
1. Elección del esquema más apropiado, el esquema general y explicación de aplicación al problema. (1 punto).
2. Descripción de las Estructuras de datos necesarias. (0,5 puntos)
3. Algoritmo completo a partir del refinamiento del esquema general. (2 puntos)
4. Estudio del coste del algoritmo desarrollado. (0,5 puntos)
• Dos matrices m[1..n, 1..n ] y h[1..n, 1..n ] que contienen las preferencias de unos por los otros.
m [i,j] : mujer a la que prefiere el hombre i-ésimo en el lugar de preferencia j-ésimo.
h [i,j] : hombre al que prefiere la mujer i-ésima en el lugar de preferencia j-ésimo.
• Un array asignado[1..n] que indicará si una mujer está o no asignada libre [i] : valor booleano que
indica si la mujer i-ésima ha sido asignada.
El algoritmo principal posee un bucle para que se ejecuta n veces y llamada recursiva que se ejecuta
también n veces.
El coste adicional sale de que esta función llama a la función estable, esta función posee una complejidad
O(n4).
Cuestión 1 (1,5 puntos). Escribe el grafo asociado al espacio de soluciones del problema del
nonograma, a partir del nodo que se presenta a continuación. Se supone que a partir de dicho nodo se
2
van a comenzar a explotar la fila 3. (No puntúa la exploración por fuerza bruta de coste 2n ).
1 1 1
1 1 2 2
2 1 X X X
1 X
3
1 1
Cuestión 2 (2 puntos). ¿Cuáles de las siguientes afirmaciones son verdaderas y cuales falsas?
Demuestra tus respuestas.
Cuestión 3 (2,5 puntos). En una fontanería se necesitan hacer n reparaciones urgentes, y sabe de antemano el
tiempo que le va a llevar cada una de ellas: en la tarea i-ésima tardará ti minutos. Como en su empresa le
pagan dependiendo de la satisfacción del cliente, necesita decidir el orden en el que atenderá los avisos para
minimizar el tiempo medio de espera de los clientes. Si llamamos Ei a lo que espera el cliente i-ésimo hasta
ver reparada su avería por completo, necesita minimizar la expresión:
n
E (n) = ∑ Ei
i =1
Indica qué esquema o esquemas consideras más adecuados para resolver este problema en los siguientes
casos:
• La empresa sólo dispone de un fontanero para realizar las reparaciones y quiere minimizar el tiempo
medio de espera de los clientes.
• La empresa dispone de F fontaneros para realizar las reparaciones y quiere minimizar el tiempo
medio de espera de los clientes.
Además de nombrar el esquema o esquemas, explica el porque de su elección, los aspectos destacados de
cómo resolverías el problema (función de selección, restricciones, cotas en función del esquema propuesto) y
el coste asociado. No se piden los algoritmos.
Problema (4 puntos). El tío Facundo posee n huertas, cada una con un tipo diferente de árboles frutales. Las
frutas ya han madurado y es hora de recolectarlas. El tío Facundo conoce, para cada una de las huertas, el
beneficio bi que obtendría por la venta de lo recolectado. El tiempo que se tarda en recolectar los frutos de
cada finca es así mismo variable (no unitario) y viene dado por ti. También sabe los días di que tardan en
pudrirse los frutos de cada huerta. Se pide ayudar a decidir al tío Facundo que debe recolectar para maximizar
el beneficio total obtenido.
Cuestión 1 (1,5 puntos). Escribe el grafo asociado al espacio de soluciones del problema del
nonograma, a partir del nodo que se presenta a continuación. Se supone que a partir de dicho nodo se
2
van a comenzar a explotar la fila 3. (No puntúa la exploración por fuerza bruta de coste 2n ).
1 1 1
1 1 2 2
2 1 X X X
1 X
3
1 1
1 1 1
1 1 2 2
2 1 X X X
1 X
3 X X X
1 1 X X
Fila 3
X O
X X X O O X O O
X X X X X O O X X O X O
O X X X O X X O
Fila 4
X O
X X X O
X O X X O O
X O O X X O O O
Cuestión 2 (2 puntos). ¿Cuáles de las siguientes afirmaciones son verdaderas y cuales falsas?
Demuestra tus respuestas.
f (n)
2. Si lim ∈ 0 , entonces f ( n ) ∈ O ( g ( n ) ) pero f ( n ) ∉ Θ ( g ( n ) )
n →∞ g ( n)
f ( n)
3. Si lim ∈ +∞ , entonces f ( n ) ∈ Ω ( g ( n ) ) pero f ( n ) ∉ Θ ( g ( n ) )
n →∞ g ( n)
a) n 2 ∈ O(n 3 )
n2 2n 2 0
lim 3
= lim 2 = lim = =0
n →∞ n n →∞ 3n n →∞ 6n 6
b) n 2 ∈ Ω(n3 )
n2 2n 2 0
lim 3
= lim 2 = lim = =0
n →∞ n n →∞ 3n n →∞ 6n 6
c) 4n 2 − 3n + 2 ∈ Ω(n log n)
4n 2 − 3n + 2 8n − 3 8
lim = lim = 1 = +∞
n →∞ n log n n →∞ ln n + 1
n
d) n !∈ Θ(2n + 1!)
lim
n!
= lim
n ⋅ (n − 1)!
= lim
[ n ⋅ (n − 1)!] / n = lim (n − 1)! = +∞
n →∞ 2n + 1! n →∞ 2n + 1 n →∞ 2n / n + 1/ n n →∞ 2
Cuestión 3 (2,5 puntos). En una fontanería se necesitan hacer n reparaciones urgentes, y sabe de antemano el
tiempo que le va a llevar cada una de ellas: en la tarea i-ésima tardará ti minutos. Como en su empresa le pagan
dependiendo de la satisfacción del cliente, necesita decidir el orden en el que atenderá los avisos para minimizar
el tiempo medio de espera de los clientes. Si llamamos Ei a lo que espera el cliente i-ésimo hasta ver reparada su
avería por completo, necesita minimizar la expresión:
n
E (n) = ∑ Ei
i =1
Indica qué esquema o esquemas consideras más adecuados para resolver este problema en los siguientes casos:
• La empresa sólo dispone de un fontanero para realizar las reparaciones y quiere minimizar el tiempo
medio de espera de los clientes.
• La empresa dispone de F fontaneros para realizar las reparaciones y quiere minimizar el tiempo medio
de espera de los clientes.
Además de nombrar el esquema o esquemas, explica el porque de su elección, los aspectos destacados de cómo
resolverías el problema (función de selección, restricciones, cotas en función del esquema propuesto) y el coste
asociado. No se piden los algoritmos.
En primer lugar hemos de observar que el fontanero siempre tardará el mismo tiempo global T = t1 + t2 + ... + tn
en realizar todas las reparaciones, independientemente de la forma en que las ordene. Sin embargo, los tiempos
de espera de los clientes sí dependen de esta ordenación. En efecto, si mantiene la ordenación original de las
tareas (1, 2, ..., n), la expresión de los tiempos de espera de los clientes viene dada por:
E 1 = t1
E 2 = t1 + t2
.....
En = t1 + t2 + ... + tn .
Lo que queremos encontrar es una permutación de las tareas en donde se minimice la expresión de E(n) que,
basándonos en las ecuaciones anteriores, viene dada por:
Vamos a demostrar que la permutación óptima es aquella en la que los avisos se atienden en orden creciente de
sus tiempos de reparación. Para ello, denominemos X = (x1,x2,...,xn) a una permutación de los elementos
(1,2,...,n), y sean (s1,s2,...,sn) sus respectivos tiempos de ejecución, es decir, (s1,s2,...,sn) va a ser una permutación
de los tiempos orginales (t1,t2,...,tn). Supongamos que no está ordenada en orden creciente de tiempo de
reparación, es decir, que existen dos números xi < xj tales que si > sj. Sea Y = (y1,y2,...,yn) la permutación obtenida
a partir de X intercambiando xi con xj, es decir, yk = xk si k ≠ i y k ≠ j, yi = xj, yj = xi. Si probamos que E(Y) < E(X)
habremos demostrado lo que buscamos, pues mientras más ordenada (según el criterio dado) esté la
permutación, menor tiempo de espera supone. Pero para ello, basta darse cuenta que
E (Y ) = ( n - xi +1) s j + ( n - x j +1) si +
n
∑ ( n - k +1) s
k=1, k ≠ i,k ≠ j
k
En consecuencia, el algoritmo pedido consiste en atender a las llamadas en orden inverso a su tiempo de
reparación. Con esto conseguirá minimizar el tiempo medio de espera de los clientes, tal y como hemos
probado.
En el segundo caso también tenemos que minimizar el tiempo medio de espera de los clientes, pero lo que
ocurre es que ahora existen F fontaneros dando servicio simultámeamente. Basándonos en el método utilizado
anteriormente, la forma óptima de atender los avisos va a ser la siguiente:
• En primer lugar, se ordenan los avisos por orden creciente de tiempo de reparación.
• Un vez hecho esto, se van asignando los avisos por este orden, siempre al fontanero menos ocupado. En
caso de haber varios con el mismo grado de ocupación, se escoge el de número menor.
En otras palabras, si los avisos están ordenados de forma que ti ≤ tj si i < j, asignaremos al fontanero k los avisos
k, k+F, k+2F, ...
Problema (4 puntos). El tío Facundo posee n huertas, cada una con un tipo diferente de árboles frutales. Las
frutas ya han madurado y es hora de recolectarlas. El tío Facundo conoce, para cada una de las huertas, el
beneficio bi que obtendría por la venta de lo recolectado. El tiempo que se tarda en recolectar los frutos de cada
finca es así mismo variable (no unitario) y viene dado por ti. También sabe los días di que tardan en pudrirse los
frutos de cada huerta. Se pide ayudar a decidir al tío Facundo que debe recolectar para maximizar el beneficio
total obtenido. (Solucionado en el ejercicio 15.4 pág 516 de Estructura de datos y métodos algorítmicos)
Solución:
En el ejercicio 14.6, página 463 de Estructura de datos y métodos algorítmicos, vimos la solución a
este problema mediante la técnica de vuelta atrás. Representamos las soluciones mediante tuplas
(x1,…,xn) donde xi = 1 indica que los frutos de la huerta i se recolectan mientras que xi = 0 indica que
los frutos de la huerta no se recolectan.
También vimos como comprobar que un subconjunto de huertas es factible, es decir, que todas pueden
recolectarse sin superar su plazo, manteniendo las huertas ordenadas por tiempo de caducidad
creciente.
En cuanto a las estimaciones a utilizar para el esquema optimista-pesimista, tenemos:
Pesimista: igual que en el ejercicio 15.2, podemos obtener una cota pesimista calculando una posible
solución extensión de la que tengamos: las huertas no consideradas se van recolectando en orden
(acumulando el tiempo), siempre que no se supere su fecha de caducidad.
En cada nodo, además de la información usual (solución parcial, etapa y prioridad), mantendremos el
tiempo y beneficio acumulados.
tipos
nodo = reg
sol[1..n]de 0..1
k:0..n
tiempo.beneficio:real
beneficio-opt:real {prioridad}
freg
ftipos
Cuestión 1 (2 puntos). En la práctica obligatoria del presente curso 2007/2008 se ha tenido que diseñar
y desarrollar un algoritmo para resolver el problema del nonograma. Dado el cuadrado de 4x4 de la
figura, trazar el algoritmo que lo resuelve tal como lo hace el desarrollado para la práctica y explicar
cada paso. Hacer al menos 3 niveles y al menos 2 backtrakings.
1 1 1
1 1 2 2
2 1 X X X
1 X
3 X X X
1 1 X X
Cuestión 2 (2 puntos). Sea T[1..n] con k elementos (k<n) un montículo de mínimos. Se pide programar
una función “flotar” recursiva que dado un nuevo elemento T[k+1] restaure la propiedad de montículo
en T. Una función iterativa que lo resuelva puntuará cero puntos.
Cuestión 3 (2 puntos). Dado n potencia de 2, escribe un algoritmo recursivo que calcule en tiempo
logarítmico el valor de an suponiendo que solo se pueden realizar multiplicaciones y que éstas tienen
coste unitario. Demuestre el coste mediante la ecuación de recurrencia. No justifique el esquema usado,
aplíquelo.
Problema (4 puntos). Una flota de 4 camiones (T1..T4) debe transportar cargamento variado a otras
tantas ciudades (C1..C4). El coste de adjudicar el transporte varía en función de la distancia y de la
peligrosidad del trayecto y se resume en la tabla adjunta. Exponer un algoritmo que calcule de manera
óptima a quién encargarle qué destino de manera que en total el coste sea mínimo.
Cuestión 1 (2 puntos). En la práctica obligatoria del presente curso 2007/2008 se ha tenido que
diseñar y desarrollar un algoritmo para resolver el problema del nonograma. Dado el cuadrado de 4x4
de la figura, trazar el algoritmo que lo resuelve tal como lo hace el desarrollado para la práctica y
explicar cada paso. Hacer al menos 3 niveles y al menos 2 backtrakings.
1 1 1
1 1 2 2
2 1 X X X
1 X
3 X X X
1 1 X X
Solución:
En función del algoritmo desarrollado por el alumno en la práctica.
Fila 1
X O
X X X O
X X X X X O
X X O X
Fila 2
X O
O X O O
O O X O O O
Fila 3
X O
X X X O O X O O
X X X X X O O X X O X O
O X X X O X X O
Fila 4
X O
X X X O
X O X X O O
X O O X X O O O
Cuestión 2 (2 puntos). Sea T[1..n] con k elementos (k<n) un montículo de mínimos. Se pide
programar una función “flotar” recursiva que dado un nuevo elemento T[k+1] restaure la propiedad de
montículo en T. Una función iterativa que lo resuelva puntuará cero puntos.
ffun
( )
En este problema: a=1, b=2, k=0, luego el caso es T ( n ) ∈ Θ n k logn por lo que el coste es T ( n ) ∈ Θ ( logn )
Problema (4 puntos). Una flota de 4 camiones (T1..T4) debe transportar cargamento variado a otras
tantas ciudades (C1..C4). El coste de adjudicar el transporte varía en función de la distancia y de la
peligrosidad del trayecto y se resume en la tabla adjunta. Exponer un algoritmo que calcule de manera
óptima a quién encargarle qué destino de manera que en total el coste sea mínimo.
Solución:
El problema de las asignaciones Seccion 9.7 Brassad&Bradley. Similar al problema 4.6 del libro
Esquemas Algorítmicos: Enfoque metodológico y problemas resueltos. Gonzalo, J. Rodríguez, M.
ejercicio 15.1, página 508 de Estructura de datos y métodos algorítmicos.
El algoritmo es el siguiente:
Coste:
En este caso únicamente podemos hallar una cota superior del coste del algoritmo por descripción del
espacio de búsqueda. En el caso peor se generan (k-1) hijos por cada nodo del nivel k, habiendo n. Por
tanto, el espacio a recorrer siempre será menor que n!.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (1 punto). Escribe el grafo asociado al espacio de soluciones del problema del
nonograma, a partir del nodo que se presenta a continuación. Se supone que a partir de dicho nodo se
2
van a comenzar a explotar la fila 3. (No puntúa la exploración por fuerza bruta de coste 2n ).
1 1
1 2 2 2
2 X X
1 2 X X X
1 1
2
Cuestión 2 (2 puntos). El algoritmo mergesort posee una complejidad T(n) ∈ Θ (n log n), describa y
demuestre un caso en el que mergesort tiene una complejidad T(n) ∈ Θ (n2).
Cuestión 3 (3 puntos). Considere un array A[1..n] ordenado y formado por enteros diferentes,
algunos de los cuales pueden ser negativos. Escriba un algoritmo recursivo que calcule en tiempo
logarítmico un índice i tal que 1 ≤ i ≤ n y T[i] = i , siempre que este índice exista, devolviendo -1 si no
existe. Se supone que las operaciones elementales tienen coste unitario. Demuestre el coste mediante
la ecuación de recurrencia. No justifique el esquema usado, aplíquelo.
Problema (4 puntos). Una empresa de mensajería tiene n repartidores con distintas velocidades según
el tipo de envío. Se trata de asignar los próximos n envíos, uno a cada repartidor, minimizando el
tiempo total de todos los envíos. Para ello se conoce de antemano la tabla de tiempos T[1..n,1..n] en la
que el valor t[i,j] corresponde al tiempo que emplea el repartidor i en realizar el envío j. Se pide:
Cuestión 1 (1 punto). Escribe el grafo asociado al espacio de soluciones del problema del
nonograma, a partir del nodo que se presenta a continuación. Se supone que a partir de dicho nodo se
2
van a comenzar a explotar la fila 3. (No puntúa la exploración por fuerza bruta de coste 2n ).
1 1
1 2 2 2
2 X X
1 2 X X X
1 1
2
1 1
1 2 2 2
2 X X
1 2 X X X
1 1 X X
2 X X
Fila 3:
X O
O X O O
O X X O X O
O X O X O X O O
Fila 4:
X O
X X X O
Cuestión 2 (2 puntos). El algoritmo mergesort posee una complejidad T(n) ∈ Θ (n log n), describa y
demuestre un caso en el que mergesort tiene una complejidad T(n) ∈ Θ (n2).
Solución:
Si utilizamos como posición de corte el penúltimo o (segundo) elemento del vector de forma
consecutiva, el coste será equivalente a:
t (n) = t(n−1) + t(1) + g(n) , donde g(n) ∈ Θ(n) de forma que t (n) ∈ Θ (n2)
Cuestión 3 (3 puntos). Considere un array A[1..n] ordenado y formado por enteros diferentes,
algunos de los cuales pueden ser negativos. Escriba un algoritmo recursivo que calcule en tiempo
logarítmico un índice i tal que 1 ≤ i ≤ n y T[i] = i , siempre que este índice exista, devolviendo -1 si no
existe. Se supone que las operaciones elementales tienen coste unitario. Demuestre el coste mediante
la ecuación de recurrencia. No justifique el esquema usado, aplíquelo.
Ecuación de recurrencia:
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k logn ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
( )
En este problema: a=1, b=2, k=0, luego el caso es T ( n ) ∈ Θ n k logn por lo que el coste es T ( n ) ∈ Θ ( logn )
Problema (4 puntos). Una empresa de mensajería tiene n repartidores con distintas velocidades según
el tipo de envío. Se trata de asignar los próximos n envíos, uno a cada repartidor, minimizando el
tiempo total de todos los envíos. Para ello se conoce de antemano la tabla de tiempos T[1..n,1..n] en la
que el valor t[i,j] corresponde al tiempo que emplea el repartidor i en realizar el envío j. Se pide:
Se trata de un problema de optimización con restricciones. Por tanto, podría ser un esquema voraz o un
esquema de ramificación y poda. Sin embargo descartamos el esquema voraz porque no es posible
encontrar una función de selección y de factibilidad tales que una vez aceptado un candidato se
garantice que se va alcanzar la solución óptima. Se trata, por tanto, de un algoritmo de ramificación y
poda.
nodo=tupla
asignaciones: vector[1..N];
último_asignado: cardinal;
filas_no_asignadas: lista de cardinal;
coste: cardinal;
Las funciones generales del esquema general que hay que instanciar son:
1. a. solución(nodo): si se han realizado N asignaciones (último_asignado==N)
2. b. acotar(nodo,costes): nodo.coste + “mínimo coste de las columnas no asignadas”
3. c. compleciones(nodo,costes): posibilidades para la siguiente asignación (valores posibles para
asignaciones[último_asignado+1])
4.
Función asignación(costes[1..N,1..N]) dev solución[1..N]
Montículo:=montículoVacío();
nodo.último_asignado=0;
nodo.coste=0;
cota:=acotar(nodo,costes);
poner((cota,nodo),Montículo);
mientras no vacío(Montículo) hacer
(cota,nodo):=quitarPrimero(Montículo);
si nodo.último_asignado==N entonces
devolver nodo.asignaciones;
si no para cada hijo en compleciones(nodo,costes) hacer
cota:=acotar(hijo,costes);
poner((cota,hijo),Montículo);
fsi;
fmientras
devolver ∅;
Coste:
En este caso únicamente podemos hallar una cota superior del coste del algoritmo por descripción del
espacio de búsqueda. En el caso peor se generan (k-1) hijos por cada nodo del nivel k, habiendo n. Por
tanto, el espacio a recorrer siempre será menor que n!.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (1 punto). Escribe el grafo asociado al espacio de soluciones del problema del
nonograma, a partir del nodo que se presenta a continuación. Se supone que a partir de dicho nodo se
2
van a comenzar a explotar la fila 3. (No puntúa la exploración por fuerza bruta de coste 2n ).
1 1 1
1 1 2 2
2 1 X X X
1 X
3
1 1
Cuestión 3 (2 puntos). Aplique el algoritmo de Prim al siguiente grafo empezando por el nodo 1. Indique
claramente en cada paso qué arista se selecciona, y la evolución de la solución.
Problema (4 puntos). Un cajero automático dispone de n tipos de billetes distintos teniendo cada uno
de los n tipos un valor distinto. Se trata de calcular si es posible suministrar al cliente el valor exacto
solicitado, y si este fuera el caso el sistema deberá suministrar el conjunto de billetes que forman una
solución, además se desea que el sistema utilice el menor número de billetes posible. Se puede suponer
que el número de billetes de cada tipo disponibles es finito.
Cuestión 1 (1 punto). Escribe el grafo asociado al espacio de soluciones del problema del
nonograma, a partir del nodo que se presenta a continuación. Se supone que a partir de dicho nodo se
2
van a comenzar a explotar la fila 3. (No puntúa la exploración por fuerza bruta de coste 2n ).
1 1 1
1 1 2 2
2 1 X X X
1 X
3
1 1
1 1 1
1 1 2 2
2 1 X X X
1 X
3 X X X
1 1 X X
Fila 3
X O
X X X O O X O O
X X X X X O O X X O X O
O X X X O X X O
Fila 4
X O
X X X O
X O X X O O
X O O X X O O O
Cuestión 2 (3 puntos). Hallar formalmente el coste de los siguientes algoritmos, siendo
h ( n, r , k ) ∈ Ο ( n ) NOTA: Acertar a ojo se evalúa con cero puntos.
Sumando los términos nos sale T(n) = T(n/4) +T(n/4)+n (2n) +T(n/4), lo que equivale a
T(n) = 3T(n/4) + 2n2 Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b) + cnk y siendo 3 < 4 2 el coste es Θ(n2). a = 3, b = 4, k = 2
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
Cuestión 3 (2 puntos). Aplique el algoritmo de Prim al siguiente grafo empezando por el nodo 1. Indique
claramente en cada paso qué arista se selecciona, y la evolución de la solución.
2 5 2 5
1 3 4 1 3 4
7 6 10 8 7 6 10 8
2 5 2 5
4 4
1 3 1 3
6 6
EVOLUCION EVOLUCION
PASO ARISTA
COMPONENTES SOLUCION
INICIO - {1}{2}{3}{4}{5}{6} ∅
1 (1,3) {1,3}{2}{4}{5}{6} {(1,3)}
2 (3,4) {1,3,4}{2}{5}{6} {(1,3), (3,4)}
3 (2,3) {1,2,3,4}{5}{6} {(1,3), (3,4),(2,3)}
4 (2,6) {1,2,3,4,6}{5} {(1,3), (3,4),(2,3),( 2,6)}
5 (5,6) {1,2,3,4,5,6} {(1,3), (3,4),(2,3),( 2,6),(5,6)}
Proceso terminado porque sólo queda una única componente conexa
Problema (4 puntos). Un cajero automático dispone de n tipos de billetes distintos teniendo cada uno
de los n tipos un valor distinto. Se trata de calcular si es posible suministrar al cliente el valor exacto
solicitado, y si este fuera el caso el sistema deberá suministrar el conjunto de billetes que forman una
solución, además se desea que el sistema utilice el menor número de billetes posible. Se puede suponer
que el número de billetes de cada tipo disponibles es finito.
Solución:
Se trata de un problema de optimización, por lo tanto descarto el esquema de vuelta atrás, descarto
también el esquema de divide y vencerás ya que no se puede dividir el problema en subproblemas
iguales. Puede resolverse por un esquema voraz o ramificación y poda, voy a descartar el voraz ya que
auque es posible encontrar una función de selección y de factibilidad para resolver el problema no lo
haría de una forma optima, por lo tanto utilizaré ramificación y poda. El problema es similar al
Ejercicio 15.2, página 511 de Estructura de datos y métodos algorítmicos.
El algoritmo es el siguiente:
tipos
nodo = reg
sol[1..n]de 0..1
k:0..n
peso, beneficio:real
beneficio-opt:real {prioridad}
freg
ftipos
fun cajero-rp(P[1..n], V[1..n]de real, M: real)dev (sol-mejor[1..n]de 0..1, beneficio-mejor:real)
var X,Y: nodo,C:colapr[nodo]
{generamos raíz}
Y.k:=0; Y.peso :=0; Y.beneficio:=0;
(Y.beneficio-opt,beneficio-mejor):= calculo-estimaciones(P, V, M, Y.k, Y.peso, Y.beneficio)
C:= cp-vacia(); añadir(C,Y)
mientras ¬es-cp-vacia?(C)^(minimo(C).beneficio-opt ≥ beneficio-mejor) hacer
Y:= maximo (C); eliminar-max(C)
X.k:=Y.k+1; X.sol:=Y.sol;
{probamos a meter el objeto en la mochila}
si Y.peso + P[X.k] ≤ M entonces
{es factible y, por tanto, las estimaciones coinciden con las de Y}
{beneficio-opt(X) = beneficio-opt(Y) ≥ beneficio-mejor }
X.sol[X.k]:=1; X.peso:= Y.peso + P[X.k]
X.beneficio:= Y.beneficio + V[X.k]; X.beneficio-opt := Y.beneficio-opt
si X.k = n entonces { beneficio(X) = beneficio-opt(X) ≥ beneficio-mejor }
sol-mejor:=X.sol; beneficio-mejor:= X.beneficio
sino
añadir(C,X)
{no se puede mejorar beneficio mejor}
fsi
fsi
{probamos a no meter el objeto (siempre es factible)}
(X.beneficio-opt, pes) := calculo-estimaciones (P, V, M, X.k, Y.peso, Y.beneficio)
si X.beneficio-opt ≥ beneficio-mejor entonces
X.sol[X.k] := 0; X.peso := Y.peso
X.beneficio := Y.beneficio
si X.k = n enteonces
sol-mejor := X.sol; beneficio-mejor := X.beneficio
sino
añadir(C,X)
beneficio-mejor := max(beneficio-mejor, pes)
fsi
fsi
fmientras
ffun
fun calculo-estimaciones (P[1..n], V[1..n] de real, M: real, k: 0..n, peso, beneficio:real) dev
(opt, pes: real)
hueco := M-peso;
pes := beneficio; opt := beneficio;
j := k+1
mientras j ≤ n ^ P[j] ≤ hueco hacer
{podemos coger el objeto j entero}
hueco := hueco-P[j]
opt := opt + V[j]; pes:= pes + V[j]
j := j+1 ;
fmientras
si j ≤ n entonces {quedan objetos por probar}
{fraccionamos el objeto j (solucion voraz)}
opt := opt + (hueco/P[j]*V[j]
{extendemos a una solución en la versión 0/1}
j := j+1
mientras j ≤ n ^ hueco > 0 hacer
si P[j] ≤ hueco entonces
hueco := hueco – P[j]
pes := pes + V[j]
fsi
j := j+1
fmientras
fsi
ffun
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
PROCEDIMIENTO PROCEDIMIENTO
uno (n,k:entero):entero; dos (n,k:entero):entero;
VAR i,r:entero; VAR i,r:entero;
COMIENZO
COMIENZO
SI n < 2 ENTONCES DEVOLVER(1);
SI n < 2 ENTONCES DEVOLVER(1); SINO
SINO COMIENZO
COMIENZO r ← dos (n DIV 2, k-1);
r ← uno (n DIV 2, k-1); r ← r + dos (n DIV 2, k+1);
r ← r + uno (n DIV 2, k+1); PARA i ← 1 HASTA n HACER
r ← r + uno (n DIV 2, k+2); COMIENZO
DEVOLVER (1); r ← h (n,r,i);
FIN r ← r + h (n,r-1,i);
FIN
FIN r ← r + dos (n DIV 2, k+2);
DEVOLVER (r);
FIN
FIN
Cuestión 2 (2 puntos). Dos amigos juegan a un sencillo juego de adivinación: uno de ellos piensa un
número natural positivo y el otro debe adivinarlo preguntando solamente si es menor o igual que otros
números. ¿Qué esquema algorítmico utilizaría para adivinar el número en tiempo logarítmico? Diseñe el
algoritmo y escríbalo en pseudocódigo. (Resuelto en Estructuras de datos y métodos algorítmicos pag 312)
Problema (4 puntos). Se dispone de una cinta secuencial donde se almacenan programas. Los programas
ocupan una longitud en la cinta. Cada programa Pi ocupa una longitud Li, lo que equivale a tardar un
tiempo Ti en su lectura secuencial. Programe un algoritmo que calcule la disposición de los programas en
la cinta de manera que se minimice el tiempo medio de acceso a cualquier programa Pi.
1. Elección del esquema más apropiado, el esquema general y explicación de aplicación al problema. (1 punto).
2. Descripción de las Estructuras de datos necesarias. (0,5 puntos)
3. Algoritmo completo a partir del refinamiento del esquema general. (2 puntos)
4. Estudio del coste del algoritmo desarrollado. (0,5 puntos)
RESPUESTAS EXAMEN Programación III. Diciembre 2008
con k = 0, a=3 y b=2 queda que 3 > 20 y por tanto la función T(n) tiene un coste Θ ( n log2 3 )
Sumando los términos nos sale T(n) = T(n/2) +T(n/2)+n (2n) +T(n/2), lo que equivale a
T(n) = 3T(n/2) + 2n2 Aplicando la resolución genérica de las expresiones del tipo
T(n) = aT(n/b) + cnk y siendo 3 < 2 2 el coste es Θ(n2). a = 3, b = 2, k = 2
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
(
⎪⎩Θ n b )
log a , si a > b k
Cuestión 2 (2 puntos). Dos amigos juegan a un sencillo juego de adivinación: uno de ellos piensa un
número natural positivo y el otro debe adivinarlo preguntando solamente si es menor o igual que otros
números. ¿Qué esquema algorítmico utilizaría para adivinar el número en tiempo logarítmico? Diseñe el
algoritmo y escríbalo en pseudocódigo. (Resuelto en Estructuras de datos y métodos algorítmicos pag 312)
Solución:
Como el número a adivinar puede ser arbitrariamente grande, empezar por el 1 y seguir después en
secuencia hasta alcanzar el número propuesto no es un método práctico. En su lugar, necesitamos hacer
una búsqueda binaria, que nos permita reducir de forma más rápida el conjunto de candidatos. Sin
embargo, el algoritmo de búsqueda binaria funciona con vectores de tamaño fijo y conocido, por lo que lo
primero será encontrar alguna cota superior del número a adivinar, en principio una cota inferior es 1. Para
encontrar dicha cota habrá que seguir un método que genere números cada vez más grandes y de modo
que el incremento también aumente de forma rápida. A tal efecto, podemos utilizar, por ejemplo, las
potencias de 2.
Para adivinar el número utilizaremos una variación de la búsqueda binaria donde no necesitamos un
vector porque se trabaja con un intervalo de enteros positivos y además siempre vamos a tener éxito. Esta
nueva versión en lugar de recibir el número a buscar como argumento lo devuelve como resultado, para
ello utiliza una función interna menor-igual que devuelve cierto si y solo si el número a adivinar es
menor o igual que el propuesto como argumento.
Aunque la función no recibe argumento alguno, su resultado depende del número oculto a adivinar, el cual
determina el comportamiento de la función menor-igual. Si n es el número a adivinar, para determinar
el intervalo donde hacer la búsqueda binaria se necesitan m = ⎡log n⎤ + 1 preguntas y el tamaño del
intervalo donde hacer la búsqueda binaria es 2m . Por tanto la cantidad total de preguntas a realizar, en el
caso peor está en Θ(log n) . Podríamos considerar otra base para las potencias, observando que cuanto
mayor sea dicha base antes se encontrará la cota superior. Sin embargo hay que tener en cuenta que en
general el intervalo que quedará para la búsqueda binaria será también mayor.
Cuestión 3 (2 puntos). Inserte los valores 2,6,5,4,2,9,1,1 en un montículo de mínimos y muestre el
montículo a cada paso.
1. V=[2]
2. V=[2,6]
3. V=[2,6,5]
4. V=[2,4,5,6]
5. V=[2,2,5,6,4]
6. V=[2,2,5,6,4,9]
7. V=[1,2,2,6,4,9,5]
8. V=[1,1,2,2,4,9,5,6]
2 2 2 2 2
6 6 5 4 5 2 5
6 4
6
2 1 1
2 5 2 2 1 2
6 4 9 6 4 9 5 2 4 9 5
6
Problema (4 puntos). Se dispone de una cinta secuencial donde se almacenan programas. Los programas
ocupan una longitud en la cinta. Cada programa Pi ocupa una longitud Li, lo que equivale a tardar un
tiempo Ti en su lectura secuencial. Programe un algoritmo que calcule la disposición de los programas en
la cinta de manera que se minimice el tiempo medio de acceso a cualquier programa Pi.
1. Elección del esquema más apropiado, el esquema general y explicación de aplicación al problema. (1 punto).
2. Descripción de las Estructuras de datos necesarias. (0,5 puntos)
3. Algoritmo completo a partir del refinamiento del esquema general. (2 puntos)
4. Estudio del coste del algoritmo desarrollado. (0,5 puntos)
Estructuras de datos:
Tres vectores de enteros, un vector booleano y la función auxiliar ordenar índices.
Cuestión 1 (1 punto).
En la práctica obligatoria del presente curso 2008/2009 se ha tenido que diseñar y
desarrollar un algoritmo para resolver el problema del puzzle Shikaku. Dibuje todas las
complecciones de los niveles necesarios hasta llegar a una solución, indicando además en
qué puntos se produce un backtracking. El punto de partida es la casilla marcada con una
flecha, que contiene un 4. las casillas sombreadas indican que ya tienen asignado un
rectángulo.
Cuestión 2 (2 puntos). Indique y justifique cuales de las siguientes afirmaciones sobre órdenes de
complejidad son ciertas y cuales falsas:
a) 5n3 + 2n 2 ∈ O(n 3 ) ;
b) n3 ∈ Θ( 5n 3 + 2n 2 ) ;
c) Ο(t (n)) = Ω(t (n)) => t (n) ∈ Θ(n) ;
d) Ω( f ( n) | n = 2 k ) ⊂ Ω( f ( n) | n = 2 k )
Cuestión 3 (3 puntos).
Considere dos vectores f y g de n elementos que representan los valores que toman dos funciones en
el intervalo [0..n-1]. Los dos vectores están ordenados, pero el primero f es un vector estrictamente
creciente (f[0] < f[1] < … < f[n-1]), mientras que g es un vector estrictamente decreciente (g[0] >
g[1] > … > g[n-1]). Las curvas que representan dichos vectores se cruzan en un punto concreto, y lo
que se desea saber es si dicho punto está contenido entre las componentes de ambos vectores, es
decir, si existe un valor i tal que f[i] = g[i] para 0 ≤ i ≤ n-1. Se pide una función recursiva de coste
logarítmico que devuelva i si existe y -1 en otro caso. Se supone que las operaciones elementales
tienen coste unitario. Demuestre el coste mediante la ecuación de recurrencia. No justifique el
esquema usado, aplíquelo.
La figura muestra un ejemplo en el que las gráficas se cruzan en i=8 que se corresponde con el valor
25 en ambas curvas.
Problema (4 puntos).
Se dispone de n cubos identificados por un número del 1 al n. Cada cubo tiene impresa en cada una
de sus caras una letra distinta. Se indica además una palabra de n letras. Se trata de colocar los n
cubos uno a continuación de otro, de forma que con esta disposición se pueda formar la palabra
dada. Como entre diferentes cubos puede haber letras repetidas, la solución puede no ser única o no
existir. Se pide un algoritmo para encontrar una solución o indicar que no la hay.
Cuestión 3 (3 puntos). Los residentes de una ciudad no quieren pavimentar todas sus calles,
sino sólo aquellas que les permitan ir de una intersección a otra cualquiera de la ciudad con
comodidad. Quieren gastarse lo menos posible en la pavimentación, teniendo en cuenta que
el coste es directamente proporcional a la longitud de las calles que hay que pavimentar. El
alcalde querría saber qué calles tiene que pavimentar para gastarse lo menos posible.
1. Indique qué esquema aplicarías para resolver el problema. Justifícalo y escribe el esquema
general.
2. Explique los aspectos destacados de cómo resolverías el problema en función del esquema
elegido (Por ejemplo: voraz: función de selección, demostración de optimalidad; divide y
vencerás: tamaño del problema, cómo se divide del problema, subalgoritmo básico; vuelta
atrás: descripción del espacio de búsqueda, compleciones; ramificación y poda: función de
cota, compleciones…).
3. Indique el coste asociado y justifíquelo.
NO se pide el algoritmo.
Problema (4 puntos). Tenemos un conjunto de n componentes electrónicas (c1,…cn) para
colocar en n posiciones sobre una placa. Nos dan dos matrices N y D de dimensiones n x n,
donde N[i,j] indica el número de conexiones necesarias entre la componente ci y la
componente cj, y D[p,q] indica la distancia sobre la placa entre la posición p y la posición q
(ambas matrices son simétricas y con diagonales nulas). Un cableado (x1,...xn) de la placa
consiste en la colocación de cada componente ci en una posición distinta. La longitud total de
este cableado viene dada por la fórmula:
4
6 4
4
6
4
Solución:
Cuestión 2 (2 puntos). Responda a las siguientes cuestiones.
1. ¿Qué diferencias hay entre un montículo y un árbol binario de búsqueda?
2. Dibuje un montículo (el árbol) y un árbol binario de búsqueda con los
siguientes nodos: 6, 12, 18, 20, 27, 34, 35.
Solución:
1. En un árbol binario de búsqueda el valor contenido en todos los nodos
internos es mayor o igual que los valores contenidos en su hijo izquierdo o
en cualquiera de los descendientes de ese hijo, y menor o igual que los
valores contenidos en su hijo derecho o en cualquiera de los descendientes
de ese hijo. Mientras que un montículo es un árbol binario esencialmente
completo, en el que el valor de cada uno de los nodos internos es mayor o
igual que los valores de sus hijos. Esto se denomina propiedad del
montículo. A diferencia de un árbol binario de búsqueda, un montículo se
puede implementar como un vector sin ningún puntero explícito.
12 34
6 18 35
27
Montículo: 35
18 34
6 12 20
27
Cuestión 3 (3 puntos). Los residentes de una ciudad no quieren pavimentar todas
sus calles, sino sólo aquellas que les permitan ir de una intersección a otra
cualquiera de la ciudad con comodidad. Quieren gastarse lo menos posible en la
pavimentación, teniendo en cuenta que el coste es directamente proporcional a la
longitud de las calles que hay que pavimentar. El alcalde querría saber qué calles
tiene que pavimentar para gastarse lo menos posible.
NO se pide el algoritmo.
Solución:
Problema (4 puntos).
Tenemos un conjunto de n componentes electrónicas (c1,…cn) para colocar en n
posiciones sobre una placa. Nos dan dos matrices N y D de dimensiones n x n,
donde N[i,j] indica el número de conexiones necesarias entre la componente ci y la
componente cj, y D[p,q] indica la distancia sobre la placa entre la posición p y la
posición q (ambas matrices son simétricas y con diagonales nulas). Un cableado
(x1,...xn) de la placa consiste en la colocación de cada componente ci en una
posición distinta . La longitud total de este cableado viene dada por la fórmula:
N[i, j]D[ x , x ]
i
i j
i j
Solución:
Indicaciones:
Estructuras de datos:
Mantendremos marcadores con las posiciones ya utilizadas y el coste del
cableado que representa la solución parcial en la que nos encontramos.
nodo=tupla
asignaciones: vector[1..N];
último_asignado: cardinal;
componentes_no_asignadas: lista de cardinal;
coste: real;
coste_optimo: real
Montículo de mínimos (cota mejor la de menor coste)
Cotas:
Cota inferior:
Sea cost el coste de la solución parcial (x1,...,xk), y sema minD el mínimo global
de la matriz D. Entonces una cota inferior a la longitud del cableado de la solución
es
n j− 1
Cota superior:
Sea maxD el máximo global de la matriz D, entonces una cota superior es:
n j− 1
Coste:
El árbol de exploración tiene n niveles.
En este caso únicamente podemos hallar una cota superior del coste del algoritmo
por descripción del espacio de búsqueda. En el caso peor se generan (k-1) hijos
por cada nodo del nivel k, habiendo n. Por tanto, el espacio a recorrer siempre
será menor que n!.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (1,5 puntos). ¿Para qué tipo de operaciones el montículo es una estructura adecuada?
¿Qué coste tiene asociado? Escribe el procedimiento flotar que flote el nodo i en un montículo T[1..n].
Cuestión 2 (1,5 puntos). Dado el siguiente grafo, rellene una tabla similar a la adjunta indicando paso
a paso cómo el algoritmo de Prim halla un árbol de recubrimiento mínimo (ARM) asociado a dicho
grafo, a partir del nodo 1.
Cuestión 3 (3 puntos).
a) ¿Qué sucede con la eficacia de los algoritmos de divide y vencerás si en lugar de utilizar un
umbral para decidir cuando hay que volver al subalgoritmo básico, empleamos la recurrencia
un máximo de r veces, para alguna constante r, y utilizamos después el subalgoritmo básico?
b) Se dispone de una bolsa de n monedas de oro y una de ellas es falsa. Lo único que distingue a
la moneda falsa de las legales es su peso, aunque no hay seguridad de que éste sea mayor o
menor que el de las legales. Para descubrir cual es falsa, se dispone de una balanza con dos
platillos para comparar el peso de dos conjuntos de monedas. En cada pesada lo único que se
puede observar es si la balanza queda equilibrada, si pesan más los objetos de la derecha o si
pesan más los objetos de la izquierda. Indicad y demostrad informalmente cuantas pesadas
hacen falta como mínimo, siendo n ≥3, para determinar cual es la moneda falsa y si pesa más o
menos que las auténticas. No se pide el algoritmo.
Problema (4 puntos).
Se quiere realizar en un soporte secuencial (cinta de dos caras) una recopilación de canciones
preferidas. Se dispone de una lista de n canciones favoritas, junto con la duración individual de cada
una. Lamentablemente, la cinta de T minutos no tiene capacidad para contener todas las canciones, por
lo que se les ha asignado una puntuación (cuanto más favorita es, mayor es la puntuación). Se pretende
obtener la mejor cinta posible según la puntuación, teniendo en cuenta que las canciones deben caber
enteras y no es admisible que una canción se corte al final de una de las caras.
1. Elección razonada del esquema algorítmico mas eficiente para resolver el problema (0,5 puntos).
2. Estructuras de datos (0,5 puntos).
3. Algoritmo completo a partir del refinamiento del esquema general (2,5 puntos).
4. Estudio del coste del algoritmo desarrollado (0,5 punto).
RESPUESTAS EXAMEN Programación III. Septiembre 2009 (Original)
Cuestión 1 (1,5 puntos). ¿Para qué tipo de operaciones el montículo es una estructura adecuada?
¿Qué coste tiene asociado? Escribe el procedimiento flotar que flote el nodo i en un montículo T[1..n].
Para las operaciones de listas de prioridad dinámica: hallar el elemento prioritario de un conjunto
(O(1)), eliminarlo (O(logn)), añadir un elemento en orden de prioridad (O(logn)). Libro página 188.
Función COSTE
Vacío 1
Max 1
Min 1
Buscar 1
Añadir O ( logn )
Borrar O ( logn )
Hundir O ( logn )
Flotar O ( logn )
Fusionar O (n)
Cuestión 2 (1,5 puntos). Dado el siguiente grafo, rellene una tabla similar a la adjunta indicando paso
a paso cómo el algoritmo de Prim halla un árbol de recubrimiento mínimo (ARM) asociado a dicho
grafo, a partir del nodo 1.
b) Se dispone de una bolsa de n monedas de oro y una de ellas es falsa. Lo único que distingue a
la moneda falsa de las legales es su peso, aunque no hay seguridad de que éste sea mayor o
menor que el de las legales. Para descubrir cual es falsa, se dispone de una balanza con dos
platillos para comparar el peso de dos conjuntos de monedas. En cada pesada lo único que se
puede observar es si la balanza queda equilibrada, si pesan más los objetos de la derecha o si
pesan más los objetos de la izquierda. Indicad y demostrad informalmente cuantas pesadas
hacen falta como mínimo, siendo n ≥3, para determinar cual es la moneda falsa y si pesa más o
menos que las auténticas. No se pide el algoritmo. (Solución: EDMA 11.20 Pág. 344)
b) Se puede dividir el conjunto inicial de monedas en 3 grupos iguales, dejando aparte 1 o las 2 que
puedan sobrar. Se pesan y comparan los 3 grupos (2 pesadas). Si la moneda falsa está en uno de los 3
grupos, su peso no coincidirá con el de los otros 2, por lo que descartamos los 2 grupos que pesan
igual quedándonos con el que pesa distinto y repitiendo el procedimiento. Si los 3 grupos pesan igual,
la moneda está entre la o las que se apartaron y con una pesada de una de ellas con una auténtica se
determina cuál es la falsa y si pesa más o menos. Por lo que harán falta O(2 log3(n)) pesadas.
Problema (4 puntos).
Se quiere realizar en un soporte secuencial (cinta de dos caras) una recopilación de canciones
preferidas. Se dispone de una lista de n canciones favoritas, junto con la duración individual de cada
una. Lamentablemente, la cinta de T minutos no tiene capacidad para contener todas las canciones, por
lo que se les ha asignado una puntuación (cuanto más favorita es, mayor es la puntuación). Se pretende
obtener la mejor cinta posible según la puntuación, teniendo en cuenta que las canciones deben caber
enteras y no es admisible que una canción se corte al final de una de las caras.
1. Elección razonada del esquema algorítmico mas eficiente para resolver el problema (0,5 puntos).
2. Estructuras de datos (0,5 puntos).
3. Algoritmo completo a partir del refinamiento del esquema general (2,5 puntos).
4. Estudio del coste del algoritmo desarrollado (0,5 punto).
Se considera en cada etapa una canción y se considera grabarla en cada una de las dos caras, si hay
espacio suficiente, así como la posibilidad de no grabarla en la cinta. La posibilidad de grabarla en la
segunda cara sólo se plantea cuando la ocupación de las dos caras es distinta. Una cota superior podría
ser la puntuación estimada considerando como espacio libre total la suma del espacio libre en cada
cara y grabar el máximo posible de canciones, aunque alguna quede cortada.
Además en un array tendremos las canciones con su puntuación y su duración. Por lo que la estructura
de cada nodo sería un registro con los siguientes campos:
Sol[1..n] de 0..2
K: 0..n
Puntuación_acumulada: real
Puntuación-estimada: real {prioridad}
Ocupada[1..2] de real
3.- Una descripción detallada de la solución puede encontrarse en el texto de Estructuras de datos y
métodos algorítmicos.
4.- Una estimación del coste es el tamaño del árbol de búsqueda, que en el peor caso crece como
O(n!), ya que cada nodo del nivel k puede expandirse con las n − k canciones que quedan por
considerar.
UNED
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
INGENIERÍA TÉCNICA DE SISTEMAS E INGENIERIA TÉCNICA DE GESTION
Cuestión 1 (2 puntos). Calcule el orden de complejidad del siguiente algoritmo en función del
tamaño del problema. Justifique la respuesta aplicando las ecuaciones de recurrencia que corresponda.
fun examen (V [1..n ] de elemento, c, f : nat ) dev existe : bool, may : elemento
si c = f entonces existe, may := cierto,V [ c ]
sino
m := ( c + f ) div2
existe1 , may1 := examen (V,c,m )
existe2 , may2 := examen (V,m+1, f )
existe := falso
si existe1 entonces {comprobamos el primer candidato}
existe, may := comprobar (V,may1 ,c, f ) ,may1
fsi
si ¬existe ∧ existe2 entonces {comprobamos el segundo candidato}
existe, may := comprobar (V,may2 ,c, f ) ,may2
fsi
fsi
ffun
Considere que el coste de la función comprueba es del orden de f-c, donde f y c son dos de sus
parámetros de llamada.
Cuestión 2 (2 puntos). ¿Existe un algoritmo voraz para el problema de devolver el cambio con el
menor número de monedas en cualquier sistema monetario?
Justifique su respuesta: si existe indique cual es la función de selección y demuestre su optimalidad. Si
no existe indique un contraejemplo para la función de selección que considere más plausible.
Cuestión 3 (2 puntos). Dado el siguiente grafo, rellene la tabla adjunta (usando todas las filas que
necesite) indicando paso a paso cómo el algoritmo de Dijkstra encuentra todos los caminos de menor
coste desde el nodo 1.
Problema (4 puntos). Un robot se mueve en un edificio en busca de un anillo que no sabe dónde está.
Se trata de implementar una función busca_tesoro que le ayude a encontrar el anillo y a salir después
del edificio. El edificio debe representarse como una matriz de entrada a la función cuyas casillas
contienen uno de los siguientes tres valores: A para “paso libre”, B para “paso estrecho” (no cabe el
robot) y C para “anillo”. El robot sale de la casilla (1,1) y debe encontrar la casilla ocupada por el
anillo. En cada punto, el robot puede tomar la dirección Norte, Sur, Este u Oeste siempre que no haya
una pared. La función busca_tesoro debe devolver la secuencia de casillas que componen el camino de
regreso desde la casilla ocupada por el anillo hasta la casilla (1,1). Suponga que la distancia entre
casillas adyacentes es siempre 1.
1. Elección razonada del esquema algorítmico mas eficiente para resolver el problema (0,5 puntos).
2. Estructuras de datos (0,5 puntos).
3. Algoritmo completo a partir del refinamiento del esquema general (2,5 puntos).
4. Estudio del coste del algoritmo desarrollado (0,5 punto).
RESPUESTAS EXAMEN Programación III. Septiembre 2009 (Reserva)
Cuestión 1 (2 puntos). Calcule el orden de complejidad del siguiente algoritmo en función del
tamaño del problema. Justifique la respuesta aplicando las ecuaciones de recurrencia que corresponda.
fun examen (V [1..n ] de elemento, c, f : nat ) dev existe : bool, may : elemento
si c = f entonces existe, may := cierto,V [ c ]
sino
m := ( c + f ) div2
existe1 , may1 := examen (V,c,m )
existe2 , may2 := examen (V,m+1, f )
existe := falso
si existe1 entonces {comprobamos el primer candidato}
existe, may := comprobar (V,may1 ,c, f ) ,may1
fsi
si ¬existe ∧ existe2 entonces {comprobamos el segundo candidato}
existe, may := comprobar (V,may2 ,c, f ) ,may2
fsi
fsi
ffun
Considere que el coste de la función comprueba es del orden de f-c, donde f y c son dos de sus
parámetros de llamada.
⎧⎪cn k , si 1 ≤ n < b
T (n) = ⎨
⎪⎩aT ( n / b ) + cn , si n ≥ b
k
⎧Θ ( n k ) , si a < b k
⎪
⎪
T ( n ) ∈ ⎨Θ ( n k log n ) , si a = b k
⎪
⎪⎩Θ n b( )
log a , si a > b k
T ( n ) ∈ Θ ( n log n )
Cuestión 2 (2 puntos). ¿Existe un algoritmo voraz para el problema de devolver el cambio con el
menor número de monedas en cualquier sistema monetario?
Justifique su respuesta: si existe indique cual es la función de selección y demuestre su optimalidad. Si
no existe indique un contraejemplo para la función de selección que considere más plausible.
(Respuesta: Fundamentos de Algoritmia, pag. 211 y Esquemas algorítmicos Pág. 12). Sep 2003 (Reserva)
No.
Para aplicar el esquema voraz, el problema ha de ser de optimización. Se debe distinguir un
conjunto de candidatos y que la solución pase por ir escogiéndolos o rechazándolos. La función de
selección debe asegurar que la solución alcanzada es la óptima. Si no, puede que hayamos
confundido el problema con uno de exploración de grafos. En el problema de las monedas, la
solución es óptima tomando el conjunto de monedas españolas (cuando se utilizaban pesetas, no euros
como ahora), es decir, C = {100, 25, 10 5 1}. Sin embargo, si tomamos las monedas inglesas en
peniques C = {30,24,12,6,3,1}, para un cambio de 48 peniques, nuestro algoritmo nos daría un
conjunto solución S = {30,12,6}, pero la solución óptima sería S = {24,24}.
Cuestión 3 (2 puntos). Dado el siguiente grafo, rellene la tabla adjunta (usando todas las filas que
necesite) indicando paso a paso cómo el algoritmo de Dijkstra encuentra todos los caminos de menor
coste desde el nodo 1.
Problema (4 puntos). Un robot se mueve en un edificio en busca de un anillo que no sabe dónde está.
Se trata de implementar una función busca_tesoro que le ayude a encontrar el anillo y a salir después
del edificio. El edificio debe representarse como una matriz de entrada a la función cuyas casillas
contienen uno de los siguientes tres valores: A para “paso libre”, B para “paso estrecho” (no cabe el
robot) y C para “anillo”. El robot sale de la casilla (1,1) y debe encontrar la casilla ocupada por el
anillo. En cada punto, el robot puede tomar la dirección Norte, Sur, Este u Oeste siempre que no haya
una pared. La función busca_tesoro debe devolver la secuencia de casillas que componen el camino de
regreso desde la casilla ocupada por el anillo hasta la casilla (1,1). Suponga que la distancia entre
casillas adyacentes es siempre 1. Solución: Febrero 2003 (Primera Semana)
Solución:
Como no se indica nada al respecto de la distancia entre casillas adyacentes, y ya que se sugiere
utilizar únicamente una matriz, es lícito suponer que la distancia entre casillas adyacentes es siempre
la misma (1, sin pérdida de generalidad). Por otra parte, no se exige hallar el camino más corto entre la
entrada y el minotauro, sino que el enunciado sugiere, en todo caso, que el algoritmo tarde lo menos
posible en dar una de las posibles soluciones (y ayudar a salir al robot cuanto antes).
Tras estas consideraciones previas ya es posible elegir el esquema algorítmico más adecuado. El
tablero puede verse como un grafo en el que los nodos son las casillas y en el que como máximo
surgen cuatro aristas (N, S, E, O). Todas las aristas tienen el mismo valor asociado (por ejemplo, 1).
En primer lugar, el algoritmo de Dijkstra queda descartado. No se pide el camino más corto y si se
hiciera, las particularidades del problema hacen que el camino más corto coincida con el camino de
menos nodos y, por tanto, una exploración en anchura tendrá un coste menor siempre que no se visiten
nodos ya explorados, como mucho se recorrerá todo el tablero una vez (coste lineal con respecto al
número de nodos versus coste cuadrático para Dijkstra).
En segundo lugar, es previsible esperar que el anillo no esté cerca de la entrada (estará en un nivel
profundo del árbol de búsqueda) por lo que los posibles caminos solución serán largos.
Como no es necesario encontrar el camino más corto, sino encontrar un camino lo antes posible, una
búsqueda en profundidad resulta más adecuada que una búsqueda en anchura. En el peor de los casos
en ambas habrá que recorrer todo el tablero una vez, pero ya que buscamos un nodo profundo, se
puede esperar que en media una búsqueda en profundidad requiera explorar menos nodos que una
búsqueda en anchura.
Si se supone que el edificio es infinito entonces una búsqueda en profundidad no sería adecuada
porque no garantiza que se pueda encontrar una solución. En este enunciado se puede presuponer que
el edificio es finito.
En tercer lugar, es posible que una casilla no tenga salida por lo que es necesario habilitar un
mecanismo de retroceso.
Por último, es necesario que no se exploren por segunda vez casillas ya exploradas anteriormente.
Por estos motivos, se ha elegido el esquema de vuelta atrás.
Vamos a utilizar el esquema de vuelta atrás modificado para que la búsqueda se detenga en la primera
solución y para que devuelva la secuencia de ensayos que han llevado a la solución en orden inverso
(es decir, la secuencia de casillas desde el anillo hasta la salida).
Para almacenar las casillas bastará un registro de dos enteros x e y. Vamos a utilizar una lista de
casillas para almacenar la solución y otra para las compleciones. Para llevar control de los nodos
visitados bastará una matriz de igual tamaño que el anillo pero de valores booleanos.
Será necesario implementar las funciones de lista:
• crear_lista
• vacia
• añadir
• primero
Suponemos “anillo” inicializado con la configuración del anillo y ”visitados” inicializado con todas
las posiciones a falso.
tipoCasilla = registro
x,y: entero;
fregistro
tipoLista
fun vuelta-atrás ( anillo: vector [1..LARGO, 1..ANCHO] de entero; casilla: tipoCasilla visitados:
vector [1..LARGO, 1..ANCHO] de booleano; ) dev (es_solución: booleano; solución:
tipoLista)
visitados [casilla.x, casilla.y] ← verdadero;
si anillo[casilla.x, casilla.y] == 2 entonces
solución ← crear_lista();
solución ← añadir (solución, casilla);
devolver (verdadero, solución);
si no
hijos ← crear_lista();
hijos ← compleciones (anillo, casilla)
es_solución ← falso;
mientras ¬ es_solución ∧ ¬ vacia (hijos)
hijo ← primero (hijos)
si ¬ visitados [hijo.x, hijo.y] entonces
(es_solución, solución) ← vuelta-atrás (anillo,hijo,visitados);
fsi
fmientras
si es_solución entonces
solución ← añadir (solución, casilla);
fsi
devolver (es_solución, casilla);
fsi
ffun
Todas las operaciones son constantes salvo la llamada recursiva a vuelta-atrás. En cada nivel, pueden
realizarse hasta 4 llamadas. Sin embargo, las llamadas no se realizan si la casilla ya ha sido visitada.
Esto quiere decir que, en el caso peor, sólo se visitará una vez cada casilla. Como las operaciones para
una casilla son de complejidad constante, la complejidad será O(ANCHO*LARGO), lineal con
respecto al número de casillas.
1. Elección razonada del esquema algorítmico mas eficiente para resolver el problema (0,5 puntos).
2. Estructuras de datos (0,5 puntos).
3. Algoritmo completo a partir del refinamiento del esquema general (2,5 puntos).
4. Estudio del coste del algoritmo desarrollado (0,5 punto).