Difícil · recursión

Todas las permutaciones

Una permutación empieza con una elección: ¿qué número va primero? Fíjalo y lo que queda es el mismo problema un tamaño más pequeño. La recursión escribe esa frase literalmente: elegir, delegar, des-elegir.

En una fraseRecursión sobre posiciones: para cada valor v sin usar, ponlo, márcalo, recursión para la siguiente posición y desmárcalo. Probar los valores en orden creciente da el orden lexicográfico gratis.

El problema

Entrada: Un entero n (1 ≤ n ≤ 6).

Salida: Todas las n! permutaciones de 1..n, una por línea, valores separados por un espacio, en orden lexicográfico.

Ejemplos:

EntradaSalida
31 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

n ≤ 6 porque 7! = 5040 líneas y 10! = 3,6 millones: el crecimiento factorial es toda la lección. La profundidad de la recursión es solo n; la explosión está en el número de ramas.

Tu solución

main.cpp — tu solución
Entrada personalizada para «Ejecutar mi entrada»

La corrección corre en tu navegador contra pruebas ocultas. Un bucle infinito se corta a los 3 segundos — aun así, asegúrate de que todo bucle tenga salida. Tu código se guarda solo mientras escribes.

¿Atascado? Pistas

Pista 1 — Escribe primero el caso base

Cuando pos == n el array perm está lleno: escríbelo con espacios simples y un salto de línea, y return. Toda recursión necesita un caso mínimo que detenga el descenso — escríbelo antes del bucle, o la función no tiene donde apoyarse.

Pista 2 — El bucle ES la ramificación

En la posición pos, recorre value de 1 a n. Salta los valores ya marcados en used. Si no: perm[pos] = value; used[value] = 1; generate(pos + 1); — y después, el paso que los principiantes olvidan: used[value] = 0; para devolver el valor. Probar los valores en orden creciente regala el orden lexicográfico.

Pista 3 — Confía en la recursión

Tras colocar un valor en pos, la llamada generate(pos + 1) debe producir todas las completaciones del prefijo actual, en orden, y devolver. No lo sigas a mano más de un nivel: la prueba es estructural: es exactamente el mismo problema, un tamaño más pequeño.

Solución (¡inténtalo primero!)
#include <iostream>
using namespace std;

int n;
int used[10];
int perm[10];

void generate(int pos) {
    if (pos == n) {
        for (int i = 0; i < n; i++) {
            cout << perm[i];
            if (i < n - 1) cout << " ";
        }
        cout << endl;
        return;
    }
    for (int value = 1; value <= n; value++) {
        if (used[value]) continue;
        used[value] = 1;
        perm[pos] = value;
        generate(pos + 1);
        used[value] = 0;
    }
}

int main() {
    cin >> n;
    generate(0);
    return 0;
}

Acabas de escribir tu primer backtracking: elegir, recursión, des-elegir. El mismo esqueleto resuelve subconjuntos, N-reinas, Sudoku y laberintos — solo cambia el cuerpo del bucle. Para ver la recursión desde su otro lado (la pila de llamadas, no el árbol de búsqueda), sube a las Torres de Hanói.

Análisis profundo

Por qué funciona

El espacio de búsqueda es un árbol: la profundidad es la posición, las ramas son los valores sin usar. El backtracking recorre ese árbol en DFS lexicográfico, y el paso de desmarcar es el que permite que un valor reaparezca en otra rama — sin él, cada valor se usaría una sola vez en toda la ejecución.

El coste

O(n! × n) de tiempo — n! líneas, n de trabajo para escribir cada una — y O(n) de espacio para la pila de recursión y los arrays. Con n = 10 ya son 36 millones de líneas: el crecimiento factorial adelanta a cualquier optimización.

Dónde falla

  • Olvidar used[value] = 0; después de la llamada recursiva. Con n = 2 sales una línea en vez de dos; con n = 6, unas pocas en vez de 720 — y la causa es una sola línea ausente.
  • Escribir dentro del bucle en vez de en pos == n. Emitirías cada prefijo parcial y el número de líneas dejaría de cuadrar con n!.

Siguiente paso

Mismo esqueleto, otras cargas: subconjuntos, N-reinas, laberintos. Y la vista de pila de llamadas de la recursión protagoniza Torres de Hanói.

Dos cosas para recordar

  • Backtracking = elegir, recursión, des-elegir; lo de des-elegir no es opcional.
  • Recorre los valores en orden creciente y el lexicográfico sale gratis.
🐞 Esta versión falla: ábrela y pruébala

Nunca devuelve el valor: falta used[value] = 0

#include <iostream>
using namespace std;

int n;
int used[10];
int perm[10];

void generate(int pos) {
    if (pos == n) {
        for (int i = 0; i < n; i++) {
            cout << perm[i];
            if (i < n - 1) cout << " ";
        }
        cout << endl;
        return;
    }
    for (int value = 1; value <= n; value++) {
        if (used[value]) continue;
        used[value] = 1;
        perm[pos] = value;
        generate(pos + 1);
    }
}

int main() {
    cin >> n;
    generate(0);
    return 0;
}