Ocho reinas y cuadros mágicos
Pregunta central
¿Cómo se construyen soluciones combinatorias mediante backtracking y cuándo conviene reemplazar la exploración exhaustiva por una regla de construcción?
Mapa de la clase
flowchart TB P[Problema combinatorio] --> R[Definir reglas] R --> E[Representar una solución parcial] E --> D{¿Cómo obtener soluciones?} D -->|Explorar alternativas| B[Backtracking] D -->|Reconocer regularidad| T[Construcción por patrones] B --> Q[Ocho reinas] B --> M1[Enumerar cuadros mágicos pequeños] T --> M2[Construir cuadros mágicos grandes] Q --> C[Restricciones: columna y diagonales] M1 --> S[Restricciones: números y sumas] M2 --> O[Orden impar o doblemente par]
La clase continúa backtracking, visto en la sesión anterior, y lo aterriza en dos problemas. En ambos se construye una solución parcial colocando una pieza o un número. La diferencia importante es que las ocho reinas se resuelven naturalmente explorando un árbol con poda, mientras que ciertos cuadros mágicos admiten patrones que producen una solución directamente.
1. Esquema general de backtracking
Backtracking recorre en profundidad un árbol de decisiones. En cada nivel:
- elige un candidato;
- comprueba si conserva las restricciones;
- avanza si el estado parcial sigue siendo válido;
- registra el estado si ya es una solución completa;
- deshace la elección para probar la siguiente alternativa.
flowchart TD A[Estado parcial] --> B{¿Está completo?} B -->|Sí| C[Registrar solución] B -->|No| D[Tomar siguiente candidato] D --> E{¿Es válido?} E -->|No| D E -->|Sí| F[Aplicar decisión] F --> G[Llamada recursiva] G --> H[Deshacer decisión] H --> D D -->|No quedan candidatos| I[Volver al nivel anterior] C --> I
buscar(estado, nivel):
si estado está completo:
registrar(estado)
regresar
para cada candidato del nivel:
si candidato es compatible con estado:
aplicar(candidato)
buscar(estado, nivel + 1)
deshacer(candidato)Important
La validez se comprueba sobre la solución parcial, no solo al final. Rechazar temprano una rama imposible es la poda del espacio de búsqueda.
2. Problema de las ocho reinas
Enunciado y objetivo
Se deben colocar ocho reinas en un tablero de sin que ninguna ataque a otra. Una reina ataca a cualquier distancia en su fila, columna y diagonales.
La presentación distingue tres preguntas:
- Primera solución: detener la búsqueda al encontrar una configuración válida.
- Todas las soluciones: registrar una solución y continuar.
- Mejor solución: exigiría definir primero un criterio de calidad; el enunciado clásico solo distingue configuraciones válidas e inválidas.
El programa visto en clase enumera 92 soluciones. Si se consideran equivalentes las configuraciones obtenidas mediante las simetrías del cuadrado —rotaciones y reflexiones— quedan 12 soluciones fundamentales.
Note
La diapositiva menciona “rotaciones y traslaciones”. En un tablero fijo, la equivalencia habitual de las 92 soluciones usa rotaciones y reflexiones; una traslación generalmente saca piezas del tablero o cambia la configuración de una forma que no es una simetría del cuadrado.

