Backtracking

Modelado de soluciones como n-tuplas, exploración DFS, poda por factibilidad y ejemplo completo de N-Reinas.

Backtracking: idea central

Backtracking es una técnica de búsqueda sistemática. Construye una solución paso a paso, prueba candidatos y retrocede tan pronto detecta que la solución parcial ya no puede convertirse en una solución válida.

La exploración se hace en profundidad. Primero se intenta completar una rama del árbol de decisiones. Si la rama falla, se vuelve al nivel anterior y se prueba el siguiente candidato.

Idea esencial

Backtracking no evita que el problema sea combinatorio. Lo que hace es no perder tiempo completando combinaciones que ya son imposibles desde una etapa intermedia.

Backtracking vs fuerza bruta

CriterioFuerza brutaBacktracking
ConstrucciónGenera combinaciones completas.Construye soluciones parciales.
Momento de descarteFiltra después de generar.Poda antes de completar la rama.
Estructura típicaBucles anidados o generación completa.Recursión + validación + retroceso.

Caracterización de problemas resolubles por backtracking

Un problema encaja bien con backtracking cuando puede expresarse como una secuencia de decisiones. Cada decisión llena una posición de un vector solución.

Modelo de solución

X representa el vector solución final de tamaño n. Cada xi es la decisión tomada en la etapa i (por ejemplo, el número de columna), y Si es el conjunto de valores posibles para esa decisión.

Modelo de solución parcial

Xk es una solución incompleta que ya tiene valores asignados hasta la posición k. Los signos de interrogación (?) indican las decisiones que aún no se han tomado.

Tamaño del espacio de soluciones

El tamaño total del espacio de búsqueda |E| es el producto del número de opciones disponibles en cada nivel. Esto explica por qué el costo crece tan rápido al aumentar n.

Condiciones para aplicar backtracking

CondiciónSignificadoPregunta de control
Solución como n-tuplaLa solución se puede escribir como una secuencia de decisiones.¿Puedo representar una solución como ?
Conjuntos finitos de candidatosCada decisión toma valores desde un conjunto acotado.¿Cada sale de un conjunto finito ?
Restricciones parcialesSe puede detectar invalidez antes de completar toda la solución.¿Puedo podar una rama apenas viola una regla?
Árbol de decisionesCada nivel representa una posición o etapa del vector solución.¿Los hijos de un nodo son los candidatos para la siguiente decisión?
Decidir no es lo mismo que optimizar

Backtracking puede buscar una solución válida, todas las soluciones válidas o la mejor solución bajo un criterio. El modelo debe decir cuál de esas tres metas se persigue.

Modelo general de backtracking

En el nivel del árbol se intenta asignar un valor a . Si el candidato mantiene válida la solución parcial, se avanza al nivel . Si no, se descarta esa rama.

Modelo de candidatos

Ck es el conjunto de valores válidos para la posición k, condicionados por la solución parcial previa Xk-1.

Modelo de poda por factibilidad

Bk es el predicado de poda o factibilidad. Si es falso, la rama se corta inmediatamente porque se ha detectado que esa combinación no puede llevar a una solución válida.

Modelo de avance y retroceso

Este modelo muestra cómo el vector solución avanza si el candidato es factible (unión de estados) o se mantiene en el estado previo si el candidato debe ser descartado.

Modelo de solución completa

esSolucion indica que el proceso termina con éxito solo cuando llegamos al último nivel (n-1) y todas las restricciones del problema se cumplen.

Elements del modelo

ElementoRolEjemplo en N-Reinas
Nivel o etapa de decisión.Fila actual del tablero.
Valor elegido en la etapa k.Columna elegida para la reina de la fila k.
Conjunto de candidatos.
Predicado de factibilidad o poda.No compartir columna ni diagonal.

Esquema algorítmico

El patrón básico es recursivo: si la solución está completa, se reporta; si no, se prueban candidatos para la siguiente posición. Cada candidato válido abre una llamada recursiva. Al volver, se deshace la decisión.

Backtracking general
backtracking(k, X[n]) BEGIN
 IF (esSolucion(k, X)) THEN BEGIN
 reportar(X);
 RETURN 1;
 END

 total <- 0;

 FOR cada candidato v EN candidatos(k, X) DO BEGIN
 IF (factible(k, v, X)) THEN BEGIN
 X[k] <- v;
 total <- total + backtracking(k + 1, X);
 X[k] <- NULL;
 END
 END

 RETURN total;
END

Flujo de una llamada de backtracking

Renderizando diagrama...

Deshacer es obligatorio

Si no se deshace la decisión al volver de la llamada recursiva, el siguiente candidato se evalúa sobre un estado contaminado. Ese error rompe el algoritmo.

Ejemplo: N-Reinas — planteamiento

El problema de las N-Reinas consiste en ubicar reinas en un tablero sin que dos reinas se ataquen. Dos reinas se atacan si comparten fila, columna o diagonal.

Como no puede haber dos reinas en la misma fila, se coloca exactamente una reina por fila. Entonces la decisión de cada nivel ya no es escoger una casilla cualquiera del tablero, sino escoger la columna para la fila actual.

