PROBLEMA 7 (PROGRAMACIÓN DINÁMICA) Para el alineamiento global de secuencias de ADN y de proteínas (emparejamiento de símbolos de las secuencias) se usan algoritmos, basados en Programación Dinámica, que producen el alineamiento óptimo de dos secuencias. Supóngase que en la búsqueda de dicho alineamiento se utiliza una función que pondera positivamente la coincidencia de dos elementos semejantes en las secuencias y negativamente la aparición de huecos y la discordancia de símbolos en el emparejamiento. Por ejemplo, el siguiente alineamiento para las secuencias “ASILO” y “SELLOS” puntuaría positivamente los emparejamientos que aparecen encuadrados y negativamente los demás: A S - I L O - - S E L L O S Para dos secuencias A = a1a2…an, B = b1b2…bm, la ecuación general de la puntuación Sij correspondiente al alineamiento óptimo de las subsecuencias a1a2…ai y b1b2…bj será: ⎧ Si −1, j −1 + C (ai , b j ) ⎪ Sij = max ⎨ Si −1, j − 1 ⎪ Si , j −1 − 1 ⎩ ⎧+1 si x = y con C ( x, y ) = ⎨ ⎩−1 si x ≠ y Nótese que: - la coincidencia de dos elementos se pondera con +1 y la discordancia como –1 - Un salto en la secuencia se pondera con –1 (dos últimos casos de Sij) Se pide: a) Completar la ecuación en recurrencias para todos los casos, explicando su significado (la expresión anterior sólo es válida en el caso general, y no para los casos base). b) Obtener un algoritmo para el cálculo de Sij que permita obtener la puntuación correspondiente al alineamiento óptimo de dos secuencias dadas, A y B, de longitudes N y M respectivamente. c) Razonar cómo se obtendría uno de los posibles alineamientos óptimos entre las dos secuencias A y B, y utilizando en el apartado b) las estructuras de datos que serían necesarias para ello. Ejemplo: Alineamiento óptimo de las secuencias “ASILO” y “SELLOS” O S S E L L 0 -1 -2 -3 -4 -5 -6 A -1 -1 -2 -3 -4 -5 -6 S -2 0 -1 -2 -3 -4 -4 I -3 -1 -1 -2 -3 -4 -5 L -4 -2 -2 +0 -1 -2 -3 O -5 -3 -3 -1 -1 0 -1 A S - I L O - - S E L L O S Solución a) Ecuación en recurrencias Sea S[i,j] la puntuación del alineamiento óptimo de las secuencias A[1..i] y B[1..j] (0<=i<=N, 0<=j<=M): ⎧max( S [i − 1, j − 1] + C ( A[i ], B[ j ]), S [i − 1, j ] − 1, S [i, j − 1] − 1) , si i > 0 ∧ j > 0 S [i, j ] = ⎨ , si i = 0 ∨ j = 0 ⎩− max(i, j ) b) Obtención de matrices: tipo Direccion = Entero // 1=diagonal, 2=arriba, 3=izquierda func max3(x, y, z: Entero) dev (indice, alg si x >= y: si x >= z: < indice, valor > := < 1, x |otras: < indice, valor > := < 3, z fsi |otras: si y >= z: < indice, valor > := < 2, y |otras: < indice, valor > := < 3, z fsi fsi fin valor: Entero) > > > > func matricesAlineamiento(A: array[1..N] de Simbolo, B: array[1..M] de Simbolo) dev (S: array[0..N,0..M] de Entero, D: array[0..N,0..M] de Direccion) var i, j: Entero alg desde j := 0 hasta M < S[0,j], D[0,j] > := < j, 3 > // izquierda fdesde desde i := 1 hasta N < S[i,0], D[i,0] > := < i, 2 > // arriba desde j := 1 hasta M si A[i] = B[j]: < S[i,j], D[i,j] > := max3(S[i-1,j-1]+1,S[i-1,j]-1,S[i,j-1]-1) |otras: < S[i,j], D[i,j] > := max3(S[i-1,j-1]-1,S[i-1,j]-1,S[i,j-1]-1) fsi fdesde fdesde fin func puntAlineamientoOpt(A: array[1..N] de Simbolo, B: array[1..M] de Simbolo) dev (punt: Entero) var S: array[0..N, 0..M] de Entero, D: array[0..N, 0..M] de Direccion alg < S, D > := matricesAlineamiento(A, B) punt := S[N, M] fin c) Obtención del alineamiento óptimo de las secuencias A y B: Una vez obtenidas las matrices S y D: • S[i,j] es la puntuación del alineamiento óptimo de las subsecuencias A[1..i] y B[1..j] • D[i,j] contiene el código de la decisión óptima utilizada para S[i,j]: o 1=diagonal: la posición i de A se alinea con la posición j de B o 2=arriba: la posición i de A se alinea con un hueco en B, es decir, se produce un desplazamiento en B o 3=izquierda: la posición j de B se alinea con un hueco en A, es decir, se produce un desplazamiento en A El proceso para obtener el alineamiento óptimo comienza por la parte final de las secuencias, lo que corresponde con la posición (N, M) de las matrices, hasta llegar a la posición (0, 0), correspondiente a las subsecuencias vacías. En cada paso, según el valor de D[i, j] se actualizan los valores de i y j: • Si D[i, j] = 1 (diagonal): tanto i como j decrecen una unidad (no se genera hueco en ninguna de las secuencias) • Si D[i, j] = 2 (arriba): i decrece una unidad y j se mantiene igual, lo que equivale a que se genere un hueco para la secuencia B • Si D[i, j] = 3 (izquierda): j decrece una unidad e i se mantiene igual, lo que equivale a que se genere un hueco para la secuencia A func alineamientoOpt(A: array[1..N] de Simbolo, B: array[1..M] de Simbolo) dev (AA, BB: Lista de Simbolo) var S: array[0..N, 0..M] de Entero D: array[0..N, 0..M] de Direccion i, j: Entero alg < S, D > := matricesAlineamiento(A, B) < i, j > := < N, M > < AA, BB > := < listaVacia, listaVacia > mientras i > 0 Y j > 0 si D[i, j] = diagonal: AA := A[i] + AA BB := B[j] + BB < i, j > := < i - 1, j - 1 > | D[i, j] = arriba: AA := A[i] + AA BB := ‘-‘ + BB // hueco en B i := i - 1 | otras: AA := ‘-‘ + AA // hueco en A BB := B[j] + BB j := j - 1 fsi fmientras mientras i > 0 AA := A[i] + AA BB := ‘-‘ + BB // hueco en B i := i - 1 fmientras mientras j > 0 AA := ‘-‘ + AA BB := B[j] + BB // hueco en B j := j - 1 fmientras fin PROBLEMA 8 (PROGRAMACIÓN DINÁMICA) La circulación viaria de una ciudad viene representada por un grafo dirigido en el que los vértices se corresponden con intersecciones de calles y las aristas con las propias vías de tráfico, de manera que se dispone de la anchura, en número de carriles, de cada vía. Así, A[i, j] representa el número de carrilles de la vía que une la intersección i con la intersección j, en el sentido “desde i a j” (0 si no hay una vía que una directamente a las dos intersecciones en el sentido indicado). Se define la anchura de un trayecto entre dos intersecciones a la correspondiente al tramo de menor anchura. Aplicar la técnica de Programación Dinámica para: (a) implementar un algoritmo que obtenga la anchura correspondiente al trayecto de mayor anchura entre cada par de intersecciones. (b) implementar un algoritmo que obtenga el trayecto de mayor anchura desde una intersección origen hasta otra destino. Solución (a) Sea Amax[i, j, k] la anchura del trayecto de anchura máxima que va desde i hasta j pudiendo pasar por los nodos intermedios 1..k: Amax[i, j, 0] = A[i, j] Amax[i, j, k] = max(Amax[i, j, k – 1], min(Amax[i, k, k - 1], Amax[k, j, k - 1]), i ≠ k, j ≠ k Amax[k, j, k] = A[k, j, k – 1] Amax[i, k, k] = A[i, k, k – 1] La estructura es la misma que la del algoritmo de Floyd. Podemos pues utilizar una matriz Amax[1..N, 1..N] para almacenar los resultados para las etapas sucesivas k = 0, 1, …, N, ya que la fila k y la columna k no cambian en la etapa k. Sea P[i, j] un nodo intermedio del trayecto de anchura máxima que va desde i hasta j (cero si no existen nodos intermedios). func carriles(A:array [1..N, 1..N] de Entero) dev (Amax, P: array[1..N, 1..N] de Entero) var i, j, k: Entero alg < Amax, P > := < A, array de ceros > desde k := 1 hasta N desde i := 1 hasta N desde j := 1 hasta N si i ≠ k Y j ≠ k: si min(Amax[i, k], Amax[k, j]) > Amax[i, j]: Amax[i, j] := min(Amax[i, k], Amax[k, j]) P[i, j] := k fsi fsi fdesde fdesde fdesde fin (b) proc imprime-camino(i,j: Entero; Amax, P: array[1..N, 1..N] de Entero) alg si Amax[i, j] > 0: imprimir(“Camino de “+ i + “ a “ + j + “: “) imprimir(i) imprime-camino-aux(i, j, P) imprimir(j) | otras: imprimir(“No hay camino desde “ + i + “ a “ + j) fsi fin proc imprime-camino-aux(i, j:Entero; P: array[1..N, 1..N] de Entero) var k: Entero alg k := P[i, j] si k > 0: imprime-camino-aux(i, k, P) imprimir(k) imprime-camino-aux(k, j, P) fsi fin PROBLEMA 9 (BACTRACKING) Dada una fórmula lógica, formada por una serie de variables (lógicas) y los operadores de conjunción (∧), disyunción (∨) y negación (¬), se desea saber si se puede satisfacer, es decir, se puede establecer una asignación a cada variable de manera que la fórmula se evalúe a cierto. Se dispone de los tipos: Variable: Entero // sirve para identificar o representar a una variable lógica Variables: array [] de Variable // un conjunto de variables lógicas Formula: (descripción oculta) // representa una fórmula lógica Asimismo, se dipone de las funciones: func esCierto(f: Formula) dev (b: Logico) func esFalso(f: Formula) dev (b: Logico) // indica si f es la fórmula lógica cierto // indica si f es la fórmula lógica falso func varsFormula(f: Formula) dev (vs: Variables) // devuelve las variables // que aparecen en f func asigna(f: Formula; v: Variable; b: Logico) dev (f1: Formula) // devuelve la Formula equivalente al asignar el valor b (cierto o falso) // a la variable v en la fórmula f; no necesariamente todas las variables // de f aparecen en f1 Ejemplos: asigna(((¬x1∨x2∨x3)∧(x1∨x4)∧(x1∨¬x4)), x1, cierto) Æ (x2∨x3) asigna(((¬x1∨x2∨x3)∧(x1∨x4)∧(x1∨¬x4)), x1, falso) Æ (x4∧¬x4) Mediante la técnica de Backtracking, implementar la función SAT(f: Formula) dev (b: Logico) que devuelva cierto si f se puede satisfacer y falso en caso contrario. Solución Clase EsquemaBtUna func btUna(x: Etapa) dev (exito: Logico) var xsig: Etapa cand: Candidatos alg si esSolucion(x): exito := cierto | otras: exito := falso cand := calculaCandidatos(x) mientras NO exito Y quedanCandidatos(cand) xsig = seleccionaCandidato(cand, x) si esPrometedor(cand, x, xsig): anotaSolucion(cand, x, xsig) exito := btUna(xsig) si NO exito: cancelaAnotacion(cand, x, xsig) fsi fsi fmientras fsi fin … fClase Clase SATBT extiende a EsquemaBtUna // No hace falta la clase Solucion Clase Etapa f: Formula // la fórmula a considerar fClase Clase Candidatos vLog: array [1..2] de Logico := {cierto, falso} i: Entero // para recorrer el array fClase func SAT(f: Formula) dev (b: Logico) var x: Etapa alg x.f := f b := btUna(x) fin func esSolucion(x: Etapa) dev (b: Logico) alg b := esCierto(x.f) fin func calculaCandidatos(x: Etapa) dev (cand: Candidatos) alg cand.i := 0 fin func quedanCandidatos(cand: Candidatos) dev (b: Logico) alg b := (cand.i < 2) fin func seleccionaCandidato(cand: Candidatos, x: Etapa) dev (xsig : Etapa) var varsNoInstanciadas: Variables alg cand.i := cand.i + 1 varsNoInstanciadas := varsFormula(x.f) // seguro que el array no está vacío xsig.f := asigna(x.f, varsNoInstanciadas[1], Candidatos.vLog[cand.i]) fin func esPrometedor(cand: Candidatos, x, xsig: Etapa) dev (b: Logico) alg b := (esFalso(xsig.f) ≠ falso) fin proc anotaSolucion(cand: Candidatos, x, xsig: Etapa) alg fin proc cancelaAnotacion(cand: Candidatos, xsig: Etapa) alg fin PROBLEMA 10 (BACTRACKING) Se tiene una cantidad de dinero C distribuida en diferentes billetes y monedas, con la que se necesita realizar una serie de pagos. Para realizar cada pago se usarán los billetes y monedas disponibles, teniendo en cuenta que quien recibe el pago verá satisfecha al menos la cantidad que se le debía, sin estar obligado a devolver cambio. Nota: Se deben usar las siguientes variables cantidadDisponible: Entero // cantidad en céntimos de euro monedasDisponibles: array[1..15] de Entero // número de monedas o // billetes de cada tipo valoresMonedas: array[1..15] := {50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1} facturas: array[1..N] de Entero // las N cantidades a pagar pagos: array[1..N, 1..15] de Entero // pagos[i, j] es el número // de monedas o billetes usados del tipo j para pagar la factura i Diseñar un algoritmo usando Backtracking que indique para cada pago la forma de realizarlo, de manera que se minimice la cantidad total que se paga de más. Solución Clase EsquemaBtOptimo proc btOptimo(x: Etapa) var xsig: Etapa cand: Candidatos alg si esSolucion(x): si esMejor(): actualizaSolucion() fsi fsi cand := calculaCandidatos(x) mientras quedanCandidatos(cand) xsig = seleccionaCandidato(cand, x) si esPrometedor(cand, x, xsig): anotaSolucion(cand, x, xsig) btOptimo(xsig) cancelaAnotacion(cand, x, xsig) fsi fmientras fin … fClase Clase PagosBT hereda de EsquemaBTOptimo cantidadDisponible: Entero // en céntimos de euro monedasDisponibles: array [1..15] de Entero valoresMonedas: array [1..15] de Entero := {50000, 20000, 10000, …, 5, 2, 1} N: Entero // número de facturas a pagar facturas: array [1..N] de Entero // cantidades a pagar sol, solOpt: Solucion xini: Etapa Clase Solucion pagos: array [1..N, 1..15] de Entero // pagos[i, j]: número de monedas // billetes del tipo j en el pago de la factura i cantDisp: Entero // cantidad disponible aún monDisp: array [1..15] de Entero // número de monedas disponibles aún fClase Clase Etapa i: Entero // próxima factura a considerar j: Entero // próximo tipo de moneda o billete a considerar pagadoFactura: Entero // cantidad ya pagada en la factura i fClase Clase Candidatos nMonedas: Entero // número de monedas a usar (desde 0 hasta nMaxMonedas) nMaxMonedas: Entero // número máximo de monedas a usar fClase proc resuelvePagos() alg < xini.i, xini.j, xini.pagadoFactura > := < 1, 1, 0 > sol.pagos := < array de ceros > sol.cantDisp := cantidadDisponible sol.monDisp := monedasDisponibles solOpt.cantDisp := -1 // los otros atributos son irrelevantes al inicio btOptimo(xini) si solOpt.cantDisp >= 0: // hay solución < imprimir solOpt > | otras: < imprimir “No hay solución” > fsi fin func esSolucion(x: Etapa) dev (b: Logico) alg b := (x.i > N) fin func esMejor() dev (b: Logico) alg b:= (sol.cantDisp > solOpt.cantDisp) fin proc actualizaSolucion() alg solOpt := < copia sol > fin func calculaCandidatos(x: Etapa) dev (cand: Candidatos) alg si x.j = 15: // el número de monedas necesarias para pagar la factura i cand.nMaxMonedas := ⎡(facturas[x.i] - x.pagadoFactura) / valoresMonedas[x.j])⎤ si cand.nMaxMonedas > sol.monDisp[x.j]: // no se puede pagar cand.nMonedas := cand.nMaxMonedas // no hay candidatos | otras: cand.nMonedas := cand.nMaxMonedas – 1 // se termina de pagar // con nMaxMonedas monedas fsi | otras: cand.nMonedas := -1; cand.nMaxMonedas := min(sol.monDisp[x.j], ⎡(facturas[x.i] – x.pagadoFactura) / valoresMonedas[x.j])⎤) fsi fin func quedanCandidatos(cand: Candidatos) dev (b: Logico) alg b := (cand.nMonedas < cand.nMaxMonedas) fin func seleccionaCandidato(cand: Candidatos; x: Etapa) dev (xsig: Etapa) alg cand.nMonedas := cand.nMonedas + 1 si x.pagadoFactura + cand.nMonedas * valoresMonedas[x.j] >= facturas[x.i]: < xsig.i, xsig.j, xsig.pagadoFactura > := < x.i + 1, 1, 0 > | otras: < xsig.i, xsig.j > := < x.i, x.j + 1 > xsig.pagadoFactura := x.pagadoFactura + cand.nMonedas * valoresMonedas[x.j] fsi fin func esPrometedor(cand: Candidatos; x, xsig: Etapa) dev (b: Logico) alg b := cierto // en calculaCandidatos se determina si el pago no se puede hacer fin proc anotaSolucion(cand: Candidatos; x, xsig: Etapa) alg sol.pagos[x.i, x.j] := cand.nMonedas sol.cantDisp := sol.cantDisp – cand.nMonedas * valoresMonedas[x.j] sol.monDisp[x.j] := sol.monDisp[x.j] – cand.nMonedas fin proc cancelaAnotacion(cand: Candidatos, xsig: Etapa) alg sol.pagos[x.i, x.j] := 0 sol.cantDisp := sol.cantDisp + cand.nMonedas * valoresMonedas[x.j] sol.monDisp[x.j] := sol.monDisp[x.j] + cand.nMonedas fin fClase PROBLEMA 11 (RAMIFICACIÓN Y ACOTACIÓN) Se necesita realizar N tareas independientes en una máquina multiprocesador, con M procesadores pudiendo trabajar en paralelo (supóngase N > M). Siendo ti el tiempo de ejecución de la i-ésima tarea en cualquier procesador, el problema consiste en determinar en qué procesador hay que ejecutar cada uno de los trabajos, de forma que el tiempo final de la ejecución de todos los trabajos (tiempo de ejecución del procesador más cargado) sea mínimo. Supóngase que no hay restricciones acerca de cuándo puede comenzar la ejecución de cada trabajo. Implementar un algoritmo de ramificación y acotación que resuelva el problema teniendo en cuenta: (a) Para la solución inicial suponer que se usa el algoritmo voraz correspondiente al ejercicio 4 del boletín de algoritmos voraces (no resolver). (b) En la expansión de nodos deben evitarse soluciones equivalentes, como las que surgirían al considerarse varios procesadores con el mismo tiempo final de ejecución de las tareas anteriores. Así pues, en el caso señalado sólo se considerará uno de los procesadores. (c) Considerar como función de cota el tiempo acumulado del procesador más cargado. (d) Explicar las modificaciones que habría que realizar si se usa como función de cota una que considere, aparte del tiempo total acumulado, que el tiempo total de ejecución de las tareas que quedan por asignar se distribuye uniformemente entre todos los procesadores, rellenando los huecos existentes (ver figura). De esta manera, cualquier solución que se obtenga a partir del nodo en cuestión no podría nunca mejorar la que idealmente se está obteniendo mediante esta estimación. cota inferior cota inferior Las barras blancas se corresponden con tareas ya asignadas, y la zona sombreada se corresponde con el tiempo total de las tareas que faltan por asignar Solución Clase EsquemaRyA fClase proc RyA() var ed: EstructuraDatos n: Nodo hijos: array[] de Nodo numHijos, i: Entero alg ed := crearEstructuraDatos() calcularSolucionInicial() n := calcularNodoInicial() mientras NO fin(n, ed) hijos := expande(n) numHijos := tamaño(hijos) desde i := 1 hasta numHijos si esMejorCota(hijos[i]): si esSolucion(hijos[i]) actualizaSolucion(hijos[i]) eliminaNodos(ed) |otras ed.añadir(hijos[i]) fsi fsi fdesde si NO ed.esVacia(): n := ed.obtener() |otras n := NULO fsi fmientras fin ... Clase TareasRyA hereda de EsquemaRyA t: array [1..N] de Numero // duración de cada tarea solOpt: Solucion Clase Solucion asigTarea: array [1..N] de Entero // procesador asignado a cada tarea tFinal: Numero // tiempo en que han acabado todas las tareas fClase Clase Nodo sol: Solucion // si es parcial, se refiere a las tareas asignadas nivel: Entero // número de tareas asignadas cotaInf: Numero // estimación de la mejor solución a partir del nodo fFinProc: array [1..M] de Numero // tiempo acumulado en cada procesador totalHuecos: Numero // se usa para el apartado (d) fClase proc asignacion(tiemposTareas: array [1..N] de Numero) alg t := < copiar tiemposTareas > RyA() < imprimir solOpt > fin func crearEstructuraDatos() dev (ed: EstructuraDatos) alg ed := < crear Monticulo > // de mínimos, según cotaInf fin proc calcularSolucionInicial() var aux: Entero alg solOpt = < llamada al algoritmo voraz > fin func calcularNodoInicial() dev (n: Nodo) alg n := < crear Nodo > n.sol.asigTarea := < crear array de ceros > n.sol.tFinal := 0 n.nivel := 0 n.finProc := < array de ceros > n.totalHuecos := 0 n.cotaInf := calcularCota(n) fin func fin(n: Nodo, ed: EstructuraDatos) dev (b: Logico) alg b := ((n = NULO) O (n.cotaInf >= solOpt.tFinal)) fin func expande(n: Nodo) dev (hijos: array[] de Nodo) var i, j: Entero procesadoresAAsignar: array [1..M] de Logico // los que tienen un tiempo // acumulado diferente a los anteriores alg < hijos, procesadoresAAsignar > := numeroProcContFinProcDiferentes(n) j := 0 desde i := 1 hasta M si procesadoresAAsignar[i]: j := j + 1 hijos[j] := < crear Nodo > hijos[j].nivel := n.nivel + 1 hijos[j].tFinProc := < copiar n.tFinProc > hijos[j].tFinProc[i] := hijos[j].tFinProc[i] + t[hijos[j].nivel] hijos[j].sol := < copiar n.sol > hijos[j].sol.asigTarea[hijos[j].nivel] := i si hijos[j].tFinProc[i] <= n.sol.tFinal: hijos[j].sol.tFinal := n.sol.tFinal // esto es redundante hijos[j].totalHuecos := n.totalHuecos - t[hijos[j].nivel] | otras: hijos[j].sol.tFinal := hijos[j].tFinProc[i] hijos[j].totalHuecos := n.totalHuecos - t[hijos[j].nivel] + M*(hijos[j].sol.tFinal - n.sol.tFinal) fsi hijos[j].cotaInf := calcularCota(n) fsi fdesde fin func numeroProcContFinProcDiferentes(n: Nodo) dev (hijos: array [] de Nodo, procesadoresAAsignar: array [1..M] de Logico) var i, j, numProc: Entero enc: Logico alg numProc := 0 desde i := 1 hasta M < j, b > := < 1, falso > mientras j < i Y NO enc si n.tFinProc[j] = n.tFinProc[i]: enc := cierto fsi j := j + 1 fmientras si enc: procesadoresAAsignar[i] := falso | otras: procesadoresAAsignar[i] := cierto numProc := numProc + 1 fsi fdesde hijos := < crear array [1..numProc] de Nodo > fin func calcularCota(n: Nodo) dev (cota: Numero) var tRestantes: Numero alg tRestantes := 0 desde i := n.nivel + 1 hasta N tRestantes := tRestantes + t[i] fdesde si tRestantes < n.totalHuecos: cota := n.sol.tFinal | otras: cota := n.sol.tFinal + (tRestantes – n.totalHuecos) / M fsi fin func esMejorCota(n: Nodo) dev (b: Logico) alg b := (n.cotaInf < solOpt.tFinal) fin func esSolucion(n: Nodo) dev (b: Logico) alg b := (n.nivel = N) fin proc actualizaSolucion(n: Nodo) alg solOpt := < copiar n.sol > fin proc eliminaNodos(ed: EstructuraDatos) alg fin fClase PROBLEMA 12 (RAMIFICACIÓN Y ACOTACIÓN) Se tiene un laberinto bidimensional, representado por una matriz lab[1..M, 1..N] de enteros, de forma que en cada casilla puede haber un obstáculo (valor –1), un objeto de valor v > 0, o no haber nada (valor 0). La entrada al laberinto se produce por la casilla (1, 1) (esquina superior izquierda), y la salida por la casilla (M, N). Para atravesar el laberinto, los únicos movimientos 0 -1 -1 0 0 0 0 1 posibles son realizar un paso hacia la derecha o 0 1 0 5 5 -1 0 0 hacia abajo en la matriz, sin pasar dos veces por la misma casilla y sin pasar por los obstáculos. 0 3 -1 0 0 0 0 0 Codificar un algoritmo basado en Ramificación y acotación, para obtener una de las posibles soluciones óptimas (que maximicen el valor total obtenido), teniendo en cuenta que el valor máximo de un objeto es VMAX. -1 0 0 0 -1 0 -1 -1 0 2 -1 1 2 1 0 -1 4 0 5 -1 -1 0 0 0 Para la solución inicial suponer que se usa algoritmo voraz correspondiente al ejercicio 4 del boletín de algoritmos voraces (no resolver), de manera que devuelve la secuencia de pasos y el valor obtenido (-1 si no encuentra solución) Solución Clase EsquemaRyA fClas proc RyA() var ed: EstructuraDatos n: Nodo hijos: array[] de Nodo numHijos, i: Entero alg ed := crearEstructuraDatos() calcularSolucionInicial() n := calcularNodoInicial() mientras NO fin(n, ed) hijos := expande(n) numHijos := tamaño(hijos) desde i := 1 hasta numHijos si esMejorCota(hijos[i]): si esSolucion(hijos[i]) actualizaSolucion(hijos[i]) eliminaNodos(ed) |otras ed.añadir(hijos[i]) fsi fsi fdesde si NO ed.esVacia(): n := ed.obtener() |otras n := NULO fsi fmientras fin ... Clase LaberintoRyA hereda de EsquemaRyA lab: array [1..M, 1..N] de Entero solOpt: Solucion Clase Solucion movs: array [1..M+N-2] de (derecha, abajo) valor: Numero // valor obtenido fClase // sucesión de movimientos Clase Nodo x, y: Entero // posición actual (fila, columna) nivel: Entero // número de pasos realizados sol: Solucion // si es parcial, se refiere a los pasos realizados cotaSup: Numero // estimación de la mejor solución a partir del nodo fClase proc recorreLaberinto(laberinto: array [1..M, 1..N] de Entero) alg lab := < copiar laberinto > RyA() si solOpt.valor = -1: < no hay solución > | otras: < imprimir solOpt > fsi fin func crearEstructuraDatos() dev (ed: EstructuraDatos) alg ed := < crear Monticulo > // de máximos, según cotaSup fin proc calcularSolucionInicial() var aux: Entero alg solOpt = < llamada al algoritmo voraz (ej. 11, boletín voraces) > // si el algoritmo voraz no ha encontrado solución, solOpt.valor = -1 fin func calcularNodoInicial() dev (n: Nodo) alg n := < crear Nodo > < n.x, n.y > := < 0, 0 > n.nivel := 0 n.sol.movs := < crear array de (derecha, abajo) > n.sol.valor := 0 n.cotaSup := calcularCota(n) fin // da igual el valor func fin(n: Nodo, ed: EstructuraDatos) dev (b: Logico) alg b := ((n = NULO) O (n.cotaSup <= solOpt.valor)) fin func expande(n: Nodo) dev (hijos: array[] de Nodo) var numHijos, i, j: Entero movsPosibles: array [1..2] de Logico // 1: abajo, 2: derecha alg numHijos := 0 si n.i < M Y lab[n.x + 1, n.y] ≠ -1 // posible mov. abajo < numHijos, movsPosibles[1] > := < numHijos + 1, cierto > | otras: movsPosibles[1] := falso fsi si n.j < N Y lab[n.x, n.y + 1] ≠ -1 // posible mov. derecha < numHijos, movsPosibles[2] > := < numHijos + 1, cierto > | otras: movsPosibles[2] := falso fsi hijos := < crear array [1..numHijos] de Nodo > // puede ser vacío si movsPosibles[1]: j := 1 // para recorrer movsPosibles | otras: j := 2 fsi desde i := 1 hasta numHijos // recorre hijos hijos[i] := < crear Nodo > hijos[i].nivel := n.nivel + 1 hijos[i].sol := < copiar n.sol > si j = 1: // abajo < hijos[i].x, hijos[i].y > := < n.x + 1, n.y > hijos.sol.movs[hijos[i].nivel] := abajo | otras: // derecha < hijos[i].x, hijos[i].y > := < n.x, n.y + 1 > hijos.sol.movs[hijos[i].nivel] := derecha fsi hijos.sol.valor := n.sol.valor + lab[hijos[i].x, hijos[i].y]] hijos[i].cotaSup := calcularCota(n) j := j + 1 fmientras fin func calcularCota(n: Nodo) dev (cota: Numero) alg cota := n.sol.valor + VMAX * (M – n.x + N – n.y) // lo mejor que podemos // obtener, si en las restantes casillas encontramos VMAX fin func esMejorCota(n: Nodo) dev (b: Logico) alg b := (n.cotaSup > solOpt.valor) fin func esSolucion(n: Nodo) dev (b: Logico) alg b := (n.x = M Y n.y = N) fin proc actualizaSolucion(n: Nodo) alg solOpt := < copiar n.sol > fin proc eliminaNodos(ed: EstructuraDatos) alg fin fClase