PARTE II: ALGORÍTMICA Tema 4. Divide y vencerás.

Anuncio
Backtracking
1. Método general.
2. Análisis de tiempos de ejecución.
3. Ejemplos de aplicación.
3.1. Problema de las 8 reinas.
3.2. Problema de la mochila 0/1.
1
Método general
• El backtracking (método de retroceso ó vuelta atrás) es
una técnica general de resolución de problemas, aplicable
tanto a problemas de optimización, juegos y otros tipos.
• El backtracking realiza una búsqueda exhaustiva y
sistemática en el espacio de soluciones. Por ello, suele
resultar ineficiente.
• La solución de un problema de backtracking se puede
expresar como una tupla (x1, x2, ..., xn), satisfaciendo unas
restricciones P(x1, x2, ..., xn) y tal vez optimizando una cierta
función objetivo.
• En cada momento, el algoritmo se encontrará en un cierto
nivel k, con una solución parcial (x1, ..., xk). Si se puede
añadir un nuevo elemento a la solución xk+1, se genera y se
avanza al nivel k+1.
2
Método general
• Si no, se prueban otros valores de xk.
• Si no existe ningún valor posible por probar, entonces se
retrocede al nivel anterior k-1.
• Se sigue hasta que la solución parcial sea una solución
completa del problema, o hasta que no queden más
posibilidades.
• El resultado es equivalente a hacer un recorrido en
profundidad en el árbol de soluciones. Sin embargo, este
árbol es implícito, no se almacena en ningún lugar.
• Ejemplo. Dado un conjunto de números enteros {13, 11, 7},
encontrar si existe algún subconjunto cuya suma sea
exactamente 20.
3
Método general
• Posibilidad 1) Árbol binario: En cada nivel i decidir si el
elemento i está o no en la solución. Representación de la
solución: (x1, x2, x3), donde xi= (0, 1).
Árbol de
soluciones 1 0 2 1
3
0
1
0
1
0
6
1
9
1
10
13
0
1
0
1
0
1
4
5
7
8
11
12
14
15
0
7
11
18
13
20
24
31
k=1
(13)
k=2
(11)
k=3
(7)
Sumas totales
• Cada nodo representa un paso del algoritmo, una solución parcial en
cada momento dado. El árbol indica un orden de ejecución (recorrido
en profundidad) pero no se almacena en ningún lugar.
• Una solución es un nodo hoja con valor de suma 20.
• Posible mejora: En cada nodo llevamos el valor de la suma hasta
ese punto. Si el valor es mayor que 20: retroceder al nivel anterior.
4
Método general
• Posibilidad 2) Árbol combinatorio: En cada nivel i decidir qué elemento
se añade (1, 2 o 3). Representación de la solución (s1, ..., sm), donde m≤
n y si ∈ {1, 2, 3}.
0
1
Árbol de
soluciones 2
13 2
2
3
24 3
3
31 4
20 5
1
3
2
11 6
k=1
7 8
3
k=2
18 7
k=3
• Cada nodo es una posible solución. Será válida si la suma es 20.
• El recorrido es también en profundidad.
• Necesitamos funciones para generar los nodos, para descartar
nodos y para saber si un nodo es solución.
• ¿Cómo será la eficiencia del algoritmo? Depende del número de
nodos.
5
Método general
• Esquema general (sin recursividad). Suponiendo que
existe al menos una solución y que queremos obtener una
cualquiera.
Backtracking (var s: array [1.. Max_nivel] of tipo)
nivel = 1
fin = false
repetir
s[nivel] = Generar (nivel, s)
si Solución (nivel, s)
fin = true
en otro caso si Criterio (nivel, s)
nivel = nivel + 1
en otro caso mientras no MasHermanos (nivel, s)
Retroceder (nivel, s)
hasta fin=true
6
• Variables:
Método general
– s: Almacena la solución (un array, desde 1 hasta el nº máximo
de niveles).
– nivel: Indica el nivel actual en el que se encuentra el algoritmo.
– fin: Valdrá true cuando hayamos encontrado alguna solución.
• Funciones:
– Generar (nivel, s): Genera el siguiente hermano (o el primero)
para el nivel actual. Devuelve el siguiente valor a añadir a la
solución parcial actual (depende de la solución parcial y del
nivel).
– Solución (nivel, s): Comprueba si la solución (s[1], ..., s[nivel])
es una solución válida para el problema.
– Criterio (nivel, s): Comprueba si a partir de (s[1], ..., s[nivel])
se puede alcanzar una solución válida, o si se puede mejorar
la actual. En otro caso se rechazarán todos los descendientes.
7
• Funciones:
Método general
– MasHermanos (nivel, s): Devuelve verdad si hay más
hermanos del nodo actual que todavía no han sido
generados.
– Retroceder (nivel, s): Retrocede un nivel en el árbol de
soluciones. Disminuye en 1 el valor de nivel, y posiblemente
tendrá que actualizar la solución actual, quitando los
elementos retrocedidos.
• ¿Cómo serían estas funciones en los ejemplos anteriores?
• Otros posibles casos de problemas:
1) No está garantizado que exista una solución, puede existir
alguna o no.
2) Queremos obtener todas las soluciones, no sólo una.
3) El problema es de optimización. De todas las soluciones
posibles queremos aquella que maximice (o minimice) una
función objetivo.
8
Análisis de tiempos de ejecución
• El tiempo de ejecución depende del número de nodos generados y del
tiempo requerido para cada nodo, que viene dado por el coste de las
funciones.
• Suponiendo que una solución sea de la forma: (x1, x2, ..., xn), en el peor
caso se generarán todas las posibles combinaciones para cada xi.
• Si el número de posibles valores para cada xi es mi, entonces se generan:
m1
m1·m2
...
m1·m2· ... ·mn
nodos en el nivel 1
nodos en el nivel 2
....
nodos en el nivel n
• Ejemplo: para el problema de la suma de subconjuntos mi = 2. El número
de nodos generados es:
t(n) = 2 + 22 + 23 + ... + 2n = 2n+1 - 2
• Ejemplo: calcular todas las permutaciones de (1, 2, ..., n). En el nivel 1
tenemos n posibilidades, en el nivel 2 n-1, ..., en el nivel n una posibilidad.
t(n) = n + n·(n-1) + n·(n-1)·(n-2) + ... + n! ∈ O(n!)
• En general tendremos tiempos con órdenes de complejidad factoriales o
9
exponenciales.
•
•
•
•
•
Problema de las n reinas
Problema: Dado un tablero de ajedrez de tamaño nxn,
encontrar una forma (o todas) de colocar n reinas, sin que
ninguna de ellas pueda comerse a otra.
Solución 1: Probar todas las posiciones posibles y para
cada una comprobar si es válida. Para tamaño 8:
4.4261165.368 posibilidades.
Solución 2: Colocar cada reina en cada fila. Una solución
será un array de 1..8. Para cada reina se probarán cada una
de las 8 columnas. Habrá que probar: 88 = 161777.216
posibilidades.
Solución 3: No colocar dos reinas en una misma columna.
La solución será una permutación de los números (1, 2, ...,
8): 8! = 40.320 posibilidad.
Error: No se comprueba si la situación es correcta hasta el
final.
10
Problema de las n reinas
Solución con backtracking.
• Características: Puede existir solución o no. No es un problema
de optimización. Suponemos que buscamos todas las soluciones.
• Representación.
– Array solución: s: array [1..8] of 0..8.
– s[i] = j. La reina de la fila i está en la columna j.
• Recorrido con backtracking: En cada nivel i, probar las formas
de colocar la reina de la fila i, desde la columna 1 hasta la 8.
• La posición será válida (podemos avanzar de nivel) si la reina i no
está en la misma columna o diagonal que alguna de las reinas
anteriores.
11
Funciones:
Problema de las n reinas
– Generar (nivel, s). Probar primero la posición 1, luego la
2, ..., hasta la 8.
– MasHermanos (nivel, s). Cierto si s[nivel] es menor que
8.
– Criterio (nivel, s). Comprobar si la reina de la posición
s[nivel] no se come a las anteriores (1, 2, ..., nivel-1).
– Solución (nivel, s). Cierto si el nodo es una hoja (nivel =
n) y se cumple Criterio.
– Retroceder (nivel, s). Quitar la reina de la posición nivel.
s[nivel]:= 0 (valor de inicialización).
12
Problema de las n reinas
NReinasBacktracking (var s: array [1.. n] of integer)
nivel = 1
s[1] = 0
repetir
s[nivel] = s[nivel] + 1
mientras (s[nivel] ≤ n) y no Criterio (nivel, s)
s[nivel] = s[nivel] + 1
si (nivel=n) y (s[nivel] ≤ n)
MostrarSolucion (s)
en otro caso si (s[nivel] ≤ n)
nivel = nivel + 1
s[nivel] = 0
en otro caso
nivel = nivel -1
hasta nivel = 0
Criterio (nivel: 0..n; s: array [1.. n] of integer) : boolean
para i = 1,2,...,nivel-1
si (s[nivel]=s[i]) o |s[i] - s[nivel]| = |i - nivel|
Devolver false
Devolver true
13
Problema de las n reinas
• Ejemplo. N= 4.
1
X
X
6
2
X
X
3
X
X
X
4
X
7
X
8
X
5
X
X
X
X
X
9
X
X
X
14
Problema de las n reinas
• Evaluación de la eficiencia. La evaluación es compleja, ya que el
tiempo de ejecución en cada nodo no es constante y el número de
nodos generado es difícil de predecir.
– El tiempo de ejecución de la función Criterio depende del nivel. Para
nivel i, el número de comprobaciones es (i-1). Está en O(i). Se puede
hacer constante.
– Número de nodos, en el peor caso:
• Nivel 0: 1
• Nivel 1: n/2
0·n/2 Comprobaciones
• Nivel 2: (n/2)(n-1)
1·(n/2)(n-1)
“
• ....
• Nivel n: n!/2
(n-1)·n!/2
“
• La cota superior está muy alejada del número real de nodos
generados.
• Ejemplo. Para n = 4. Número de nodos en el peor caso = 33.
Número de nodos generados realmente = 9.
15
Problema de las n reinas
• Además, en cada nodo (excepto en las hojas) se
comprueban 4 posibles descendientes. Este es el número de
veces que se ejecutan las funciones Criterio y Genera
(s[nivel] = s[nivel] + 1).
• Solución: Estimación de la eficiencia por probabilidad.
Hacemos un cálculo aproximado del número de nodos
esperado.
• Estimación de la eficiencia por probabilidad.
– Generamos varias permutaciones de (1, 2, .., n), de forma
aleatoria.
– Para cada una calcular el nivel al que llegaría (aplicando
la función Criterio), y el número de nodos máximo para
ese nivel.
– Hacer una media del número de nodos.
16
Problema de la mochila 0/1
• Problema: maximizar
n
∑ xi ⋅ vi
sujeto a
i =1
n
∑ xi ⋅ wi ≤ M
con xi= 0, 1
i =1
siendo vi = beneficio del objeto i; wi = peso de i; M capacidad máxima.
• Características del problema:
– Es un problema de optimización (maximización).
– Sólo nos interesa una solución, la óptima.
– Existirá al menos una solución (no incluir ningún objeto).
• Diseño de la solución con backtracking:
– Representación de la solución: Una solución será de la forma (x1,
x2, ..., xn), con xi= 0, 1. Se generará un árbol binario de soluciones.
– En cada nivel i, probamos la1 posibilidad de incluir o no el objeto i.
0
0
3
0
1
4
5
2
1
6
1
0
7
8
1
0
10
0
1
11 12
x1
9
1
13
1
0
14 15
x2
x3
17
Problema de la mochila 0/1
• Para calcular el peso y el beneficio en cada nodo podemos usar
variables locales w_act, v_act que guardarán el peso y el
beneficio acumulado.
• El array de soluciones será s: array [1..n] of –1,0,1.
– s[i] = 1, 0. Se añade o no se añade el objeto i.
– s[i] = -1. No se ha considerado el objeto i (es el valor de
inicialización).
• Al ser un problema de optimización no acabamos hasta haber
recorrido todos los nodos. Acabar cuando nivel = 0 (volvemos al
nodo raíz).
• En cada momento llevamos la mejor solución hasta un nodo. Si
encontramos una solución nueva, comprobar si es mejor que la
solución actual.
• Variable v_max, con el valor de la mejor solución hasta este nodo
y s_max con los objetos que la componen.
si Solución (nivel, s)
si v_act > v_max
v_max = v_act
s_max = s
18
Problema de la mochila 0/1
• Generar (nivel, s). Probar los valores 0 y 1.
Si s[nivel]=-1
Devolver 0
en otro caso
w_act = w_act + w[nivel]
v_act = v_act + v[nivel]
Devolver 1
• Solución (nivel, s). Indica los nodos hoja que cumplen la
restricción de peso.
Devolver (nivel=n) y (w_act ≤ M)
• Criterio (nivel, s). Indicará si se cumple la restricción de peso y no
estamos en el último nivel.
Devolver (nivel<n) y (w _act ≤ M)
• MasHermanos (nivel, s).
Devolver s[nivel] ≠ 1
• Retroceder (nivel, s).
w_act = w_act - s[nivel]*w[nivel]
v_act = v_act - s[nivel]*v[nivel]
s[nivel] = -1
nivel = nivel - 1
19
Problema de la mochila 0/1
• Orden de complejidad del algoritmo: Número de nodos generado=
2n+1-1. El algoritmo es de O(2n).
• Problema: En el ejemplo, se generan todos los nodos posibles. La
función Criterio es siempre cierta (excepto para algunos nodos
hoja).
• Solución: Intentar eliminar algunos nodos del árbol de soluciones,
con una función Criterio más restrictiva.
– Para cada nodo, hacer una estimación del máximo beneficio
que se podría obtener a partir del mismo.
– Si es menor que el mayor beneficio de una solución anterior
(v_max) entonces rechazar ese nodo y sus descendientes.
• La estimación del beneficio para el nivel y nodo actual será:
v_estimado = v_act + Estimacion (k + 1, M - w_act)
• Estimacion (k, Q): Estimar una cota superior para el problema de
la mochila 0/1, usando los objetos k..n, con capacidad máxima Q.
20
Problema de la mochila 0/1
• Idea: el resultado del problema de la mochila (no 0/1) es una cota
superior válida para el problema de la mochila 0/1.
• Estimacion (k, Q): Aplicar el algoritmo voraz para el problema de
la mochila, con los elementos de k..n. Si los beneficios son
enteros, nos podemos quedar con la parte entera del resultado
anterior.
• Ejemplo. n = 4; M = 7;
v = (2, 3, 4, 5) w = (1, 2, 3, 4)
21
14
Problema de la mochila 0/1
• Función Criterio (nivel, s, v_act, w_act, v_max).
Si (w _act > M) o (nivel=n)
Devolver False
en otro caso
v_estimado= v_act + MochilaVoraz (nivel+1, M - w_act)
Devolver v_estimado > v_max
• Modificación en el algoritmo de backtracking.
...
mientras no MasHermanos (nivel, s) o
no Criterio (nivel, s, v_act, w_act, v_max)
Retroceder (nivel, s);
...
• Se eliminan nodos a costa de aumentar el tiempo de ejecución de
la función Criterio. ¿Cuál será el tiempo de ejecución total?
22
Problema de la mochila 0/1
• Suponemos todos los objetos ordenados por vi/wi.
• Tiempo de la función Criterio en el nivel i (en el peor caso) = 1
+ Tiempo de la función MochilaVoraz = 1 + n - i.
• Idea intuitiva. Tiempo en el peor caso (suponiendo todos los
nodos): Número de nodos O(2n) * Tiempo de cada nodo
(función criterio) O(n).
• ¿Tiempo: O(n·2n)?
n
n
n
t ( n ) = ∑ 2 ⋅ ( n − i + 1) = ( n + 1)∑ 2 − ∑i ⋅ 2 i = 2·2 n +1 − 2n − 4
i =1
i
i =1
i
i =1
• Conclusiones:
– El cálculo “intuitivo” del tiempo no es correcto (no es válido
O(n·2n)).
– El orden de complejidad no varía, O(2n), aunque el tiempo
es 2 veces mayor.
– Si se podan más de la mitad de los nodos podemos esperar
una mejora.
23
Problema de la mochila 0/1
Posible modificación 1
• Para cada nodo, generar primero el valor 1 y luego el valor 0 (en
lugar de primero 0 y luego 1).
• Ejemplo anterior.
• Idea: es de esperar que la solución de la mochila 0/1 sea “parecida”
a la de la mochila no 0/1. Si ordenamos los objetos por vi/wi
entonces tendremos una solución con 1 en las primeras posiciones.
24
Problema de la mochila 0/1
Posible modificación 2
• Utilizar una representación de la solución como un conjunto de los
elementos incluidos. S = (s1, s2, ..., sm) donde m≤n y si ∈ {1, 2, ..., n}.
• ¿Cómo generar un
árbol de este tipo?
• ¿Cuál es el número
de nodos máximo?
Árbol
combinatorio
25
Descargar