Modelo de solución

En este vector X, el índice i representa la fila del tablero y el valor xi representa la columna donde ubicamos la reina de esa fila.

Modelo de candidatos

Para cada fila, los candidatos Si son simplemente los números de columna del 1 al n.

Modelo de factibilidad

La función revisa que la columna c no se repita y que no haya ataques en diagonal. La fórmula |i-r| != |c-xr| asegura que la distancia en filas no sea igual a la distancia en columnas, lo cual caracteriza a las diagonales.

Modelo de solución completa

La condición i=n+1 significa que ya hemos logrado colocar una reina en cada una de las n filas del tablero.

Reducción del espacio de búsqueda

ModeloEspacio aproximadoComentario
Elegir cualquier casilla para cada reinaDemasiado grande y con muchas combinaciones obviamente inválidas.
Una reina por filaCada nivel decide una columna.
Una reina por fila y sin repetir columnasLa poda por columna reduce drásticamente el árbol.

N-Reinas 4×4 — seguimiento paso a paso

Para una solución puede representarse como . Esto significa: fila 1 columna 2, fila 2 columna 4, fila 3 columna 1 y fila 4 columna 3.

Tablero solución para X = (2,4,1,3)

FilaCol 1Col 2Col 3Col 4
1·Q··
2···Q
3Q···
4··Q·

Seguimiento hasta encontrar X = (2,4,1,3)

PasoFilaColumna probada¿Válida?Acción
111SíColocar reina y avanzar.
221NoMisma columna. Probar siguiente.
322NoDiagonal. Probar siguiente.
423SíColocar reina y avanzar.
531,2,3,4NoTodas fallan por columna o diagonal. Retroceder.
624SíReemplazar columna 3 por 4 y avanzar.
731,2,3,4NoTodas fallan. Retroceder a fila 1.
812SíNueva rama: colocar en fila 1 columna 2.
924SíColocar y avanzar.
1031SíColocar y avanzar.
1143SíSolución encontrada.

Fragmento del árbol de decisiones

Renderizando diagrama...

Resultado

Una solución válida para el tablero 4×4 es . Ninguna pareja de reinas comparte columna ni diagonal.

N-Reinas — algoritmo

Condición de validez

Este modelo unifica las restricciones: la reina actual no debe compartir columna (col != X[r]) ni diagonal (distancia absoluta de filas != distancia absoluta de columnas) con ninguna reina colocada previamente.

N-Reinas con backtracking
nReinas(n) BEGIN
 X[1..n] <- NULL;
 RETURN ubicarReina(1, n, X);
END

ubicarReina(fila, n, X[n]) BEGIN
 IF (fila = n + 1) THEN BEGIN
 reportar(X);
 RETURN 1;
 END

 total <- 0;

 FOR col <- 1 TO n DO BEGIN
 IF (esValida(fila, col, X)) THEN BEGIN
 X[fila] <- col;
 total <- total + ubicarReina(fila + 1, n, X);
 X[fila] <- NULL;
 END
 END

 RETURN total;
END
Validación de una posición
esValida(fila, col, X[n]) BEGIN
 FOR r <- 1 TO fila - 1 DO BEGIN
 IF (X[r] = col) THEN BEGIN
 RETURN F;
 END

 IF (abs(fila - r) = abs(col - X[r])) THEN BEGIN
 RETURN F;
 END
 END

 RETURN T;
END

Reglas de validación

ReglaModeloSignificado
No misma columnaDos reinas en la misma columna se atacan.
No misma diagonalEn una diagonal, la diferencia de filas coincide con la diferencia de columnas.

Complejidad de backtracking

La complejidad de backtracking depende del número de nodos visitados en el árbol de búsqueda y del costo de validar cada candidato. La poda puede reducir mucho el trabajo real, pero el peor caso sigue siendo combinatorio.

Modelo general de costo

Nk es el número de nodos visitados en el nivel k del árbol y Cvalidar es el costo de ejecutar la función de factibilidad en ese nivel.

Peor caso con factor de ramificación uniforme

En el peor caso, b es el factor de ramificación (promedio de hijos por nodo) y d es la profundidad del árbol. La poda busca que los nodos visitados sean mucho menos que el total teórico bd.

Cota típica para N-Reinas

El costo n! aparece cuando evitamos repetir columnas. Si también validamos en tiempo constante O(1), el costo se reduce simplemente al número de nodos del árbol factorial.

Factores que afectan el costo

FactorEfectoEjemplo
ProfundidadNúmero de decisiones que deben tomarse.En N-Reinas, profundidad .
RamificaciónNúmero de candidatos por decisión.Columnas posibles por fila.
PodaReduce ramas exploradas.Descartar columnas y diagonales atacadas.
Costo de validarMultiplica cada nodo visitado. con escaneo, con arreglos auxiliares.
La poda no cambia automáticamente el peor caso

Que el algoritmo pode muchas ramas en ejemplos pequeños no significa que el peor caso deje de ser exponencial o factorial. Para cambiar la cota hay que justificar formalmente cuántos nodos se eliminan.

Optimización de validación y poda