Reducción del problema: una reina por fila
En vez de probar 64 casillas para cada reina, el algoritmo fija una reina en cada fila. En el nivel recursivo solo decide su columna . Así, dos reinas nunca comparten fila por construcción.
flowchart TD F0[Fila 0: elegir columna] --> F1[Fila 1: elegir columna segura] F1 --> F2[Fila 2: elegir columna segura] F2 --> DOT[...] DOT --> F7[Fila 7: elegir columna segura] F7 --> SOL[Configuración completa] F2 -. sin columna válida .-> BT1[Retroceder a fila 1] BT1 -. probar otra columna .-> F2
La solución se representa con un vector:
donde es la fila y la columna elegida. El código usa filas internas y columnas .
Cómo detectar ataques en tiempo constante conceptual
Para una casilla basta vigilar tres identificadores:
| Restricción | Identificador guardado | Razón |
|---|---|---|
| columna | dos reinas se atacan si comparten columna | |
| diagonal de | la diferencia es constante sobre esa diagonal | |
| diagonal de | la suma es constante sobre esa diagonal |
flowchart LR X[Probar casilla k,j] --> C{¿j está en col?} C -->|Sí| R[Rechazar] C -->|No| D1{¿j-k está en diag45?} D1 -->|Sí| R D1 -->|No| D2{¿j+k está en diag135?} D2 -->|Sí| R D2 -->|No| A[Aceptar y bajar a k+1]
Ejemplo: y comparten diagonal porque . En cambio, y comparten la otra diagonal porque .
Traza paso a paso del estado
sequenceDiagram participant R as reinas(k) participant S as sol participant U as col/diagonales R->>R: probar columna j R->>U: comprobar j, j-k y j+k alt casilla atacada U-->>R: inválida R->>R: probar siguiente j else casilla segura R->>S: sol[k] = j R->>U: push_back de 3 identificadores R->>R: reinas(k+1, ...) R->>U: pop_back de 3 identificadores Note over R,U: retroceso: restaurar el estado end
Un fragmento del árbol deja ver la poda. Los números son columnas:
graph TD A[Inicio] --> B[F0 = 1] B --> C[F1 = 3] B --> D[F1 = 4] C --> E[F2 = 5] C --> F[F2 = 6] E --> X[... rama sin salida] X -. retroceso .-> C D --> G[F2 = 2] D --> H[F2 = 6] G --> Y[...] H --> Z[...]
No se generan hijos para columnas o diagonales ocupadas: esos subárboles se eliminan antes de explorarlos.
Código visto en clase
El archivo se llama Ocho reinas.c, pero contiene C++: usa iostream, vector, algorithm y cout. Por ello debe compilarse como C++.
#include <iostream>
#include <sstream>
#include <cstdio>
#include <vector>
#include <algorithm>
#define NREINAS 8
using namespace std;
vector<int> sol;
int nro_sol = 1;
inline bool contiene(const vector<int>& v, const int val)
{
return find(v.begin(), v.end(), val) != v.end();
}
void reinas(int k, vector<int> col,
vector<int> diag45, vector<int> diag135)
{
if (k == NREINAS)
{
printf("%3d:", nro_sol++);
for (int j = 0; j < NREINAS; j++)
cout << " (" << j + 1 << "," << sol[j] << ")";
cout << endl;
}
else
{
for (int j = 1; j <= NREINAS; j++)
if (!contiene(col, j) &&
!contiene(diag45, j - k) &&
!contiene(diag135, j + k))
{
sol[k] = j;
col.push_back(j);
diag45.push_back(j - k);
diag135.push_back(j + k);
reinas(k + 1, col, diag45, diag135);
col.pop_back();
diag45.pop_back();
diag135.pop_back();
}
}
}
int main()
{
cout << "SOLUCIONES AL PROBLEMA DE LAS "
<< NREINAS << " REINAS";
sol.resize(NREINAS);
reinas(0, vector<int>(), vector<int>(), vector<int>());
return 0;
}Lectura del programa
flowchart TD M[main] --> RES[Reservar sol para 8 entradas] RES --> CALL[reinas 0, conjuntos vacíos] CALL --> BASE{¿k = 8?} BASE -->|Sí| PRINT[Imprimir solución] BASE -->|No| LOOP[Recorrer j de 1 a 8] LOOP --> SAFE{¿Columna y diagonales libres?} SAFE -->|No| LOOP SAFE -->|Sí| PUT[Guardar sol k = j] PUT --> PUSH[Agregar restricciones] PUSH --> REC[reinas k+1] REC --> POP[Quitar restricciones] POP --> LOOP
contienecomprueba si un identificador ya está ocupado.k == NREINASsignifica que se colocaron las ocho reinas.- Los vectores de restricciones se pasan por valor. Cada llamada recibe copias; aun así el código hace
pop_back()al volver. Esospop_back()expresan el retroceso, aunque no son necesarios para proteger al nivel llamador porque las copias son locales. solsí es global y se modifica por nivel. No necesita “borrarse”: al explorar otra rama, cada posición relevante se sobrescribe antes de imprimir una nueva solución completa.findhace una búsqueda lineal. Para es suficiente; para grande podrían usarse arreglos booleanos o conjuntos para consultar ocupación en esperado.
Costo
La estrategia de una reina por fila parte de hasta asignaciones, pero la restricción de columnas reduce el universo a permutaciones y las diagonales podan muchas más ramas. Para reinas, el tiempo sigue siendo exponencial en el peor caso; la memoria de la búsqueda es , sin contar las soluciones almacenadas o impresas.
3. Cuadros mágicos
Definición
Un cuadro mágico normal de orden coloca una vez cada entero de a en una matriz , de modo que todas las filas, columnas y las dos diagonales principales sumen lo mismo.
La constante mágica se deduce sin buscar:
Como las filas tienen suma común ,
| Orden | Constante |
|---|---|
| 1 | 1 |
| 3 | 15 |
| 4 | 34 |
| 5 | 65 |
| 6 | 111 |
| 7 | 175 |
| 8 | 260 |
| 9 | 369 |
Formulación como backtracking
Una solución parcial coloca un número no usado en una casilla vacía. La búsqueda debe impedir números repetidos y podar cuando una suma parcial supera o cuando una línea completa no suma exactamente .
flowchart TD A[Elegir siguiente casilla] --> B[Probar número no usado] B --> C[Actualizar sumas parciales] C --> D{¿Alguna suma supera M?} D -->|Sí| X[Deshacer y probar otro número] D -->|No| E{¿Línea completa suma M?} E -->|No| X E -->|Sí o aún incompleta| F{¿Tablero completo?} F -->|No| A F -->|Sí| G[Registrar cuadro mágico] G --> X
Este enfoque sirve para enumerar soluciones, pero crece con enorme rapidez. La presentación señala como tarea escribir un programa imperativo que muestre todos los cuadros de orden leído por el programa, limitándose en la práctica a o por el tiempo de ejecución.
Para orden 3 existe una solución fundamental, cuyas rotaciones y reflexiones producen las demás orientaciones. La presentación reporta 880 cuadros de orden 4 y 275,305,224 de orden 5.
4. Solución por patrones
La construcción por patrones observa soluciones conocidas, identifica una regla y la replica. No enumera candidatos: genera directamente un cuadro válido. Por eso puede construir órdenes mayores con mucha más rapidez que backtracking.
flowchart LR K[Soluciones conocidas] --> OBS[Observar regularidades] OBS --> RULE[Formular regla] RULE --> GEN[Generar una solución] GEN --> VERIFY[Verificar filas, columnas y diagonales] VERIFY -->|Falla| RULE VERIFY -->|Cumple| DONE[Patrón validado para ese tipo de orden]
Órdenes impares: método siamés
Los ejemplos de y siguen el patrón para órdenes impares:
- colocar el 1 en el centro de la fila superior;
- para el siguiente número, intentar subir una fila y avanzar una columna a la derecha;
- si se sale del tablero, reaparecer por el borde contrario;
- si la casilla destino ya está ocupada, bajar una fila desde la posición actual;
- repetir hasta colocar .
flowchart TD S[Colocar 1: fila superior, columna central] --> N[Tomar siguiente número] N --> UR[Intentar arriba y derecha] UR --> WRAP[Aplicar envoltura en bordes] WRAP --> OCC{¿Destino ocupado?} OCC -->|No| PLACE[Colocar en destino] OCC -->|Sí| DOWN[Colocar debajo de la posición actual] PLACE --> END{¿Se colocó n²?} DOWN --> END END -->|No| N END -->|Sí| V[Cuadro terminado]
En forma modular, desde se propone
Si está ocupada, se usa . El cuadro de del material suma en cada fila, columna y diagonal.

