Backtracking
Diseño y Análisis de Algoritmos
Backtracking Contenidos
Contenidos
1 Introducción
2 Árboles de búsqueda
3 N reinas
4 Otros problemas
5 Ramificación y poda
6 Poda alfa-beta
7 Esquemas generales
URJC DAA 2 / 67
Backtracking Introducción
Introducción
URJC DAA 3 / 67
Backtracking Introducción
Backtracking - Vuelta atrás
Estrategia para encontrar soluciones a problemas con restricciones
definidos sobre espacios discretos (de elevado tamaño)
Construye soluciones parciales progresivamente, las cuales deben
cumplir las restricciones del problema
El recorrido (en profundidad) tiene éxito si, procediendo de esta
forma, se puede definir por completo una solución (en una hoja de un
árbol de recursión)
Puede detenerse al encontrar una solución o seguir hasta encontrar
todas
Si en alguna etapa la solución parcial construida hasta el momento no
se puede completar, se vuelve atrás deshaciendo la solución parcial,
hasta un punto donde puede seguir explorando posibles soluciones
Es un método de “fuerza bruta” pero “inteligente”
URJC DAA 4 / 67
Backtracking Introducción
Ejemplo - 4 reinas
URJC DAA 5 / 67
Backtracking Árboles de búsqueda
Árboles de búsqueda
URJC DAA 6 / 67
Backtracking Árboles de búsqueda
Permutaciones de {a, b, c}
a c
b
b c a c a b
c b c a b a
(a,b,c) (a,c,b) (b,a,c) (b,c,a) (c,a,b) (c,b,a)
URJC DAA 7 / 67
Backtracking Árboles de búsqueda
Permutaciones de {a, b, c}
primer a c
b 3 llamadas
elemento
segundo
b c a c a b 2 llamadas
elemento
tercer
c b c a b a 1 llamada
elemento
permutación (a,b,c) (a,c,b) (b,a,c) (b,c,a) (c,a,b) (c,b,a)
URJC DAA 8 / 67
Backtracking Árboles de búsqueda
Permutaciones de {a, b, c}
¿Cuántas llamadas recursivas se hacen en cada nivel?
3, 2, 1 (se podrı́an controlar con bucles)
Pero lo más fácil es generar siempre 3 posibles llamadas, y usar un
vector de valores booleanos para ver si realmente se debe realizar la
llamada recursiva
El vector de booleanos se puede pasar por valor o referencia
Referencia: habrá que deshacer los cambios al retroceder (C o Java)
Valor: no será necesario deshacer cambios
El nivel de la llamada indica la posición del nuevo elemento a añadir
Al llegar al último nivel (a las hojas) tenemos completada la
permutación
URJC DAA 9 / 67
Backtracking Árboles de búsqueda
Implementación - I
1 void permutaciones(int n){
2 int[] perm = new int[n];
3 boolean[] libres = new boolean[n];
4
5 for(int i=0; i<n; i++)
6 libres[i] = true;
7
8 perms(n, 0, perm, libres);
9 }
10
11 void imprimir(int[] v){
12 for(int i=0; i<[Link]; i++)
13 [Link](v[i]+" ");
14
15 [Link]();
16 }
URJC DAA 10 / 67
Backtracking Árboles de búsqueda
Implementación - II
1 void perms(int n, int i, int[] solucion, boolean[] xs){
2 for(int k=0; k<n; k++)
3 if(xs[k]){
4 solucion[i] = k;
5 xs[k] = false;
6
7 if(i==n-1)
8 imprimir(solucion);
9 else
10 perms(n, i+1, solucion, xs);
11
12 xs[k] = true;
13 }
14 }
n: número de elementos (profundidad del árbol)
i: posición del elemento a insertar
solucion: permutación parcial construida
xs: indica los elementos incluidos en la solución parcial
URJC DAA 11 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - árbol binario
0 1
0 1 0 1
0 1 0 1 0 1 0 1
{} {c} {b} {b,c} {a} {a,c} {a,b} {a,b,c}
URJC DAA 12 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - árbol binario
se incluye: a 0 1
se incluye: b 0 1 0 1
se incluye: c 0 1 0 1 0 1 0 1
{} {c} {b} {b,c} {a} {a,c} {a,b} {a,b,c}
URJC DAA 13 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - árbol binario
Árbol binario
En cada nodo decides si un elemento estará presente o no
Simplemente crea un vector un vector de valores booleanos
[1, 0, 1] = {a, c}
No es necesario deshacer los cambios
URJC DAA 14 / 67
Backtracking Árboles de búsqueda
Implementación - I
Solución - árbol binario
1 void partesConj1(int[] c){
2 boolean[] subc = new boolean[[Link]];
3
4 partes1([Link], 0, c, subc);
5 }
6
7 void imprimir(int[] c, boolean[] v){
8 for(int i=0; i<[Link]; i++)
9 if(v[i])
10 [Link](c[i]+" ");
11
12 [Link]();
13 }
URJC DAA 15 / 67
Backtracking Árboles de búsqueda
Implementación - II
Solución - árbol binario
1 void partes1(int n, int i, int[] c, boolean[] subc){
2 for(int k=0; k<=1; k++){
3 if(k==0)
4 subc[i] = false;
5 else
6 subc[i] = true;
7
8 if(i==n-1)
9 imprimir(c,subc);
10 else
11 partes1(n, i+1, c, subc);
12 }
13 }
n: número de elementos (profundidad del árbol)
i: elemento a considerar (nivel del árbol)
c: vector de elementos
subc: valores booleanos que definen los subconjuntos
URJC DAA 16 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - subconjunto en cada nodo
{}
a c
b
{a} {b} {c}
b c c
{a,b} {a,c} {b,c}
{a,b,c}
URJC DAA 17 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - subconjunto en cada nodo
{}
0 elementos
a c
b
{a} {b} {c}
1 elemento
b c c
{a,b} {a,c} {b,c}
2 elementos
c
{a,b,c}
3 elementos
La etiqueta de la rama indica qué elemento a insertar
URJC DAA 18 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - subconjunto en cada nodo
El árbol de recursión no es binario
En el nivel i las soluciones tienen i elementos
Crea un vector con los ı́ndices de los elementos que se van incluyendo
[0, 2] = {a, c}
Tiene la mitad de nodos (factor constante) que el árbol binario del
primer algoritmo
Se obtiene un subconjunto en cada nodo (no solo en las hojas)
URJC DAA 19 / 67
Backtracking Árboles de búsqueda
Partes de {a, b, c}
Solución - subconjunto en cada nodo
-1
0 2 j
1
1 2 2 j+1 j+2 ... n-1
Ahora es necesario llevar dos ı́ndices
Posición en la solución del elemento a incluir
Índice del elemento a incluir (qué elemento se incluye)
URJC DAA 20 / 67
Backtracking Árboles de búsqueda
Implementación - I
Solución - subconjunto en cada nodo
1 void partesConj2(int[] c){
2 int[] subc = new int[[Link]];
3
4 partes2([Link], 0, -1, c, subc);
5 }
6
7 void imprimir(int[] c, int[] subc, int fin){
8 for(int i=0; i<=fin; i++)
9 [Link] (c[subc[i]]+" ");
10
11 [Link]();
12 }
URJC DAA 21 / 67
Backtracking Árboles de búsqueda
Implementación - II
Solución - subconjunto en cada nodo
1 void partes2(int n, int i, int j, int[] c, int[] subc){
2 imprimir(c, subc, i-1);
3
4 for(int k=j+1; k<n; k++){
5 subc[i] = c[k];
6
7 partes2(n, i+1, k, c, subc);
8 }
9 }
n: número de elementos (profundidad máxima del árbol)
i: posición en la solución del elemento a incluir
j: ı́ndice del elemento a incluir (qué elemento se incluye)
c: conjunto (vector) original
subc: subconjuntos hallados
URJC DAA 22 / 67
Backtracking N reinas
N reinas
URJC DAA 23 / 67
Backtracking N reinas
N reinas
Problema de las N reinas
Dado un “tablero ajedrez” de n × n celdas, se pide ubicar n reinas de
modo que no se amenacen
No pueden estar en la misma fila
No pueden estar en la misma columna
No pueden estar en la misma diagonal (principal o secundaria)
Ejemplo más famoso de problema que puede resolverse aplicando
backtracking
URJC DAA 24 / 67
Backtracking N reinas
8 reinas
Para n = 8 hay 92 soluciones posibles
Aunque 12 únicas
Las demás pueden obtenerse aplicando simetrı́as, rotaciones y
traslaciones
El problema puede solicitar encontrar una solución o todas
URJC DAA 25 / 67
Backtracking N reinas
N reinas
Solución obvia pero absurda:
2
Probar las 2n formas de colocar reinas en el tablero
1,84 · 1019 para n = 8
65536 para n = 4
Pero los conjuntos solución solo deben contener n elementos
n2
Esto reducirı́a nuestro espacio de búsqueda a n
4,42 · 1010 para n = 8
1820 para n = 4
URJC DAA 26 / 67
Backtracking N reinas
N reinas
Además solo puede haber una reina por cada columna:
Esto reduce las posibilidades a nn (hay n formas de colocar una reina
en una columna, y hay n columnas)
16777216 para n = 8
256 para n = 4
Pero además, no puede haber dos reinas en la misma fila
Esto convierte nuestro problema en la búsqueda de una permutación
con n! posibilidades (lo cual sigue siendo elevado)
40320 para n = 8
24 para n = 4
URJC DAA 27 / 67
Backtracking N reinas
N reinas
Formato de la solución:
(fila de la columna 1, fila de la columna 2,. . . ,fila de la columna n)
(2, 4, 1, 3) en la figura
Todas las filas son diferentes y tienen que estar representadas
Tendremos que buscar las permutaciones válidas
URJC DAA 28 / 67
Backtracking N reinas
Árbol de búsqueda para N = 4
1 4
2 3
2 4 1 4 1 4 1 3
3 3 2 2
3 4 2 4 2 3 3 4 1 4 1 3 2 4 1 4 1 2 2 3 1 3 1 2
4 3 4 2 3 2 4 3 4 1 3 1 4 2 4 1 2 1 3 2 3 1 2 1
Podemos emplear un algoritmo para buscar permutaciones
Al llegar a una hoja se “han colocado” las cuatro reinas y podemos
probar si la solución es válida
Pero, podemos comprobar si la solución parcial puede llegar a ser
solución final antes de llegar a una hoja
Podarı́amos el árbol ahorrando cálculos
URJC DAA 29 / 67
Backtracking N reinas
Árbol de búsqueda para N = 4
1 2
2 4 1 4
3 3
2 4 2 3 1
3 3
Solo se muestra el árbol hasta encontrar la primera solución válida
URJC DAA 30 / 67
Backtracking N reinas
Árbol de búsqueda podado para N = 4
1 4
2 3
2 4 1 4 1 4 1 3
3 3 2 2
3 4 2 4 2 3 3 4 1 4 1 3 2 4 1 4 1 2 2 3 1 3 1 2
4 3 4 2 3 2 4 3 4 1 3 1 4 2 4 1 2 1 3 2 3 1 2 1
solución parcial válida solución completa válida
solución no válida nodo no explorado
URJC DAA 31 / 67
Backtracking N reinas
Implementación
f c-f+n-1 c+f
0
1 6 0
2 5 1
3 4 2
0 1 2 3 3 4 5 6
diagonales diagonales
filas
principales secundarias
Para verificar que una solución parcial es válida usamos:
f: filas libres
dp: diagonales principales libres (columna − fila + n − 1)
ds: diagonales secundarias libres (columna + fila)
c: solución parcial
URJC DAA 32 / 67
Backtracking N reinas
Implementación - I
1 void n_reinas(int n){
2 int[] c = new int[n];
3
4 boolean[] f = new boolean[n];
5 for(int i=0; i<n; i++)
6 f[i] = true;
7
8 boolean[] dp = new boolean[2*n-1];
9 for(int i=0; i<2*n-1; i++)
10 dp[i] = true;
11
12 boolean[] ds = new boolean[2*n-1];
13 for(int i=0; i<2*n-1; i++)
14 ds[i] = true;
15
16 buscarReinas(n, 0, c, f, dp, ds);
17 }
URJC DAA 33 / 67
Backtracking N reinas
Implementación - II
1 void buscarReinas(int n, int i, int[] solucion,
2 boolean[] f, boolean[] dp, boolean[] ds){
3 for(int j=0; j<n; j++)
4 if(f[j] && dp[i-j+n-1] && ds[i+j]){
5 solucion[i] = j;
6
7 f[j] = false;
8 dp[i-j+n-1] = false;
9 ds[i+j] = false;
10
11 if(i==n-1)
12 imprimir(solucion);
13 else
14 buscarReinas(n, i+1, solucion, f, dp, ds);
15
16 f[j] = true;
17 dp[i-j+n-1] = true;
18 ds[i+j] = true;
19 }
20 }
URJC DAA 34 / 67
Backtracking N reinas
Implementación - III
Lı́nea 3: se generan los candidatos
Lı́nea 4: se comprueba la validez del candidato
Lı́neas 5 − 9: se incluye el candidato en la solución, y se actualizan las
estructuras de datos
Lı́nea 12: si se ha llegado a una solución válida se imprime
Lı́nea 14: en caso contrario se sigue buscando
Lı́neas 16 − 18: se borra el candidato de la solución, y se actualizan
las estructuras de datos
Al modificar las estructuras de datos no es necesario modificar el vector
que contiene la solución parcial
URJC DAA 35 / 67
Backtracking Otros problemas
Otros problemas
URJC DAA 36 / 67
Backtracking Otros problemas
Salto de caballo
No es requisito que pueda volver al punto de partida
1 14 9 20 23
10 19 22 15 8
5 2 13 24 21
18 11 4 7 16
3 6 17 12 25
Variante: ciclo Hamiltoniano (camino cerrado en un grafo)
URJC DAA 37 / 67
Backtracking Otros problemas
Salto de caballo
Árbol de búsqueda
0 1 2 3 4
(fila = 3, columna = 3)
(1, 2) (2, 1) (4, 1) (5, 2) (5, 4) (4, 5) (2, 5) (1, 4)
4
casilla origen casilla ocupada casilla libre fuera del tablero
Se puede encontrar una permutación de n2 elementos (tablero de n × n)
Se va a seguir otra estrategia de backtracking, aunque la solución es similar
Para tamaños mayores que 6 × 6 se puede tardar bastante en hallar una solución
URJC DAA 38 / 67
Backtracking Otros problemas
Implementación - I
1 boolean saltoCaballo(int f, int c, int lado){
2 int[][] tablero = new int[lado][lado];
3 int[] incrX = new int[] {-1, 1, 2, 2, 1, -1, -2, -2};
4 int[] incrY = new int[] {-2, -2, -1, 1, 2, 2, 1, -1};
5
6 tablero[f][c] = 1;
7 boolean hay = buscar([Link]*[Link],
8 [Link], 2, f, c, tablero, incrX, incrY);
9 if(hay)
10 imprimir(tablero);
11 return hay;
12 }
13
14 void imprimir(int[][] tablero){
15 for(int i=0; i<[Link]; i++){
16 for(int j=0; j<[Link]; j++)
17 [Link](((tablero[i][j]<10)?" ":"")+tablero[i][j]+" ");
18
19 [Link]();
20 }
21 }
URJC DAA 39 / 67
Backtracking Otros problemas
Implementación - II
1 boolean buscar(int n2, int n, int i, int f, int c,
2 int[][] recorrido, int[] incrX, int[] incrY){
3 boolean exito = false;
4 for(int k=0; k<8 && !exito; k++){
5 int nuevaF = f + incrY[k];
6 int nuevaC = c + incrX[k];
7 if(nuevaF>=0 && nuevaF<n && nuevaC>=0 && nuevaC<n)
8 if(recorrido[nuevaC][nuevaF] == 0){
9 recorrido[nuevaC][nuevaF] = i;
10 if(i==n2)
11 exito = true;
12 else{
13 exito = buscar (n2, n, i+1,
14 nuevaF, nuevaC, recorrido, incrX, incrY);
15 if(!exito)
16 recorrido[nuevaC][nuevaF] = 0;
17 }
18 }
19 }
20 return exito;
21 }
URJC DAA 40 / 67
Backtracking Otros problemas
Laberinto
laberinto solución
(0,0)
(n-1, n-1)
libre pared comienzo final camino
URJC DAA 41 / 67
Backtracking Otros problemas
Laberinto
Árbol de búsqueda
1,1
fila , columna
2,1 1,2
3,1 2,2
No se debe hallar una permutación (el camino puede tener duración variable)
No se debe hallar un subconjunto de celdas (el orden importa)
URJC DAA 42 / 67
Backtracking Otros problemas
Implementación - I
1 boolean hayCamino(char[][] laberinto){
2 int[] incrX = new int[] {1, 0, -1, 0};
3 int[] incrY = new int[] {0, 1, 0, -1};
4
5 laberinto[0][0] = ’C’;
6 boolean exito =
7 buscar([Link], 0, 0, laberinto, incrX, incrY);
8
9 imprimir(laberinto);
10 return exito;
11 }
12
13 void imprimir(char[][] laberinto){
14 for(int i=0; i<[Link]; i++){
15 for(int j=0; j<[Link]; j++)
16 [Link] (laberinto[i][j]+" ");
17
18 [Link]();
19 }
20 }
URJC DAA 43 / 67
Backtracking Otros problemas
Implementación - II
1 boolean buscar(int n, int x, int y, char[][] laberinto,
2 int[] incrX, int[] incrY){
3 boolean exito = false;
4 for(int k=0; k<4 && !exito; k++){
5 int coordX = x + incrX[k];
6 int coordY = y + incrY[k];
7 if(coordX>=0 && coordX<n && coordY>=0 && coordY<n)
8 if(laberinto[coordY][coordX] == ’ ’){
9 laberinto[coordY][coordX] = ’C’;
10 if (coordX==n-1 && coordY==n-1)
11 exito = true;
12 else{
13 exito = buscar(n, coordX, coordY, laberinto, incrX, incrY);
14 if(!exito)
15 laberinto[coordY][coordX] = ’ ’;
16 }
17 }
18 }
19 return exito;
20 }
URJC DAA 44 / 67
Backtracking Otros problemas
Suma de subconjuntos
Problema de la suma de subconjuntos
Dada una lista L de números no negativos y otro número s, determinar los
subconjuntos de L que suman s
13 = 6 + 7
13 = 2 + 5 + 6
1 2 3 5 6 7 9 13 13 = 1 + 5 + 7
13 = 1 + 3 + 9
L s 13 = 1 + 2 + 3 + 7
Solución con partes de conjuntos (de tamaño fijo)
La lista L puede tener elementos repetidos
URJC DAA 45 / 67
Backtracking Otros problemas
Suma de subconjuntos
L = h2, 7, 3, 5i, s=8
Se añade una condición para podar el árbol si la suma parcial es mayor que s
0
+2?
0 2
+7?
0 7 2 9
+3?
0 3 7 10 2 5
+5?
12 10
0 5 3 8 7 2 7 5
3+5
URJC DAA 46 / 67
Backtracking Otros problemas
Implementación - I
1 void main(String[] args){
2 int[] L = new int[] {1, 2, 3, 5, 6, 7, 9};
3
4 sumaSubconjuntos(L, 13);
5 }
6
7 void sumaSubconjuntos (int[] L, int s){
8 buscarSubconjs([Link], 0, 0, L, s, new boolean[[Link]]);
9 }
10
11 void imprimir(int[] L, boolean[] v){
12 for(int i=0; i<[Link]; i++)
13 if(v[i])
14 [Link](L[i]+" ");
15
16 [Link]();
17 }
18
URJC DAA 47 / 67
Backtracking Otros problemas
Implementación - II
1 void buscarSubconjs (int n, int i, int p,
2 int[] L, int s, boolean[] subconj){
3 for (int k=0; k<=1; k++){
4 int nuevoP = p + k*L[i];
5 if (nuevoP<=s){
6 if(k==0)
7 subconj[i]=false;
8 else
9 subconj[i]=true;
10
11 if(i==n-1){
12 if(nuevoP==s)
13 imprimir (L, subconj);
14 }
15 else
16 buscarSubconjs(n, i+1, nuevoP, L, s, subconj);
17 }
18 // nuevoP = p - k*L[i]; lo suprimimos por innecesario
19 }
20 }
URJC DAA 48 / 67
Backtracking Otros problemas
Problema de la mochila 0-1
Problema de la mochila 0-1
Dado un conjunto de n objetos, cada uno con un peso pi y un valor vi , i = 1, . . . , n, y
una mochila con capacidad C . Maximizar la suma de los valores asociados a los objetos
que se introducen en la mochila, sin sobrepasar la capacidad C , sabiendo que los objetos
NO pueden partirse en fracciones más pequeñas:
n
X
maximizar xi vi sujeto a xi ∈ {0, 1} i = 1, . . . , n
x i=1
n
X
xi pi ≤ C
i=1
p1
p2
p3
C
URJC DAA 49 / 67
Backtracking Otros problemas
Mochila 0-1
Árbol de búsqueda
pesos = (3, 6, 9, 5), beneficios = (7, 2, 10, 4), capacidad = 15
(15, 0)
0 1
(15, 0) (12, 7)
0 1 0 1
(15, 0) (9, 2) (12, 7) (6, 9)
0 1 0 1 0 1 0 1
(15, 0) (6, 10) (9, 2) (0, 12) (12, 7) (3, 17) (6, 9) (-3, 19)
0 1 0 1 0 1 0 1 0 1 0 1 0 1
(15, 0) (10, 4) (6, 10) (1, 14) (9, 2) (4, 6) (0, 12) (-5, 16) (12, 7) (7, 11) (3, 17) (-2, 21) (6, 9) (1, 13)
beneficio óptimo
0 ( X, Y ) 1 ( X, Y ) = ( capacidad restante, beneficio parcial )
( X, Y ) pi , bi ( X - pi , Y + bi ) ( X, Y ) capacidad sobrepasada
URJC DAA 50 / 67
Backtracking Otros problemas
Implementación - I
1 void main(String[] args){
2 int[] ps= new int[] {3, 6, 9, 5};
3 int[] bs= new int[] {7, 2, 10, 4};
4 mochila_0_1(ps,bs, 15);
5 }
6
7 int mochila_0_1(int[] ps, int[] bs, int c){
8 int[] solParcial = new int[[Link]];
9 int[] solOptima = new int[[Link]];
10 int bOpt = buscar01([Link], 0, c, 0, solParcial,
11 solOptima, -1, ps, bs, c);
12 imprimir(solOptima, bOpt);
13 return bOpt;
14 }
15
16 void imprimir(int[] v, int bOpt){
17 for (int i=0; i<[Link]; i++)
18 [Link](v[i]+" ");
19
20 [Link]("= "+bOpt);
21 }
URJC DAA 51 / 67
Backtracking Otros problemas
Implementación - II
1 int buscar01(int n, int i, int p, int b, int[] solParc,
2 int[] solOpt, int bOpt, int[] ps, int[] bs, int c){
3 for(int k=0; k<=1; k++){
4 if(k*ps[i]<=p){
5 solParc[i] = k;
6 int np = p - k*ps[i]; int nb = b + k*bs[i];
7 if(i==n-1){
8 if(nb>bOpt){
9 bOpt = nb;
10 for(int j=0; j<[Link]; j++)
11 solOpt[j] = solParc[j];
12 }
13 }
14 else
15 bOpt = buscar01(n, i+1, np, nb, solParc, solOpt, bOpt, ps, bs, c);
16 //int np = p + k*ps[i]; innecesaria
17 //int nb = b - k*bs[i]; innecesaria
18 }
19 }
20 return bOpt;
21 }
URJC DAA 52 / 67
Backtracking Ramificación y poda
Ramificación y poda
URJC DAA 53 / 67
Backtracking Ramificación y poda
Ramificación y poda
Hasta ahora podábamos el árbol de recursión si una solución parcial
no cumplı́a los requisitos del problema para llegar a ser una solución
completa
En problemas de optimización se pueden realizar podas adicionales:
Habiendo conseguido hallar una solución válida con valor
óptimo v , podemos descartar soluciones que nunca puedan
alcanzar dicho valor óptimo
Es un método exacto de resolución de problemas de optimización
Se aplica a problemas duros (desde un punto de vista computacional),
para acelerar la búsqueda del óptimo global
URJC DAA 54 / 67
Backtracking Ramificación y poda
Ramificación y poda: Mochila 0-1
pesos = (3, 6, 9, 5), beneficios = (7, 2, 10, 4), capacidad = 15
0 (23)
bs[1] -7 +7
0 (16) 7 (23)
bs[2] -2 +2 -2 +2
0 (14) 2 (16) 7 (21) 9 (23)
bs[3] - 10 + 10 - 10 + 10 - 10 + 10 - 10
0 (4) 10 (14) 2 (6) 12 (16) 7 (11) 17 (21) 9 (13)
bs[4] -4 +4 -4 +4 6 < 14 -4 11 < 14 -4 13 < 17
0 (0) 4 (4) 10 (10) 14 (14) 12 (12) 17 (17)
12 < 14
beneficio óptimo exceso de peso X ( Y ) = beneficio parcial ( máximo beneficio posible )
-Z X (Y) +Z
beneficio parcial óptimo poda X (Y-Z) X+Z (Y)
URJC DAA 55 / 67
Backtracking Ramificación y poda
Implementación - I
1 void main(String[] args){
2 int[] ps= new int[] {3, 6, 9, 5};
3 int[] bs= new int[] {7, 2, 10, 4};
4
5 mochila_0_1(ps,bs,15);
6 }
7
8 void mochila_0_1(int[] ps, int[] bs, int c){
9 int[] solParcial = new int[[Link]];
10 int[] solOptima = new int[[Link]];
11
12 int suma=0;
13 for(int i=0; i<[Link]; i++)
14 suma += bs[i];
15
16 int bOpt = buscar01poda([Link], 0, c, 0, suma, solParcial, solOptima, -1, ps, bs, c);
17 imprimir(solOptima, bOpt);
18 }
19
20 void imprimir(int[] v, int bOpt){
21 for(int i=0; i<[Link]; i++)
22 [Link](v[i]+" ");
23
24 [Link]("= "+bOpt);
25 }
URJC DAA 56 / 67
Backtracking Ramificación y poda
Implementación - II
1 int buscar01poda(int n, int i, int p, int b, int bmax, int[] solParc,
2 int[] solOpt, int bOpt, int[] ps, int[] bs, int c){
3
4 for(int k=0; k<=1; k++){
5 if(k*ps[i]<=p){
6 solParc[i] = k;
7 int np = p - k*ps[i];
8 int nb = b + k*bs[i];
9 int nbmax = bmax - (1-k)*bs[i];
10
11 if(i==n-1){
12 if(nb>bOpt){
13 bOpt = nb;
14 for(int j=0; j<[Link]; j++)
15 solOpt[j] = solParc[j];
16 }
17 }
18 else
19 if(nbmax>bOpt)
20 bOpt = buscar01poda(n, i+1, np, nb, nbmax, solParc, solOpt, bOpt, ps, bs, c);
21 }
22 }
23 return bOpt;
24 }
URJC DAA 57 / 67
Backtracking Poda alfa-beta
Poda alfa-beta
URJC DAA 58 / 67
Backtracking Poda alfa-beta
Poda alfa-beta
Algoritmo minimax
Se utiliza en juegos de estrategia entre adversarios (p.e., ajedrez)
Cada jugador toma turnos
Se evalúa una función numérica acerca de la situación
Jaque-mate a favor (∞), en contra (−∞), comer reina contraria (valor
muy alto), etc.
Un jugador trata de maximizar la función, el otro de minimizarla
Poda alfa-beta
Estrategia de búsqueda para reducir el número de nodos evaluados por
el algoritmo minimax
URJC DAA 59 / 67
Backtracking Poda alfa-beta
Poda alfa-beta
α = cota inferior de los máximos
β = cota superior de los mı́nimos
Se poda si β ≤ α
Se conocen valores heurı́sticos de los nodos hoja
a = alpha
1 máx
b = beta
a=1
b=1
1 -3 min
a=1
b=1 a=1
1 4 -3
b=INF
6 máx
a=1 a=1
b=0 b=-2
1 -3 2 4 -3 -4 6 -2 -5 mín
2 1 0 -3 3 2 4 5 -2 -3 6 7 8 máx
URJC DAA 60 / 67
Backtracking Poda alfa-beta
Pseudocódigo de la poda alfa-beta
1 alfabeta(nodo, n, a, b, jugador)
2 si (n=0) o si el nodo es una hoja
3 devuelve el valor heurı́stico del nodo
4 si jugador = jugadorMax
5 para cada hijo del nodo
6 a := max(a, alfabeta(hijo, n-1, a, b, not(jugador) ))
7 si b=a
8 break (* Poda Beta *)
9 devuelve a
10 en caso contrario
11 para cada hijo del nodo
12 b := min(b, alfabeta(hijo, n-1, a, b, not(jugador) ))
13 si b=a
14 break (* Poda Alfa *)
15 devuelve b
16
17 (* Llamada inicial *)
18 n := profundidad del árbol
19 alfabeta(nodo origen, n, -infinito, +infinito, jugadorMax)
URJC DAA 61 / 67
Backtracking Esquemas generales
Esquemas generales
URJC DAA 62 / 67
Backtracking Esquemas generales
Pasos a seguir para diseñar algoritmos
Diseño de la representación de la solución y del árbol de búsqueda
Deducción de las comprobaciones de validez a realizar en cada nodo a
partir de las restricciones globales de la solución
Diseño de estructuras de datos auxiliares para la generación de
candidatos o las comprobaciones de validez
Codificación con ayuda de los esquemas generales de código
URJC DAA 63 / 67
Backtracking Esquemas generales
Esquema para todas las soluciones - versión 1
1 void buscarTodas (int n, int i, Valor[ ] solucion){
2 for(int k=0; k<n; k++){
3 <<generar candidato k-ésimo>>;
4
5 if(<<candidato válido>>){
6 <<incluirlo en solucion >>;
7
8 if(i==n-1)
9 imprimirSolucion(solucion);
10 else
11 buscarTodas(n, i+1, solucion);
12
13 <<borrarlo de solucion >>;
14 }
15 }
16 }
Se ha usado, por ejemplo, en el problema de las N reinas
URJC DAA 64 / 67
Backtracking Esquemas generales
Esquema para todas las soluciones - versión 2
1 void buscarTodas(int n, int i, Valor[ ] solucion){
2 if(i==n)
3 imprimirSolucion(solucion);
4 else
5 for(int k=0; k<n; k++){
6 <<generar candidato k-ésimo>>;
7
8 if(<<candidato válido>>){
9 <<incluirlo en solucion>>;
10
11 buscarTodas(n, i+1, solucion);
12
13 <<borrarlo de solucion>>;
14 }
15 }
16 }
Esquema alternativo (ahorras comparaciones, se comprueba i==n una
sola vez)
URJC DAA 65 / 67
Backtracking Esquemas generales
Esquema para una solución
1 boolean buscarUna(int n, int i, Valor[ ] solucion){
2 boolean exito=false;
3 for(int k=0; k<n && !exito; k++){
4 <<generar candidato k-ésimo>>;
5 if (<<candidato válido>>){
6 <<incluirlo en solucion>>;
7
8 if(i==n-1)
9 exito = true;
10 else{
11 exito = buscarUna(n, i+1, solucion);
12 if (!exito)
13 <<borrarlo de solucion >>;
14 }
15 }
16 }
17 return exito;
18 }
URJC DAA 66 / 67
Backtracking Esquemas generales
Esquema para la solución óptima
1 int buscarOptima(int n, int i,
2 Valor[ ] solActual, int valorActual,
3 Valor[ ] solOptima, int valorOptimo){
4 int valor;
5 for(int k=0; k<n; k++){
6 <<generar candidato k-ésimo>>;
7 if(<<candidato válido>>){
8 <<incluirlo en solActual y actualizar valorActual>>;
9 if(i==n-1)
10 if(valorActual>valorOptimo){
11 <<solOptima = solActual>>
12 valorOptimo = valorActual;
13 }
14 else
15 valorOptimo = buscarOptima(n, i+1, solActual,
16 valorActual, solOptima, valorOptimo);
17 <<borrarlo de solActual y restaurar valorActual>>;
18 }
19 }
20 return valorOptimo;
21 }
URJC DAA 67 / 67