DMarlon Studio

Lección 19 · 19 de 20

Recursión y vuelta atrás · C

Resuelva el problema de las N reinas en C con recursión y vuelta atrás (backtracking): probar una opción, seguir y deshacerla si no lleva a nada.

C · Recursión y vuelta atrás en DMarlon Studio: el código del ejemplo y su salida en la terminal

El problema de las N reinas consiste en colocar N reinas en un tablero de N×N sin que ninguna ataque a otra: ni en la misma fila, ni en la misma columna, ni en la misma diagonal. Como cada fila lleva exactamente una reina, basta guardar en col[r] la columna de la reina de la fila r.

safe comprueba si una reina puede ir en la columna c de la fila row mirando solo las filas anteriores. Hay ataque si comparten columna (col[r] == c) o diagonal: en una diagonal, la distancia entre columnas es igual a la distancia entre filas, abs(col[r] - c) == row - r.

place es recursiva y aplica la vuelta atrás (*backtracking*): prueba cada columna de la fila actual; si es segura, coloca la reina y se llama a sí misma para la fila siguiente. Cuando esa llamada termina, haya encontrado algo o no, el bucle pasa a la columna siguiente, y el valor de col[row] se sobrescribe: así se deshace la elección sin código extra.

El caso base es row == N: todas las filas tienen reina, así que es una solución. Se cuentan todas y solo se dibuja la primera. Para 6 reinas hay 4 soluciones; para 8, el tablero de ajedrez, 92.

La vuelta atrás sirve para cualquier problema de «elegir, comprobar y retroceder»: sudokus, laberintos, horarios o permutaciones. Las ramas que no son seguras se abandonan pronto, y por eso es mucho más rápida que probar todas las combinaciones.

Código · main.c

#include <stdio.h>#include <stdlib.h>#define N 6int col[N], solutions = 0;  // col[r]: the column of the queen in row r// Can a queen go at (row, c) without attacking the rows above?int safe(int row, int c) {    for (int r = 0; r < row; r++)        if (col[r] == c || abs(col[r] - c) == row - r) return 0;  // column or diagonal    return 1;}void place(int row) {    if (row == N) {                       // all rows filled: a solution        if (++solutions == 1)             // draw only the first one            for (int r = 0; r < N; r++) {                for (int c = 0; c < N; c++) printf(c == col[r] ? " Q" : " .");                printf("\n");            }        return;    }    for (int c = 0; c < N; c++)        if (safe(row, c)) {            col[row] = c;                 // try this column...            place(row + 1);               // ...solve the rest recursively...        }                                 // ...and backtrack: the loop tries the next one}int main(void) {    place(0);    printf("%d queens on a %dx%d board: %d solutions\n", N, N, N, solutions);    return 0;}

Salida · cc main.c -o main && ./main

 . Q . . . .
 . . . Q . .
 . . . . . Q
 Q . . . . .
 . . Q . . .
 . . . . Q .
6 queens on a 6x6 board: 4 solutions

Para recordar

  • Recursión: una función que se llama a sí misma con un problema menor.
  • Siempre hace falta un caso base que detenga la recursión.
  • Vuelta atrás: elegir, seguir y deshacer la elección.
  • Descartar pronto las ramas imposibles ahorra casi todo el trabajo.

Ejercicio

Cambie N a 8 y compruebe que salen 92 soluciones; después cuente también cuántas veces se llama a safe y compárelo con las 8⁸ formas de poner una reina por fila.