Aunque el árbol de búsqueda siga siendo grande, se puede mejorar mucho el tiempo real reduciendo el costo de validar candidatos y ordenando los candidatos de forma más útil.

Validación O(1) en N-Reinas

Mediante arreglos booleanos para columnas y diagonales (identificadas por fila+col y fila-col), podemos saber si una casilla está amenazada en tiempo constante O(1), sin necesidad de recorrer las reinas anteriores.

Optimizaciones frecuentes

OptimizaciónIdeaEfecto
Arreglos de columnasGuardar qué columnas ya están ocupadas.Evita escanear reinas previas para columnas.
Arreglos de diagonalesGuardar diagonales ocupadas por índices y .Validación de diagonales en tiempo constante.
Orden de candidatosProbar primero candidatos más prometedores.Puede encontrar una solución antes, aunque no mejora necesariamente el peor caso.
Poda por límiteDetectar que ni completando todos los pasos restantes se alcanza la meta.Útil en suma de subconjuntos y problemas de selección.
N-Reinas con validación O(1)
ubicarReinaRapido(fila, n, X[n], colUsada[n], diagA[2*n], diagB[2*n]) BEGIN
 IF (fila = n + 1) THEN BEGIN
 reportar(X);
 RETURN 1;
 END

 total <- 0;

 FOR col <- 1 TO n DO BEGIN
 dA <- fila - col + n;
 dB <- fila + col;

 IF (NOT colUsada[col] AND NOT diagA[dA] AND NOT diagB[dB]) THEN BEGIN
 X[fila] <- col;
 colUsada[col] <- T;
 diagA[dA] <- T;
 diagB[dB] <- T;

 total <- total + ubicarReinaRapido(fila + 1, n, X, colUsada, diagA, diagB);

 X[fila] <- NULL;
 colUsada[col] <- F;
 diagA[dA] <- F;
 diagB[dB] <- F;
 END
 END

 RETURN total;
END

Problemas clásicos de backtracking

Backtracking aparece cuando hay que tomar una secuencia de decisiones bajo restricciones. La forma cambia, pero el patrón de solución parcial y poda se repite.

Familias típicas

ProblemaVector soluciónCandidatosPoda principal
N-ReinasColumnas del tablero.No misma columna ni diagonal.
Suma de subconjuntosTomar o no tomar cada elemento.La suma parcial no debe exceder el objetivo si todos los números son positivos.
Coloreo de grafosColores disponibles.Vértices adyacentes no pueden compartir color.
SudokuDígitos posibles.No repetir en fila, columna ni subcuadro.
Permutaciones restringidasElementos no usados.Restricciones específicas del problema.

Backtracking vs fuerza bruta vs voraces

Backtracking está entre dos extremos: no es tan ciego como fuerza bruta, pero tampoco se compromete irrevocablemente como un voraz.

Comparación de técnicas

CriterioFuerza brutaBacktrackingVoraz
ConstrucciónGenera todo.Construye y poda.Elige localmente.
RetrocesoNo aplica.Sí.No.
Poda tempranaNo.Sí, por factibilidad.No explora alternativas descartadas.
GarantíaCompleta si enumera todo.Completa si la poda es correcta.Solo si se demuestra propiedad voraz.
Costo típicoCombinatorio completo.Combinatorio con poda.Usualmente polinómico.

Selección de técnica

Renderizando diagrama...

Errores típicos

Errores frecuentes en backtracking

ErrorPor qué está malCorrección
No deshacer la decisiónEl siguiente candidato hereda un estado que no le corresponde.Después de la llamada recursiva, restaurar la posición o estructura modificada.
Validar solo al finalEso convierte el algoritmo en fuerza bruta.Validar cada solución parcial antes de avanzar.
Podar una rama que sí podía llegar a soluciónLa poda incorrecta vuelve incompleto el algoritmo.La poda debe ser una condición necesaria de invalidez, no una intuición.
Confundir backtracking con vorazEl voraz no reconsidera; backtracking sí retrocede.Si hay deshacer y explorar alternativas, es backtracking.
Analizar solo el camino exitosoEl costo incluye candidatos fallidos y ramas podadas visitadas.Analizar nodos visitados del árbol completo, no solo la solución final.
Decir que la poda hace el algoritmo polinómicoLa poda mejora casos reales, pero no necesariamente cambia el peor caso.Justificar formalmente cualquier mejora de cota.

Resumen operativo

Receta mínima
  1. Define la solución como una n-tupla.
  2. Define qué representa cada posición de la tupla.
  3. Define los candidatos de cada nivel.
  4. Define el predicado de factibilidad.
  5. Define cuándo una solución está completa.
  6. Explora en profundidad usando recursión.
  7. Antes de avanzar, valida la solución parcial.
  8. Después de volver de la recursión, deshaz la decisión.
  9. Analiza el costo como nodos visitados por costo de validación.

Modelos clave

Solución como tupla

Cada posición del vector representa una decisión.

Solución parcial

La solución todavía no está completa, pero ya puede validarse parcialmente.

Poda por factibilidad

Si el predicado es falso, la rama se corta.

Costo general

El costo depende de cuántos nodos se visitan en cada nivel y cuánto cuesta validarlos.