Sunteți pe pagina 1din 7

EXAMEN DE ALGORÍTMICA.

Segundo Ingenierı́a Técnica en Informática de Gestión y de


Sistemas. Febrero 2004
1) Dadas tres matrices A, B y C de dimensiones n × n, cuyos valores son dı́gitos entre 0 y 9, se quiere
modificar la matriz C de manera que contenga en cada fila i la ”menor”de las filas i de las matrices A y
B, en caso de que no coincidan, y cuando estas filas coinciden dejar en la fila de C el valor original.
Se resuelve el problema con el siguiente esquema:
para i = 1, 2, . . . , n
menor(A[i],B[i],C[i])
finpara
donde:
menor(a,b,var c:array [1,...,n] de 0,...,9):
i=1
mientras i ≤ n Y a[i] = b[i]
i=i+1
finmientras
si i ≤ n
si a[i] < b[i]
copiar(a[i],c[i])
en otro caso
copiar(b[i],c[i])
finsi
finsi
donde la función copiar copia cada uno de los elementos del primer vector en las posiciones del segundo
vector.
a) (1 puntos) Obtener Ω, O y θ del tiempo de ejecución.
b) (0.5 puntos) Obtener θ del tiempo promedio.
c) (0.75 puntos) Obtener o (o-pequeña) del número promedio de instrucciones que se ejecutan,
suponiendo que en cada posición es equiprobable que aparezca uno de los dı́gitos del 0 al 9.
d) (0.75 puntos) ¿Qué se puede decir sobre los tiempos de ejecución si los elementos de las matrices
son cadenas de caracteres de longitud m en vez de ser dı́gitos, las comparaciones se hacen carácter a
carácter y las copias consisten en copiar referencias al principio de la cadena?
Solución:
a) La función copiar tiene un orden lineal en n, ya que se trata de copiar cada uno de los elementos
del vector origen.
P
El tiempo de ejecución tendrá la forma ni=1 tmenor (i), con tmenor el tiempo de la función menor.
En cada llamada a la función menor se puede llegar hasta el final del bucle mientras, y no se copia
nada de las matrices A y B en la C, con lo que el coste es lineal; o se sale del bucle sin llegar al final, con lo
que se realiza una copia de n elementos. De este modo sabemos que en la función menor, tanto el caso¡más ¢
P
favorable como el más desfavorable tiene tiempo lineal, y por tanto el tiempo es t(n) = ni=1 an ∈ θ n2 .
b) Dado que hemos obtenido el valor de θ independientemente de la forma de la entrada, este valor
es válido también para el tiempo promedio.
c) Para calcular el o del número de instrucciones consideramos que el interior del bucle para se
ejecuta n veces, y por cada pasada hay que contar el número promedio de instrucciones en la función
P
menor: tp (n) = n + 1 + ni=1 tpmenor (i). Como sabemos que el orden es n2 sólo nos interesa este término
en el sumatorio.
En menor se ejecuta la inicialización de la variable, el paso por el bucle, y la comparación, tras la
cual posiblemente se copia un vector: tpmenor = 1 + tmientras (n) + tsi .
Para calcular tmientras consideramos que se hace una comparación más que veces se entra, con lo que
la última comparación para no entrar al interior del bucle la consideramos aparte, y se entra una primera
1
vez si los dos primeros dı́gitos son iguales, lo que ocurre con probabilidad 10 , se entra una segunda vez
³ ´2
1
si los dos primeros son iguales y los dos segundos también (probabilidad 10 ). En general tenemos
³ ´2 ³ ´n 1
− 1
1 1 1 11
tmientras = 1 + 2 10 +2 10 + ... + 2 10 = 1 + 2 10 1−101n+1 < 9 . Por tanto, el paso por el bucle no
10
afecta al término de mayor orden del tiempo de ejecución, ni a o.
La función copiar se ejecuta si no coinciden las filas en las matrices A y B, y esto ocurre con
probabilidad 1 − 101n . Si en cada copia se ejecutan 2n instrucciones (paso por un ¡bucle¢ y actualización de
un elemento), el término de mayor orden en la función menor es 2n, y tp (n) ∈ o 2n2 .
d) La única diferencia con el caso anterior es el coste de las comparaciones, pues las asignaciones
siguen haciéndose en una única instrucción.
En el caso en que las cadenas sean distintas en el primer carácter tenemos una única operación por
comparación, y los costes coincidirı́an con los calculados.
En el caso más desfavorable de que las cadenas sean iguales hasta el último carácter los costes
cambian multiplicándose por m, salvo la o del promedio, pues se obtenı́a de la parte de copia de la
función menor, y el coste de la copia no ha cambiado.
Tiene más sentido considerar valores aleatorios en las cadenas, y por lo tanto que el tiempo de
ejecución de la comparación es el promedio. Para calcular el coste de las comparaciones en este caso se
sigue el mismo razonamiento que para la comparación de dos filas. En este caso el coste promedio de las
³ ´2 ³ ´m
1 1 1
comparaciones es 1 + 2 10 + 2 10 + . . . + 2 10 , que también está acotado por una constante, con lo
que los costes siguen siendo los mismos que cuando tenemos dı́gitos.
2) (4 puntos) En una celebración a la que asisten n invitados que se sentarán en m mesas, con n
múltiplo de m, se pretende hacer una asignación óptima de los invitados a las mesas. Para esto se dispone
de una tabla de ”amistad” a:array[1,...,n][1,...,n] de naturales, donde a[i, j] indica la amistad que siente
la persona i hacia la j. La tabla no es simétrica (la amistad no es recı́proca). El grado de amistad en
una mesa es la suma de los grados de amistad entre todos los sentados a ella. Se tratará de maximizar la
suma de todos los grados de amistad de las mesas.
a) (1 punto) Dar un esquema de un método de avance rápido para este problema.
b) (2 puntos) Se puede resolver por backtracking con un esquema no recursivo de optimización.
Indicar cómo serı́a el árbol de soluciones y cómo se representarı́an estas, qué variables se usan en la
solución por backtracking, y programar las funciones generar, retroceder, solucion, criterio (no es necesario
que esté optimizado) y mashermanos.
c) (1 punto) Para resolver el problema por Branch and Bound indicar alguna forma de calcular las
cotas inferior y superior y la estimación del beneficio en cada nodo, y cómo el método de avance rápido
se puede utilizar en combinación con el Branch and Bound.
Solución:
a) El problema consiste en asignar los n invitados a las m mesas, por lo que la solución se almacena
en un vector s:array[1,...,n] de 0,...,m, que se inicializa a cero para indicar que no se ha asignado mesa
a ningún invitado. Podemos empezar completando mesa a mesa, asignando un invitado aleatoriamente
a la primera mesa, después sentando a esa mesa al invitado con el que se maximiza la amistad con el
ya asignado, a continuación eligiendo al que maximiza la amistad con los dos asignados, etc. Cuando
se acaba de asignar invitados a una mesa se pasa a completar del mismo modo otra, etc. Además de s
utilizamos una tabla de asignaciones t=array[1,...,m][1,...,n/m] de 0,...,n, que indicará en t[i, j] cuál es el
j-ésimo invitado en la mesa i. Un esquema será:
//Para cada mesa
para mesa = 1, . . . , m
invitado = 1
mientras s[invitado] 6= 0
invitado = invitado + 1
finmientras
//Elige el primero que encuentra sin asignar
s[invitado] = mesa
t[mesa, 1] = invitado
pos = 2
maximo = −1
para i = 1, . . . , n
//Para cada no asignado
si s[i] = 0
suma = 0
para j = 1, . . . , pos − 1
suma = suma + a[t[mesa][j]][i] + a[i][t[mesa][j]]
finpara
//Calcula su amistad con todos los ya asignados a la mesa
si suma > maximo
maximo = suma
invitado = i
finsi
finsi
finpara
//Y asigna a la mesa al que maximiza la amistad
s[invitado] = mesa
t[mesa, pos] = invitado
pos = pos + 1
finpara
b) El árbol puede tener n niveles más el raı́z. En el nivel i se decidirá la mesa a la que se asigna el
invitado i. Por tanto, cada nodo no terminal tendrá m hijos, que corresponden a las m mesas donde se
puede asignar el invitado. La solución se almacena en un array s, como el del apartado a).
Al ser un problema de optimización llevaremos una solución óptima actual (SOA), que es un array
de las mismas dimensiones que s, que estará inicializado a 0, y que al final contendrá la solución óptima.
En VOA guardamos la suma de los grados de amistad de las distintas mesas correspondiente a la solución
almacenada en SOA. Está inicializado a 0.
La variable nivel indica el nivel del árbol por el que vamos. Cuando vuelva a valer 0 es porque se ha
recorrido todo el árbol y se acaba la ejecución.
También usamos una tabla t como la del apartado a), pues es necesario guardar la asignación que se
está haciendo, para comparar su valor con el de la solución óptima que se lleve.
n
Además, utilizamos un array pm:array[1,...,m] de 0,..., m , para indicar las posiciones ocupadas en
cada mesa. Estará inicializado a 0, y es necesario porque no se asignan todos los invitados a una mesa
antes de empezar con la siguiente, al contrario de lo que se hacı́a en el apartado a).
El esquema del algoritmo puede ser:
inicializar variables
nivel = 1
generar(s,t,pm,nivel)
repetir
si solucion(s,pm,nivel)
ga=gradoamistad(t)
si ga >VOA
VOA=ga
SOA← s
finsi
finsi
si criterio(s,pm,nivel)
generar(s,t,pm,nivel)
en otro caso
mientras nivel 6= 0 Y NO mashermanos(s,nivel)
retroceder(s,pm,nivel)
finmientras
si nivel 6= 0
generar(s,t,pm,nivel)
finsi
finsi
hasta nivel = 0
Un nodo es solución si es terminal y el último invitado que se ha asignado completa la última mesa
que quedaba:
solucion(s,pm,nivel):
n
devolver nivel = n Y pm[s[nivel]] = m
Se puede seguir por un nodo si no es terminal y si no hemos asignado invitados de más a una mesa:
criterio(s,pm,nivel):
n
devolver nivel 6= n Y pm[s[nivel]] ≤ m
Se podrı́a intentar incluir algún criterio para desechar nodos desde los que no se puede mejorar el valor
VOA. Esto se puede hacer calculando los grados de amistad que se lleva en cada mesa (como se hace
en el apartado a)), y completándolos con los valores máximos de amistad que se pueden poner, y si el
valor que da es menor que VOA no seguir. Dado que no nos piden una función criterio optimizada no la
programaremos.
Un nodo tiene más hermanos si no corresponde a intentar asignar a la última mesa:
mashermanos(s,nivel):
devolver s[nivel] < m
Al generar un nodo se asigna a un invitado una mesa, se apunta un ocupante más en la mesa, y
en t se pone la posición que ocupa el invitado en la mesa. Si no es el primer hijo de un nodo ya se
habı́a intentado asignar al invitado una mesa, por lo que hay que deshacer esa asignación restando uno
al número de ocupantes de la mesa antes de volver a asignarle la siguiente.
generar(s,t,pm,nivel):
si s[nivel] 6= 0
pm[s[nivel]] − −
finsi
s[nivel] + +
pm[s[nivel]]++
t[s[nivel]][pm[s[nivel]]] = nivel
Al retroceder se desasigna el invitado a la mesa en la que estaba antes de deshacer la asignación y
retroceder en el árbol.
retroceder(s,pm,nivel):
pm[s[nivel]] − −
s[nivel]] − −
nivel − −
No es necesario deshacer las asignaciones en t pues quedan invalidadas al contabilizar en pm un invitado
menos en la mesa.
c) El árbol coincidirá con el del backtracking del apartado b), aunque se recorre en otro orden.
Se puede utilizar la misma tabla t del apartado anterior, y a partir de ella calcular la cota inferior en
un nodo de nivel nivel sumando los grados de amistad de todas las mesas, con los invitados ya asignados
a la mesa:
P Ppm[i] Pj
CI(nodo)= m i=1 j=1 k=1 (a[t[i][j]][t[i][k]] + a[t[i][k]][t[i][j]])
También se puede usar un avance rápido para calcular una solución a partir de un nodo. De esta
forma la solución óptima a partir del nodo tiene valor mayor o igual a la obtenida con avance rápido,
por lo que este sirve como cota inferior. Habrı́a que plantear un problema de avance rápido pero con los
huecos que quedan en las mesas, las asignaciones ya realizadas, y sumando el valor calculado al que ya
tenemos en CI(nodo).
Para la cota superior podemos considerar que todas las asignaciones que quedan por hacer corres-
ponden al mayor grado de amistad de la tabla. Para esto se calcula el máximo de la tabla al principio
(coste n2 ), y será:
P Pn P j
CS(nodo)=CI(nodo)+ m i=1
m
j=pm[i]+1 k=1 2 ∗ maximo
Se puede mejorar (reducir la cota superior) calculando el máximo de cada fila y columna (lo que
también se hace con coste n2 ), con lo que tendrı́amos la suma de tres términos: los grados de amistad
entre ya asignados (que es el primer valor de la CI que hemos dado); otra parte de ya asignados con otros
invitados que no sabemos los que son, por lo que se toma el máximo de la fila y columna para el invitado
ya asignado; y por último las amistades entre invitados todavı́a no asignados, para los que se utiliza el
máximo de toda la tabla: ¡n ¢ Pm ¡n ¢
P Ppm[i]
CI(nodo)+ m i=1 j=1 (maxf il[j] + maxcol[j]) m − pm[i] + i=1 2 ∗ maximo m − pm[i] − 1
La estimación del beneficio puede ser cualquiera de las dos cotas o la media, pero, ya que tenemos
un método de avance rápido, puede usarse este para una estimación más próxima al óptimo.
3) (2 puntos) Se dispone de un dispositivo hardware con el que se puede evaluar eficientemente el
contenido de una matriz cuadrada de datos de tamaño menor o igual que b × b que representan una
imagen. En la evaluación se pueden utilizar varias funciones; una de ellas es maxcadena, con la que se
obtiene la zona contigua en vertical u horizontal de datos iguales (un vector fila o columna). La cabecera
de maxcadena es:
maxcadena(a:array[1,...,n][1,...,n] de datos;var x,y,z,t:enteros)
donde a es la matriz de datos, n representa la dimensión de la matriz, y la zona más grande de datos
contiguos tiene extremos en las posiciones (x,y) y (z,t) del array.
Programar por divide y vencerás la solución del mismo problema para matrices cuadradas de datos
con dimensiones mayores que b × b, basándonos en la función que se puede ejecutar eficientemente para
problemas de tamaño menor que b × b.
Solución:
Dividiremos cada problema en cuatro subproblemas de dimensiones n2 × n2 . Puede que durante la
ejecución el tamaño con que se trabaja no sea par, y que las submatrices no sean cuadradas. Por no com-
plicar la solución consideraremos que las submatrices siguen siendo cuadradas. Esto se puede conseguir,
por ejemplo, completando la matriz de tamaño n hasta otra de tamaño 2k b, con k el menor valor tal que
n ≤ 2k b. Los valores que se ponen en la zona ampliada deben ser distintos entre sı́ y también de los que
tienen adyacentes en la matriz a original.
Los problemas se dividirán recursivamente hasta llegar a problemas de dimensión menor o igual que
b × b.
En la combinación compararemos las cuatro subsoluciones óptimas quedándonos con la mejor, pero,
dado que una cadena máxima puede quedar cortada en horizontal o vertical, habrá que evaluar también
las fronteras.
El esquema será:
DV(a:array[1,...,n][1,...,n] de datos;n:entero;var x,y,z,t:entero)
si n ≤ base
maxcadena(a,x,y,z,t)
en otro caso
DV(a, n2 ,x11,y11,z11,t11)
DV(&a[1][n/2 + 1], n2 ,x12,y12,z12,t12)
DV(&a[n/2 + 1][1], n2 ,x21,y21,z21,t21)
DV(&a[n/2 + 1][n/2 + 1], n2 ,x22,y22,z22,t22)
combinar(a,x11,y11,z11,t11,x12,y12,z12,t12,x21,y21,z21,t21,x22,y22,z22,t22,x,y,z,t)
finsi
Donde combinar tiene la forma:
combinar(a,x11,y11,z11,t11,x12,y12,z12,t12,x21,y21,z21,t21,x22,y22,z22,t22;var x,y,z,t)
x = x11
y = y11
z = z11
t = t11
si z − x + t − y < z12 − x12 + t12 − y12
x = x12
y = y12
z = z12
t = t12
finsi
si z − x + t − y < z21 − x21 + t21 − y21
x = x21
y = y21
z = z21
t = t21
finsi
si z − x + t − y < z22 − x22 + t22 − y22
x = x22
y = y22
z = z22
t = t22
finsi
//Se comprueba la frontera en cada fila
para i = 1, 2, . . . , n
si a[i][n/2] = a[i][n/2 + 1]
j = n/2 − 1
mientras j > 0 Y a[i][j] = a[i][n/2]
j =j−1
finmientras
k = n/2 + 2
mientras k < n + 1 Y a[i][k] = a[i][n/2]
k =k+1
finmientras
si z − x + t − y < k − j − 1
x=i
y =j+1
z=i
t=k−1
finsi
finsi
finpara
//Y en cada columna
para j = 1, 2, . . . , n
si a[n/2][j] = a[n/2 + 1][j]
i = n/2 − 1
mientras i > 0 Y a[i][j] = a[n/2][j]
i=i−1
finmientras
k = n/2 + 2
mientras k < n + 1 Y a[k][j] = a[n/2][j]
k =k+1
finmientras
si z − x + t − y < k − i − 1
x=i+1
y=j
z =k−1
t=j
finsi
finsi
finpara
4) (1 punto) Dada una cadena, c:array[1,...,n] de caracteres, se quiere contar el número de subcadenas
contiguas ascendentes, c[i] < c[i+1] < . . . < c[j]. En la cadena bacbd tenemos las subcadenas ascendentes:
b, a, ac, c, b, bd, d. Aunque este problema se puede resolver fácilmente en tiempo lineal, queremos
resolverlo con un esquema de programación dinámica, para lo que hay que obtener la fórmula de recursión,
los valores de los casos base, e indicar las tablas que se usan y cómo se obtiene la solución, que es el
número de subcadenas ascendentes, sin necesidad de obtener las cadenas.
Solución:
Podemos ir obteniendo el número de subcadenas ascendentes desde la primera posición de la cadena
hasta llegar a la última, con lo que ponemos el problema hasta la casilla i en función de los problemas
hasta casillas anteriores a la i:
N (i) = 1 + N (i − 1) ∗ (c[i − 1] < c[i])
con lo que contamos siempre la subcadena de un único carácter y contamos tantas subcadenas como hay
hasta la posición anterior si el nuevo carácter es posterior, en orden alfabético, al anterior.
Como caso base tendremos N (0) = 0, para poder calcular N (1) con la misma fórmula. También se
puede tomar N (1) = 1 como caso base y formar la tabla a partir de la posición 2.
Sólo es necesario un array para guardar los números de subcadenas, pues no nos piden que digamos
cuáles son. Y el array es unidimensional. Al acabar de completar el array se obtiene la solución sumando
los valores que hay en él.
Con el ejemplo el array quedará:

b a c b d
1 1 2 1 2
y el número de subcadenas es 7.

S-ar putea să vă placă și