Órdenes doblemente pares:
Los ejemplos y pueden construirse llenando primero la matriz en orden con y sustituyendo ciertas casillas por su complemento
En cada bloque se conservan las casillas de sus dos diagonales y se complementan las restantes —o se usa la convención inversa; ambas producen una orientación válida—. Para decidir si una casilla local pertenece a esas diagonales:
flowchart TD A[Llenar 1 hasta n² por filas] --> B[Visitar casilla i,j] B --> C{¿Está en diagonal local de un bloque 4x4?} C -->|Sí| D[Conservar x] C -->|No| E[Reemplazar por n²+1-x] D --> F{¿Quedan casillas?} E --> F F -->|Sí| B F -->|No| G[Cuadro mágico]
Warning
Estos patrones no son intercambiables: el método siamés requiere impar y el método de complementos descrito requiere múltiplo de 4. Los órdenes pares que no son múltiplos de 4, como , necesitan otra construcción.
5. Comparación de las técnicas
| Aspecto | Backtracking | Construcción por patrones |
|---|---|---|
| Propósito | explorar una, todas o la mejor solución | producir directamente una solución |
| Espacio conceptual | árbol de decisiones | secuencia determinista |
| Mecanismo clave | poda y retroceso | regla según el tipo de orden |
| Ventaja | generalidad y enumeración | rapidez y escalabilidad |
| Desventaja | costo exponencial frecuente | exige descubrir y justificar un patrón |
| Ejemplo central | ocho reinas | cuadros mágicos 4×4, 7×7, 8×8 y 9×9 |
flowchart TD P[¿Qué pide el problema?] --> A{¿Necesito todas las soluciones?} A -->|Sí| B[Backtracking con poda] A -->|No| C{¿Existe una construcción conocida?} C -->|Sí| D[Aplicar patrón y verificar] C -->|No| E[Backtracking hasta la primera solución]
Errores conceptuales que conviene evitar
- Una solución parcial no tiene que ser completa, pero sí debe respetar todas las restricciones que ya pueden comprobarse.
push_backrepresenta aplicar una decisión ypop_backdeshacerla; la llamada recursiva ocurre entre ambas operaciones.- Las diagonales no se comparan recorriendo todo el tablero: y las identifican.
- Las 12 soluciones fundamentales de las ocho reinas no significan que el programa imprima solo 12; el programa imprime 92 porque no elimina simetrías.
- Un patrón que funciona para orden impar no necesariamente funciona para orden par.
- Construir un cuadro mágico y enumerar todos los cuadros mágicos son problemas computacionales distintos.
Preguntas de recuperación activa
- ¿Por qué colocar exactamente una reina por fila elimina una restricción sin comprobarla?
- ¿Qué representan y en el programa?
- ¿En qué orden ocurren elegir, validar, avanzar y deshacer?
- ¿Por qué el caso
k == NREINASrepresenta una solución completa? - ¿Cómo se obtiene ?
- ¿Qué hace el método siamés cuando la casilla arriba-derecha está ocupada?
- ¿Cuál es la diferencia entre enumerar cuadros con backtracking y construir uno por patrón?
Conceptos atómicos
- Backtracking
- Problema de las ocho reinas
- Poda del espacio de búsqueda
- Cuadro mágico
- Construcción por patrones
Material de la clase
Resumen después de clase
Las ocho reinas muestran cómo backtracking convierte un problema en un árbol de soluciones parciales: cada nivel asigna una columna a una fila, las columnas y diagonales ocupadas podan decisiones inválidas y el retroceso permite continuar con la siguiente alternativa. Los cuadros mágicos pueden formularse del mismo modo para enumerar soluciones, pero su crecimiento hace atractiva otra técnica: reconocer patrones. El método siamés construye órdenes impares y el método de complementos construye órdenes múltiplos de cuatro, ilustrando que una regla especializada puede superar ampliamente a una búsqueda general cuando solo se necesita una solución.