Backtracking PDF
Backtracking PDF
Backtracking PDF
Resumen
Esta es una técnica fácil de implementar que permite diseñar algoritmos
para resolver problemas de búsqueda y optimización.
1. Motivación
Hasta este punto del curso hemos visto técnicas de análisis de algoritmos que
nos permiten hacer el análisis en el caso mejor, peor y el promedio. Para esto
aplicamos conteo de instrucciones, planteamiento y solución de recurrencias, y
análisis probabilístico del valor esperado de la complejidad por medio de variables
aleatorias indicadoras.
Ahora empezaremos a ver técnicas de diseño de algoritmos. Estas son mane-
ras de conceptualizar y resolver muchos problemas de acuerdo a su estructura.
Vamos a empezar con la técnica de backtracking (vuelta atrás), que, sorprenden-
temente resulta fácil de implementar. Para ilustrar ésta técnica vamos a resolver el
problema de situar N Reinas en un tablero de ajedrez de N filas y N columnas, sin
que se ataquen entre si.
2. N-Reinas
Las reinas en el ajedrez se pueden atacar horizontalmente, verticalmente y
en diagonal. El siguiente tablero ilustra una solución al problema de N-Reinas
cuando n=8:
1
La pregunta que planteamos es ¿Como solucionar este problema por medio de
un algoritmo?. Nada de lo que hemos visto hasta ahora (técnicas de análisis de
algoritmos) nos sirve para solucionar este tipo de problemas.
Lo que necesitamos es técnicas de diseño, que permitan construir algoritmos
Para situar las N reinas en el tablero, parece que necesitamos buscar en un con-
junto grande de alternativas a solución, cuales respetan la condición del problema
de que no se ataquen las reinas.
¿Cuantas alternativas tenemos que explorar?. Por conteo, tenemos 64 casillas
en un tablero de ajedrez de 8 × 8, y 8 reinas que ubicar en posiciones distintas, asi
que hay 64 8 alternativas de solución (tableros con las 8 reinas situadas en posi-
ciones distintas), esto es, 4.426.165.368. Al número de alternativas que tenemos
que considerar se le denomina tamaño del espacio de búsqueda, y siempre que
tengamos un problema de búsqueda como éste de las N-Reinas es conveniente
estimarlo mediante técnicas de conteo.
2
columna 4 2 5 8 6 1 3 7
fila 1 2 3 4 5 6 7 8
REINAS ()
for a ← 1 to 8
do for b ← 1 to 8
do for c ← 1 to 8
...
do for g ← 1 to 8
do for h ← 1 to 8
A ← (a, b, c, . . . , g, h)
if SOLUCION(A)
then print A
Aquí a contendrá la columna en la que hay que ubicar la primera reina, c con-
tendrá la columna en la que hay que ubcar la segunda reina, y asi sucesivamente
3
hasta h. Todo lo que resta es diseñar un algoritmo que chequee si un arreglo que
contenga los valores (a, b, c, d, e, f , g, h) representa una solución válida (las rei-
nas no se atacan entre si). Si generalizamos para un tablero de cualquier tamaño,
obtenemos el siguiente algoritmo, cuya complejidad es Θ(nn ):
REINAS (n)
for a ← 1 to n
do for b ← 1 to n
do for c ← 1 to n
...
do for g ← 1 to n
do for h ← 1 to n
A ← (a, b, c, . . . , g, h)
if SOLUCION(A)
then print A
A[i] 6= A[ j] i 6= j i, j ∈ [1, 8]
|A[i] − A[ j]| 6= |i − j| i =
6 j i, j ∈ [1, 8]
4
SOLUCION (A)
for i ← 1 to 7
do for j ← i + 1 to 8
do if A[ j] = A[i] or |A[ j] − A[i]| = |i − j|
return FALSE
return TRUE
SOLUCION (A)
for i ← 1 to n − 1
do for j ← i + 1 to n
do if A[ j] = A[i] or |A[ j] − A[i]| = |i − j|
return FALSE
return TRUE
REINAS ()
A ← permutacion_inicial
while A 6= permutacion_inicial
do A ← siguiente_permutacion
if SOLUCION(A)
then print A
5
Que nos plantea una pregunta natural¿Como permutar un arreglo? La solución
consiste en pensar recursivamente. Por ejemplo, permutar:
a b c
1 2 3
a b c b a c c a b
1 2 3 2 1 3 3 1 2
a c b b c a c b a
1 3 2 2 3 1 3 2 1
...
Esta observación nos permite explicar el siguiente algoritmo para hallar una per-
mutación del arreglo A, con tamaño n:
PERM (i,n,A)
if i = n
then print A
else
for j ← i to n
do exchange A[i] ↔ A[ j]
PERM (i + 1, n, A)
exchange A[i] ↔ A[ j]
6
Aquí i lleva la posición en el arreglo del elemento que se va a dejar fijo para
permutar el resto del arreglo. El intercambio antes del llamado recursivo de perm
nos permite colocar todos los elementos de A en la posición i a medida que el ciclo
for avanza. El llamado recursivo permuta el resto del arreglo desde la posición i+1
y el último intercambio restablece el orden que el arreglo tenía antes de empezar
la siguiente iteración del for. La complejidad de este algoritmo puede calcularse
con la recurrencia:
T (n) = nT (n − 1) + Θ(1)
REINAS ()
A ← permutacion_inicial
i←0
while A 6= permutacion_inicial
do i ← i + 1
PERM (i, n, A)
if solucion(A)
then print A
Ingenua
Basada en permutaciones
Tienen el mismo defecto, solo prueban la validez de las posiciones cuando se han
colocado todas las n reinas!. La primera idea que se puede tener es hacer una
revisión parcial del arreglo A para evitar alternativas que tengan inconsistencias
antes de situar todas las n reinas. Esto puede especificarse como garantizar que
las reinas en las posiciones (1, A[1]), (2, A[2]), . . . , (k, A[k]) no se atacan entre sí.
Y donde el número k puede ser menor que n. Esta condición puede escribirse
matemáticamente así:
7
A[i] 6= A[ j] i 6= j i, j ∈ [1, k]
|A[i] − A[ j]| 6= |i − j| i =
6 j i, j ∈ [1, k]
PROMETEDOR (A,k)
PRE: A ya es k-1 prometedor
for j ← 1 to k − 1
do if A[ j] = A[k] or |A[ j] − A[k]| = |i − k|
return FALSE
return T RUE
Observe que el algoritmo asume que el arreglo A ya era k-1 prometedor como
precondición, esto permite bajar la complejidad que tenía solucion de Θ(n2 ) a
Θ(k). Con esta herramienta podemos plantear la búsqueda de soluciones de otra
forma:
8
REINAS (A,k,n)
if k = n
then if PROMETEDOR(A,k)
Se encontró la solución!
then print A
else return
else
for j ← 1 to n
do if PROMETEDOR(A,k)
then A[k + 1] ← j
REINAS (A, k + 1, n)
d e f r e i n a s (A, k , n ) :
global c
c = c +1
i f k==n :
i f p r o m e t e d o r (A, k ) :
print A
e l s e : return
else :
for j in range (1 , n +1):
i f p r o m e t e d o r (A, k ) :
9
A[ k +1]= j
r e i n a s (A, k +1 , n )
L = 9∗[0]
r e i n a s (L,0 ,8)
print c
Con la siguiente implementación de la función prometedor:
d e f p r o m e t e d o r (A, k ) :
#PRE : A ya e s k−1 p r o m e t e d o r
for j in range (1 , k ) :
i f A[ j ]==A[ k ] or a b s (A[ j ]−A[ k ] ) = = a b s ( k−j ) :
return False
return True
3. Algoritmos ingenuos
Como vimos en la sección 2.2, una forma viable de resolver un problema de
búsqueda consiste en explorar sistematicamente todos los valores que se le pueden
asignar a las variables. Esto puede generalizarse a cualquier problema de búsqueda
que tenga:
n variables: v1 , v2 , . . . , vn
10
INGENUO ()
foreach e1 in D1
do foreach e2 in D2
do foreach e3 in D3
...
do foreach en in Dn
A ← (e1 , e2 , . . . , en )
if SOLUCION(A)
then print A
4. Backtracking
Como en la sección anterior, cualquier problema de n variables con n dominios
puede codificarse en un arreglo A de n elementos
A=
1 2 3 4 ... n
En las que en en A[k] se puede poner un valor del dominio Dk asignado para
la variable k. Decimos que el arreglo A es k-prometedor si desde la posición 1
hasta la k tiene asinaciones de valores para las k primeras variables que no violan
las condiciones del problema.
A[1] A[2] A[3] ... A[k]
A=
1 2 3 ... k
Bactracking significa ir aumentando el k en cada paso hasta llegar a n y asignar
un valor posible para la última variable. Si esto no tiene éxito, hay que dar vuelta
atrás (backtrack) en un árbol de búsqueda implícito.
11
INGENUO ()
foreach e1 in D1
do foreach e2 in D2
do foreach e3 in D3
...
do foreach en in Dn
A ← (e1 , e2 , . . . , en )
if solucion(A)
then print A
12
BACKTRACK (A,k,n)
k=1
while k > 0 and not ULTIMAO PCION(k)
do
while not ULTIMAO PCION(A,k) and PROMETEDOR(A,k)
do A[k] ← PROXIMO E LEMENTO(A, k)
if k=n and PROMETEDOR(A,k)
then print A
else k ← k + 1
Vuelta atrás al nivel superior (Backtrack)
k ← k−1
ULTIMAO PCION
PROMETEDOR
PROXIMO E LEMENTO
Cada una de estas debe pensarse para el problema específico que se este resolvien-
do. En el libro de Skiena [2] hay buenos ejemplos desarrollados de aplicación de
backtracking, en el libro de Brassard encontrará una explicación de backtracking
como técnica de exploración de arboles implícitos. [1]
Referencias
[1] Gilles Brassard and Paul Bratley. Algorithmics: theory & practice. Prentice-
Hall, Inc., Upper Saddle River, NJ, USA, 1988.
[2] Steven S. Skiena. The algorithm design manual. Springer-Verlag New York,
Inc., New York, NY, USA, 1998.
13