Algoritmo para construir las permutaciones de una lista

Figura
Figura. Permutaciones con 3 elementos

Las permutaciones de una lista con n elementos distintos son todas las sublistas ordenadas con n elementos distintos que se pueden generar. Se denota generamente como P(n). Su valor es P(n) = n! Como se observa en la Figura, para tres elementos obtenemos P(3) = 3! = 6.

Para ser coherentes con mucha documentación, matemáticamente es preferible P(n, k) denominadas las k-permutaciones, de tal forma que P(n, n) = P(n) y así se prescinde del término variaciones V(n, k), que pasaría a ser P(n, k). Sin embargo en la aplicación y en esta serie de temas vamos a considerar las permutaciones de forma separada de las variaciones.

El siguiente algoritmo ya lo presenté hace años en el tema permutar un array:

// P(n) = n! = n × (n-1)! = n × P(n-1)
function permute1(list){
    let n = list.length;
    let result = [];
    if (n<2) {
        //iter += 1;
        result.push(list);
    } else if (n===2) {
        //iter += 1;
        result = [[list[0], list[1]], [list[1], list[0]]];
    } else {
        //iter += 1
        for (let j=0; j<n; j++) {
            //iter += n+1;
            let listMinor = list.slice(0,j).concat(list.slice(j+1));
            let subResult = permute1(listMinor);
            for (let i=0; i<subResult.length; i++){
                //iter += subResult[i].length+1;
                result.push([list[j], ...subResult[i]]);
            }
        }
    }
    return result;
}

Definimos la recurrencia del algoritmo anterior:

