Tema 23: Técnicas de diseño descendente de algoritmos

1 La técnica de divide y vencerás

module DivideVenceras (divideVenceras) where

divideVenceras :: (p -> Bool) -> (p -> s) -> (p -> [p])      
                  -> (p -> [s] -> s) -> p -> s
divideVenceras ind resuelve divide combina pbInicial =
    dv' pbInicial where 
    dv' pb
        | ind pb    = resuelve pb
        | otherwise = combina pb [dv' sp | sp <- divide pb]

1.1 La ordenación por mezcla como ejemplo de DyV

ghci> ordenaPorMezcla [3,1,4,1,5,9,2,8]
[1,1,2,3,4,5,8,9]
import I1M.DivideVenceras

ordenaPorMezcla :: Ord a => [a] -> [a]
ordenaPorMezcla xs = 
    divideVenceras ind id divide combina xs
    where 
      ind xs            = length xs <= 1
      divide xs         = [take n xs, drop n xs]
                          where n = length xs `div` 2
      combina _ [l1,l2] = mezcla l1 l2
mezcla [1,3] [2,4,6]  ==  [1,2,3,4,6]
mezcla :: Ord a => [a] -> [a] -> [a]
mezcla [] b = b
mezcla a [] = a
mezcla a@(x:xs) b@(y:ys) 
    | x <= y    = x : (mezcla xs b)
    | otherwise = y : (mezcla a ys)

1.2 La ordenación rápida como ejemplo de DyV

ghci> ordenaRapida [3,1,4,1,5,9,2,8]
[1,1,2,3,4,5,8,9]
import I1M.DivideVenceras

ordenaRapida :: Ord a => [a] -> [a]
ordenaRapida xs = 
    divideVenceras ind id divide combina xs
    where 
      ind xs                = length xs <= 1
      divide (x:xs)         = [[ y | y <- xs, y <= x],
                               [ y | y <- xs, y > x]]
      combina (x:_) [l1,l2] = l1 ++ [x] ++ l2

2 Búsqueda en espacios de estados

2.1 El patrón de búsqueda en espacios de estados

Descripción de los problemas de espacios de estados

Las características de los problemas de espacios de estados son:

Se supone que el grafo implícito de espacios de estados es acíclico.

El patrón de búsqueda en espacios de estados

module BusquedaEnEspaciosDeEstados (buscaEE) where
import I1M.Pila

buscaEE:: (Eq nodo) => (nodo -> [nodo])  -> (nodo -> Bool) 
                       -> nodo -> [nodo]
buscaEE sucesores esFinal x = busca' (apila x vacia) 
 where busca' p  
    | esVacia p        = [] 
    | esFinal (cima p) = cima p : busca' (desapila p)
    | otherwise        = busca' (foldr apila (desapila p) 
                                       (sucesores x))
                         where x = cima p

2.2 El problema de las n reinas