T(n) = {1si n≤2
n T(n-1) + n2 + n + 1 + n n!si n>2

Vimos en aquella página que esta recurrencia no es equiparable al modelo de ecuación característica de coeficientes constantes:

a0tn+a1tn-1+...+aktn-k = bnp(n)

En aquel momento hice un despliegue o desarrollo de la recurrencia desde n hasta el caso trivial. A continuación se expone el mismo desarrollo y solución alcanzada en ese tema. Para simplificar sustituiremos P(n) = n2 + n + 1 y lo repondremos al finalizar. En la iteración n tenemos:

T(n) = n T(n-1) + P(n) + n n!

Iteración n-1

T(n) = n ( (n-1) T(n-2) + P(n-1) + (n-1) (n-1)! ) + P(n) + n n! =
= n(n-1) T(n-2) + n P(n-1) + P(n) + n (n-1) (n-1)! + n n! =
= n(n-1) T(n-2) + n P(n-1) + P(n) + (n-1) n! + n n!

Iteración n-2

T(n) = n(n-1) ( (n-2) T(n-3) + P(n-2) + (n-2) (n-2)! ) + n P(n-1) + P(n) + (n-1) n! + n n! =
= n(n-1)(n-2) T(n-3) + n(n-1)P(n-2) + n P(n-1) + P(n) + (n-2) n! + (n-1) n! + n n!

Seguimos iterando hasta llegar a la iteración 3

T(n) = n(n-1)(n-2)···4·3 T(2) +
+ n(n-1)···5·4 P(3)+···+n(n-1)P(n-2)+n P(n-1)+P(n) +
+∑k=3..n (n-k+3) n!

Llegamos al caso trivial donde T(2) = 1 y como k=3..n (n-k+3) n!= 1/2 (n2+n-6) n!

T(n) = n!/2 + ( ∑k=3..n (n!/k!) P(k) ) + 1/2 (n2+n-6) n!

Reponiendo P(k) = k2+k+1

T(n) = n!/2 + ( n! ∑k=3..n (k2+k+1)/k! ) + 1/2 (n2+n-6) n!

Y finalmente lo ponemos como sigue

T(n) = n! (n2+n-5+ ∑j=3..nj2+j+1)
2j!

Como estamos practicando con la técnica de la función generadora o generatriz, en el resto de esto tema nos ocuparemos de obtener esa solución, o alguna equivalente, con esa técnica.

Resolución con función generadora exponencial

La técnica del desarrollo de la recurrencia del primer apartado fue bastante fácil de llevar a cabo. Sin embargo intentarlo mediante una función generadora (o generatriz) no me ha sido tan sencillo, como veremos a continuación. Tuve que estudiar algunas cosas que expongo en un tema anterior recurrencias con coeficientes variables, pues en un momento dado tendremos que utilizarlo.

Empecemos con la siguiente definición:

T(0) = T(1) = T(2) = 1
T(n) = n T(n-1) + n2 + n + 1 + n n!

Incrementamos índices para evitar negativos:

T(n+1) = (n+1) T(n) + (n+1)2 + n + 2 + (n+1) (n+1)!

Vemos que (n+1) es un factor comun de la derecha, por lo que hacemos esto

T(n+1) = (n+1) T(n) + (n+1)2 + n + 2 + (n+1) (n+1)! =
= (n+1) T(n) + (n+1)2 + (n+1) + (n+1) (n+1)! + 1 =
= (n+1) ( T(n) + (n+1) + 1 + (n+1)! ) + 1 =
= (n+1) ( T(n) + n + 2 + (n+1)! ) + 1

de donde obtenemos esta recurrencia:

T(n+1)= T(n) + n + 2 + (n+1)!+1
n+1n+1

Tal como comentamos en el tema anterior sobre desplazamiento del caso base, el caso base de esta recurrencia se encuentra también desplazado y, a efectos de la recurrencia, es T(2) = 1, puesto que el algoritmo nunca podrá bajar de n=2 cuando partimos de un valor superior. Entonces usaremos la función generadora exponencial siguiente:

Gn (x) = ∑n≥2 T(n)xn
n!

La única condición inicial que aplicaremos es la del caso base efectivo T(2), por lo que sólo consideraremos G2(x), ignorándose G0(x) y G1(x):

G0 (x) = ∑n=0..0 T(n)xn= T(0)x0= 1
n!0!
G1 (x) = ∑n=1..1 T(n)xn= T(1)x1= x
n!1!
G2 (x) = ∑n=2..2 T(n)xn= T(2)x2=x2
n!2!2

Aplicamos xn / n! a la recurrencia [1] y sumamos:

n≥2T(n+1) xn= ∑n≥2T(n) xn+ ∑n≥2(n+2+(n+1)!) xn+ ∑n≥2xn
(n+1) n!n!n!(n+1) n!

Veamos que es cada término anterior como una función f(x, y). Empecemos por lo más evidente en [3]:

n≥2T(n) xn= G(x)
n!

Veamos el término de la izquierda en [3]:

n≥2T(n+1) xn= ∑n≥2T(n+1) xn+1 x-1=1n≥2T(n+1) xn+1=
(n+1) n!(n+1)!x(n+1)!
=1n≥3T(n) xn=1( -T(2) x2+ ∑n≥2T(n) xn) =
xn!x2!n!
=1( G(x) -x2)
x2

Veamos el término de la derecha en [3], que también puede comprobarse Wolframalpha:

n≥2xn= ∑n≥2xn=∑n≥2xn+1 x-1=
(n+1) n!(n+1)!(n+1)!
=1n≥2xn+1=1n≥3xn=1(-x0-x1-x2+ ∑n≥0xn) =
x(n+1)!xn!x0!1!2!n!
=1( -1 - x -x2+ ex ) =-1+ex-x+2
x2x2

Y por último el término central en [3] es como sigue (ver desarrollo en el desplegable siguiente):

n≥2(n+2+(n+1)!) xn= (2+x) ex +1- 5x - 3
n!(1-x)2

Sustituimos [4], [5], [6], [7] en [3]:

1( G(x) -x2) = G(x) + (2+x) ex +1- 5x - 3 +ex-1-x+2
x2(1-x)2x2

Despejando G(x) queda:

G(x) =ex (1+x)2+-5x4+6x3+2x2-x-1
1-x(1-x)3

Intentemos simplificar:

G(x) =ex (1+2x+x2)+-5x4+6x3+2x2-x-1=
1-x(1-x)3
=ex+2x ex+x2 ex+1-1-10+ 5x + 9
1-x1-x1-x(1-x)3(1-x)21-x

Busquemos una serie para cada término de [9], empezando por los términos que no tienen el exponencial ex. Uno de ellos es el siguiente, cuyo desarrollo de Taylor puede ver en el desplegable siguiente. Observe que ajustamos el índice del sumatorio a n≥2 para hacerlo coincidir con la serie que deseamos obtener.

1= 1 + 3x + ∑n≥2(n+2)!xn
(1-x)32n!

El siguiente en [9] tiene este serie, con el desarrollo de Taylor en el desplegable, viendo que volvemos a ajustar el índice del sumatorio a n≥2 para hacerlo coincidir con la serie que deseamos obtener.

1= 1 + 2x + ∑n≥2 (n+1)!xn
(1-x)2n!

Los términos de la derecha en [9] que no tienen ex van quedando así:

1-1-10+ 5x + 9=
(1-x)3(1-x)21-x
= 1 + 3x + ∑n≥2(n+2)!xn- 1 - 2x - ∑n≥2 (n+1)!xn-10+5x+9 =
2n!n!1-x
= ∑n≥2 ((n+2)!-(n+1)! )xn+ 6x + 9 -10=
2n!1-x
= ∑n≥2n(n+1)!xn+ 6x + 9 -10=
2n!1-x
= ∑n≥2n(n+1)!xn- 1 - 4x - 10 ∑n≥2 n!xn=
2n!n!
= - 1 - 4x - ∑n≥2 (n(n+1)!-10 n! )xn
2n!

El término restante 6x+9-10/(1-x) se resuelve en el siguiente desplegable.

Y ahora vamos por los tres términos de la izquierda en [9] que tienen ex. Para resolver esto vease primero lo que explicamos en un tema anterior sobre las K-Tuplas, siendo k ∈ ℤ, k≥0

xk ex= ∑n≥0 (j=0..n-kn!)xn
1-xj!n!

Identificamos en lo que sigue en cada sumando x0, x1, x2 siendo k = 0, 1, 2:

(1+x)2ex =
1-x
ex+2x ex+x2 ex=
1-x1-x1-x
= ∑n≥0 (j=0..nn!)xn+ 2 ∑n≥0 (j=0..n-1n!)xn+
j!n!j!n!
+ ∑n≥0 (j=0..n-2n!)xn=
j!n!
= ∑n≥0 (j=0..nn!+ 2 ∑j=0..n-1n!+ ∑j=0..n-2n!)xn
j!j!j!n!

Veamos el término general

j=0..nn!+ 2 ∑j=0..n-1n!+ ∑j=0..n-2n!=
j!j!j!
= 4 ∑j=0..nn!- 2 ∑j=n..nn!- ∑j=n-1..nn!=
j!j!j!
= 4 (j=0..nn!) -2 n!-n!-n!=
j!n!(n-1)!n!
= 4 (j=0..nn!) - n - 3
j!

Por lo tanto

ex+2x ex+x2 ex=
1-x1-x1-x
= ∑n≥0 ( 4 (j=0..nn!) - n - 3 )xn=
j!n!
= 1 + 4x + ∑n≥2 ( 4 (j=0..nn!) - n - 3 )xn
j!n!

Se ajusta n≥2 apareciendo el componente 1+4x. Los términos generales [10] y [11] componen la solución, observando que el término 1+4x se anula con -1-4x, algo que no es casual, pues proviene del ajuste de índices al caso base, tal como veremos en un tema posterior sobre casos bases genéricos en una recurrencia:

G(x) = - 1 - 4x - ∑n≥2 (n(n+1)!-10 n! )xn+
2n!
+ 1 + 4x + ∑n≥2 ( 4 (j=0..nn!) - n - 3 )xn=
j!n!
= ∑n≥2 (n(n+1)!-10 n! - n - 3 - 4 ∑j=0..nn!)xn=
2j!n!
= ∑n≥2 n! (n2+n-10 -n+3+ 4 ∑j=0..n1)xn
2n!j!n!

Tenemos entonces el siguiente término general para n≥2:

T(n) = n! (n2+n- 10 -n+3+ 4 ∑j=0..n1)
2n!j!

Esta solución produce la secuencia 1, 34, 253, 1896, 15739, 145510, ... para n≥2 tal como vemos en Wolframalpha (incluso también expandiendo la propia recurrencia en Wolframalpha). Podríamos dejarlo así, pero sin embargo vamos a ajustar el índice del sumatorio interior para luego facilitar la comparación con la solución que alcanzamos con el despliegue:

G(x) = ∑n≥2 n! (n2+n-10 -n+3+ 10 + 4 ∑j=3..n1)xn=
2n!j!n!
= ∑n≥2 n! (n2+n-n+3+ 4 ∑j=3..n1)xn
2n!j!n!

En lo anterior vemos que -10 se anula con +10, algo que no es casual, pues proviene del ajuste de índices al caso base, tal como veremos en un tema posterior sobre casos bases genéricos en una recurrencia. Entonces este será nuestro término general final, que produce la misma secuencia 1, 34, 253, 1896, 15739, 145510, ... para n≥2 como se puede comprobar en Wolframalpha

T(n) = n! (n2+n-n+3+ 4 ∑j=3..n1)
2n!j!

Esta solución es aparentemente distinta a la que obtuvimos con el desglose de la recurrencia y que repetimos en el primer apartado, pero son equivalentes como vemos en Wolframalpha produciendo esa misma secuencia. En unos apartados más abajo demostraremos que esto es cierto.

T(n) = n! (n2+n-5+ ∑j=3..nj2+j+1)
2j!

Soluciones equivalentes del algoritmo para construir permutaciones

Para ver la equivalencia entre [13] y [14] primero restamos los términos entre paréntesis, el segundo del primero, prescidiendo de n! y (n2+n)/2 que aparece en ambas, vemos que resulta cero, siendo por tanto ambas soluciones iguales, lo que se puede comprobar en Wolframalpha

-5+ ∑j=3..nj2+j+1-4+n+3= 0
2j!n!

Intentándolo de otra forma también con ayuda de Wolframalpha resolviendo el sumatorio en [14] obtenemos:

j=3..nj2+j+1= -15-n+3+4 e Γ(n+1, 1)
j!2n!n!

En el siguiente apartado demostraremos el resultado anterior. Por ahora sígamos con nuestro propósito viendo que en el resumen que vimos en el tema anterior las soluciones equivalentes de las K-Tuplas K(n) = e Γ(n+1, 1) = ∑j=0..n n! / j! por lo que sustituimos en lo anterior

j=3..nj2+j+1= -15-n+3+ 4 ∑j=0..n1
j!2n!j!

Ajustaremos el índice:

4 ∑j=0..n1= 4 ∑j=0..21+ 4 ∑j=3..n1= 10 + 4 ∑j=3..n1
j!j!j!j!

Quedando

j=3..nj2+j+1= -15-n+3+ 10 + 4 ∑j=3..n1=
j!2n!j!
=5-n+3+ 4 ∑j=3..n1
2n!j!

Sustituyendo esto en [14]

T(n) = n! (n2+n-5+ ∑j=3..nj2+j+1)=
2j!
= n! (n2+n-5+5-n+3+ 4 ∑j=3..n1) =
22n!j!
= n! (n2+n-n+3+ 4 ∑j=3..n1)
2n!j!

Vemos que llegamos al resultado equivalente obtenido en [13].

Equiparar soluciones usando K-Tuplas y Convoluciones exponenciales de factoriales y potencias

En [16] nos apoyamos en Wolfram Alpha por medio de la función gamma incompleta para alcanzar el resultado

j=3..nj2+j+1=5-n+3+ 4 ∑j=3..n1
j!2n!j!

Multiplicaremos ambos lados por n! para facilitar la demostración:

j=3..nn! (j2+j+1)= n! (5-n+3+ 4 ∑j=3..n1)
j!2n!j!

Para demostrarlo hemos de recurrir a los temas de las K-Tuplas y de las convoluciones exponenciales de factoriales y potencias que hemos visto en temas anteriores. Por un lado tenemos las K-Tuplas K(n, k):

K(n, k) = ∑j=0..n-kn!
j!

Y por otro las siguientes equivalencias de las convoluciones exponenciales EC(n, k), con k=0:

EC(n, 0) = ∑j=0..nn!= K(n, 0) = ∑j=0..nn!
j!j!

Con k=1

EC(n, 1) = ∑j=0..nn! j= K(n, 1) = ∑j=0..n-1n!
j!j!

Y con k=2

EC(n, 2) = ∑j=0..nn! j2= K(n, 2) + K(n, 1) =
j!
= ∑j=0..n-1n!+ ∑j=0..n-2n!
j!j!

Partimos de la parte izquierda en [17] donde insertamos esas equivalencias tras hacer los correspondientes ajustes en los índices de los sumatorios:

j=3..nn! (j2+j+1)= ∑j=3..nn! j2+ ∑j=3..nn! j+ ∑j=3..nn!=
j!j!j!j!
= -n! 02-n! 12-n! 22+∑j=0..nn! j2-
0!1!2!j!
-n! 0-n! 1-n! 2+∑j=0..nn! j-
0!1!2!j!
-n!-n!-n!+∑j=0..nn!=
0!1!2!j!
= -15 n!+∑j=0..nn! j2+∑j=0..nn! j+∑j=0..nn!=
2j!j!j!
= -15 n!+∑j=0..n-1n!+∑j=0..n-2n!+∑j=0..n-1n!+∑j=0..nn!=
2j!j!j!j!
= -15 n!+∑j=0..n-2n!+ 2 ∑j=0..n-1n!+∑j=0..nn!=
2j!j!j!
= -15 n!-n!-n!+ ∑j=0..nn!-
2(n-1)!n!j!
-2 n!+ ∑j=0..nn!+ 2 ∑j=0..nn!=
n!j!j!
= -15 n!- (n+3) + 4 ∑j=0..nn!=
2j!
= n! ( -15-n+3+ 4 ∑j=0..n1) =
2n!j!
= n! ( -15-n+3+4+4+4+ 4 ∑j=3..n1) =
2n!0!1!2!j!
= n! ( -15-n+3+ 10 + 4 ∑j=3..n1)=
2n!j!
= n! (5-n+3+ 4 ∑j=3..n1)
2n!j!

En el paso [21] separamos en tres series ajustando el índice a cero. En el paso [22] aplicamos [20], [19] y [18] respectivamente. Finalmente hemos alcanzado el resultado [17] lo que equipara las dos soluciones equivalente del algoritmo que hemos visto:

T(n) = n! (n2+n-n+3+ 4 ∑j=3..n1)
2n!j!

T(n) = n! (n2+n-5+ ∑j=3..nj2+j+1)
2j!

Ajuste de las soluciones para valores inferiores al caso base

Las dos soluciones equivalentes son de una recurrencia cuyo caso base está en n=2:

T1(n) = n! (n2+n-n+3+ 4 ∑j=3..n1)
2n!j!

T2(n) = n! (n2+n-5+ ∑j=3..nj2+j+1)
2j!

Esto quiere decir que los valores inferiores n≤2 no estan definidos por esa solución, pues en lugar de obtenerse T(0) = 1, T(1) = 1 vemos que se obtienen valores negativos y diferentes en ambos casos:

T(n)n=0n=1n=2
T1(n)-3-31
T2(n)-5/2-3/21

Por lo tanto para que el coste real que produce el algoritmo sea coherente con la solución hemos de imponer el máximo entre uno y el valor obtenido:

T1(n) = Max ( 1, n! (n2+n-n+3+ 4 ∑j=3..n1) )
2n!j!

T2(n) = Max ( 1, n! (n2+n-5+ ∑j=3..nj2+j+1) )
2j!

Coste asintótico del algoritmo básico para permutar

Cuando analicé este algoritmo hace años en coste asintótico del algoritmo para permutar encontré que era del orden T(n) ∈ O((n+2)!). Como el algoritmo siempre produce resultados parciales copiados, este coste es el mismo para ambos casos.