import I1M.BusquedaEnEspaciosDeEstados  
type Columna = Int
type Fila    = Int
type SolNR = [(Columna,Fila)]
valida [(1,1)] (2,2)  ==  False
valida [(1,1)] (2,3)  ==  True
valida :: SolNR -> (Columna,Fila) -> Bool
valida solp (c,r) = and [test s | s <- solp]
    where test (c',r') = and [c'+r'/=c+r,
                              c'-r'/=c-r,
                              r'/=r]
type NodoNR = (Columna,Columna,SolNR)
ghci> sucesoresNR (1,4,[])
[(2,4,[(1,1)]),(2,4,[(1,2)]),(2,4,[(1,3)]),(2,4,[(1,4)])]
sucesoresNR :: NodoNR -> [NodoNR]
sucesoresNR (c,n,solp)
    = [(c+1,n,solp++[(c,r)]) | r <- [1..n], 
                               valida solp (c,r)]
esFinalNR :: NodoNR -> Bool
esFinalNR (c,n,solp) = c > n

Solución del problema de las n reinas por EE

ghci> buscaEE_NR 8
[(1,1),(2,5),(3,8),(4,6),(5,3),(6,7),(7,2),(8,4)]
buscaEE_NR :: Columna -> SolNR
buscaEE_NR n = s
    where ((_,_,s):_) = buscaEE sucesoresNR 
                                esFinalNR 
                                (1,n,[])
nSolucionesNR 8  ==  92
nSolucionesNR :: Columna -> Int
nSolucionesNR n = 
    length (buscaEE sucesoresNR 
                    esFinalNR 
                    (1,n,[]))

2.3 El problema de la mochila

import I1M.BusquedaEnEspaciosDeEstados
import Data.List (sort)
type Peso = Int
type Valor = Float
type Objeto = (Peso,Valor)
type SolMoch = [Objeto]
type NodoMoch = (Valor,Peso,Peso,[Objeto],SolMoch)
sucesoresMoch :: NodoMoch -> [NodoMoch]
sucesoresMoch (v,p,limite,objetos,solp)
    = [( v+v',
         p+p',
         limite,
         [o | o@(p'',_) <- objetos,(p''>=p')], 
         (p',v'):solp )
       | (p',v') <- objetos, 
         p+p' <= limite]
esObjetivoMoch :: NodoMoch -> Bool
esObjetivoMoch (_,p,limite,((p',_):_),_) = 
   p+p'>limite

Solución del problema de la mochila por EE

> buscaEE_Mochila [(2,3),(3,5),(4,6),(5,10)] 8
([(5,10.0),(3,5.0)],15.0)
> buscaEE_Mochila [(2,3),(3,5),(5,6)] 10
([(3,5.0),(3,5.0),(2,3.0),(2,3.0)],16.0)
> buscaEE_Mochila [(2,2.8),(3,4.4),(5,6.1)] 10
([(3,4.4),(3,4.4),(2,2.8),(2,2.8)],14.4)
buscaEE_Mochila :: [Objeto] -> Peso -> (SolMoch,Valor)
buscaEE_Mochila objetos limite = (sol,v) 
    where 
      (v,_,_,_,sol) = 
          maximum (buscaEE sucesoresMoch 
                           esObjetivoMoch  
                           (0,0,limite,sort objetos,[]))

3 Búsqueda por primero el mejor

3.1 El patrón de búsqueda por primero el mejor

El patrón de búsqueda por primero el mejor

module BusquedaPrimeroElMejor (buscaPM)  where
import I1M.ColaDePrioridad

buscaPM :: (Ord nodo) => 
           (nodo -> [nodo])   -- sucesores
           -> (nodo -> Bool)  -- esFinal
           -> nodo            -- nodo actual
           -> [nodo]          -- solución
buscaPM sucesores esFinal x = busca' (inserta x vacia)
 where
   busca' c 
    | esVacia c = []
    | esFinal (primero c)  
        = (primero c):(busca' (resto c))
    | otherwise            
        = busca' (foldr inserta (resto c) (sucesores x))
          where x = primero c

3.2 El problema del 8 puzzle

Para el 8-puzzle se usa un cajón cuadrado en el que hay situados 8 bloques cuadrados. El cuadrado restante está sin rellenar. Cada bloque tiene un número. Un bloque adyacente al hueco puede deslizarse hacia él. El juego consiste en transformar la posición inicial en la posición final mediante el deslizamiento de los bloques. En particular, consideramos el estado inicial y final siguientes:

+---+---+---+                   +---+---+---+
| 2 | 6 | 3 |                   | 1 | 2 | 3 | 
+---+---+---+                   +---+---+---+ 
| 5 |   | 4 |                   | 8 |   | 4 | 
+---+---+---+                   +---+---+---+ 
| 1 | 7 | 8 |                   | 7 | 6 | 5 | 
+---+---+---+                   +---+---+---+ 
Estado inicial                  Estado final
import I1M.BusquedaPrimeroElMejor
import Data.Array
type Tablero  = Array Int Posicion
+---+---+---+
| 2 | 6 | 3 | 
+---+---+---+ 
| 5 |   | 4 | 
+---+---+---+ 
| 1 | 7 | 8 | 
+---+---+---+ 
inicial8P :: Tablero 
inicial8P = array (0,8) [(2,(1,3)),(6,(2,3)),(3,(3,3)),
                         (5,(1,2)),(0,(2,2)),(4,(3,2)),
                         (1,(1,1)),(7,(2,1)),(8,(3,1))]
+---+---+---+
| 1 | 2 | 3 | 
+---+---+---+ 
| 8 |   | 4 | 
+---+---+---+ 
| 7 | 6 | 5 | 
+---+---+---+ 
final8P :: Tablero
final8P = array (0,8) [(1,(1,3)),(2,(2,3)),(3,(3,3)),
                       (8,(1,2)),(0,(2,2)),(4,(3,2)),
                       (7,(1,1)),(6,(2,1)),(5,(3,1))]
distancia (2,7) (4,1)  ==  8
distancia :: Posicion -> Posicion -> Int
distancia (x1,y1) (x2,y2) = abs (x1-x2) + abs (y1-y2)
adyacente (3,2) (3,1)  ==  True
adyacente (3,2) (1,2)  ==  False
adyacente :: Posicion -> Posicion -> Bool
adyacente p1 p2 = distancia p1 p2 == 1
todosMovimientos :: Tablero -> [Tablero]
todosMovimientos t = 
    [t//[(0,t!i),(i,t!0)] | i<-[1..8], 
                            adyacente (t!0) (t!i)] 
data Tableros = Est [Tablero] deriving Show
sucesores8P :: Tableros -> [Tableros]
sucesores8P (Est(n@(t:ts))) = 
    [Est (t':n) | t' <- todosMovimientos t, 
                  t' `notElem` ts]
esFinal8P :: Tableros -> Bool
esFinal8P (Est (t:_)) = t == final8P
heur1 inicial8P  ==  12
heur1 :: Tablero  -> Int
heur1 t = 
    sum [distancia (t!i) (final8P!i) | i <- [0..8]]
instance Eq Tableros
    where Est(t1:_) == Est(t2:_) = heur1 t1 == heur1 t2
instance Ord Tableros where 
    Est (t1:_) <= Est (t2:_) = heur1 t1 <= heur1 t2
buscaPM_8P = buscaPM sucesores8P      
                     esFinal8P        
                     (Est [inicial8P])

4 Búsqueda en escalada

4.1 El patrón de búsqueda en escalada

module BusquedaEnEscalada (buscaEscalada) where

buscaEscalada :: Ord nodo => (nodo -> [nodo]) 
                 -> (nodo -> Bool) -> nodo -> [nodo]
buscaEscalada sucesores esFinal x = 
    busca' (inserta x vacia) where
    busca' c  
        | esVacia c           = [] 
        | esFinal (primero c) = [primero c]
        | otherwise           = 
            busca' (foldr inserta vacia (sucesores x))
            where x = primero c

4.2 El problema del cambio de monedas por escalada

import I1M.BusquedaEnEscalada
type Moneda = Int
monedas :: [Moneda]
monedas = [1,2,5,10,20,50,100]
type Soluciones = [Moneda]
type NodoMonedas = (Int, [Moneda])
ghci> sucesoresMonedas (199,[])
[(198,[1]),(197,[2]),(194,[5]),(189,[10]),
 (179,[20]),(149,[50]),(99,[100])]
sucesoresMonedas :: NodoMonedas -> [NodoMonedas]
sucesoresMonedas (r,p) = 
    [(r-c,c:p) | c <- monedas, r-c >= 0]
esFinalMonedas :: NodoMonedas -> Bool
esFinalMonedas (v,_) = v==0
cambio 199  ==  [2,2,5,20,20,50,100]
cambio :: Int -> Soluciones
cambio n = 
    snd (head (buscaEscalada sucesoresMonedas 
                             esFinalMonedas 
                             (n,[])))

4.3 El algoritmo de Prim del árbol de expansión mínimo por escalada

import I1M.BusquedaEnEscalada
import I1M.Grafo
import Data.Array
import Data.List
g1 :: Grafo Int Int    
g1 = creaGrafo D (1,5) [(1,2,12),(1,3,34),(1,5,78),
                        (2,4,55),(2,5,32),
                        (3,4,61),(3,5,44),
                        (4,5,93)]
type Arista a b = (a,a,b)
type NodoAEM a b = (b,[a],[a],[Arista a b])
ghci> sucesoresAEM g1 (0,[1],[2..5],[])
[(12,[2,1],[3,4,5],[(1,2,12)]),
 (34,[3,1],[2,4,5],[(1,3,34)]),
 (78,[5,1],[2,3,4],[(1,5,78)])]
sucesoresAEM :: (Ix a,Num b) => (Grafo a b) -> (NodoAEM a b)
                           -> [(NodoAEM a b)]
sucesoresAEM g (_,t,r,aem) = 
   [(peso x y g, (y:t), delete y r, (x,y,peso x y g):aem)
    | x <- t , y <- r, aristaEn g (x,y)]
esFinalAEM (_,_,[],_) = True
esFinalAEM _          = False
prim g1 == [(2,4,55),(1,3,34),(2,5,32),(1,2,12)]
prim g = sol
    where [(_,_,_,sol)] = buscaEscalada (sucesoresAEM g) 
                                        esFinalAEM
                                        (0,[n],ns,[])
          (n:ns) = nodos g


Universidad de Sevilla

José A. Alonso Jiménez
Grupo de Lógica Computacional
Dpto. de Ciencias de la Computación e I.A.
Universidad de Sevilla