Tenía curiosidad por saber cómo std:next_permutation
se implementó, así que extraje la gnu libstdc++ 4.7
versión y desinfecte los identificadores y el formato para producir la siguiente demostración ...
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
template<typename It>
bool next_permutation(It begin, It end)
{
if (begin == end)
return false;
It i = begin;
++i;
if (i == end)
return false;
i = end;
--i;
while (true)
{
It j = i;
--i;
if (*i < *j)
{
It k = end;
while (!(*i < *--k))
/* pass */;
iter_swap(i, k);
reverse(j, end);
return true;
}
if (i == begin)
{
reverse(begin, end);
return false;
}
}
}
int main()
{
vector<int> v = { 1, 2, 3, 4 };
do
{
for (int i = 0; i < 4; i++)
{
cout << v[i] << " ";
}
cout << endl;
}
while (::next_permutation(v.begin(), v.end()));
}
El resultado es el esperado: http://ideone.com/4nZdx
Mis preguntas son: ¿Cómo funciona? ¿Cuál es el significado de i
, j
y k
? ¿Qué valor tienen en las diferentes partes de la ejecución? ¿Qué es un bosquejo de una prueba de su corrección?
Claramente, antes de ingresar al ciclo principal, solo verifica los casos triviales de lista de elementos 0 o 1. En la entrada del bucle principal, i está apuntando al último elemento (no a un final pasado) y la lista tiene al menos 2 elementos.
¿Qué está pasando en el cuerpo del bucle principal?
c++
c++11
permutation
stl-algorithm
lexicographic
Andrés Tomazos
fuente
fuente
Respuestas:
Veamos algunas permutaciones:
¿Cómo pasamos de una permutación a la siguiente? En primer lugar, veamos las cosas de manera un poco diferente. Podemos ver los elementos como dígitos y las permutaciones como números . Viendo el problema de esta manera , queremos ordenar las permutaciones / números en orden "ascendente". .
Cuando ordenamos números queremos "aumentarlos en la cantidad más pequeña". Por ejemplo, cuando contamos no contamos 1, 2, 3, 10, ... porque todavía hay 4, 5, ... en el medio y aunque 10 es más grande que 3, faltan números que pueden obtenerse por aumentando 3 en una cantidad menor. En el ejemplo anterior vemos que
1
permanece como el primer número durante mucho tiempo ya que hay muchos reordenamientos de los últimos 3 "dígitos" que "aumentan" la permutación en una cantidad menor.Entonces, ¿cuándo finalmente "usamos" el
1
? Cuando no haya más permutaciones de los últimos 3 dígitos.¿Y cuándo no hay más permutaciones de los últimos 3 dígitos? Cuando los últimos 3 dígitos están en orden descendente.
¡Ajá! Esta es la clave para comprender el algoritmo. Solo cambiamos la posición de un "dígito" cuando todo a la derecha está en orden descendente porque si no está en orden descendente, todavía quedan más permutaciones (es decir, podemos "aumentar" la permutación en una cantidad menor) .
Volvamos ahora al código:
De las 2 primeras líneas del bucle,
j
es un elemento yi
es el elemento anterior.Luego, si los elementos están en orden ascendente, (
if (*i < *j)
) haga algo.De lo contrario, si todo está en orden descendente, (
if (i == begin)
) entonces esta es la última permutación.De lo contrario, continuamos y vemos que j e i se reducen esencialmente.
Ahora entendemos la
if (i == begin)
parte, así que todo lo que necesitamos entender es laif (*i < *j)
parte.También tenga en cuenta: "Entonces, si los elementos están en orden ascendente ...", lo que respalda nuestra observación anterior de que solo necesitamos hacer algo con un dígito "cuando todo a la derecha está en orden descendente". La
if
declaración de orden ascendente es esencialmente encontrar el lugar más a la izquierda donde "todo a la derecha está en orden descendente".Veamos nuevamente algunos ejemplos:
Vemos que cuando todo a la derecha de un dígito está en orden descendente, buscamos el siguiente dígito más grande y lo colocamos al frente y luego colocamos los dígitos restantes en orden ascendente .
Veamos el código:
Bueno, dado que las cosas a la derecha están en orden descendente, para encontrar el "siguiente dígito más grande" solo tenemos que iterar desde el final, que vemos en las primeras 3 líneas de código.
A continuación, intercambiamos el "siguiente dígito más grande" al frente con la
iter_swap()
declaración y luego, como sabemos que ese dígito era el siguiente más grande, sabemos que los dígitos de la derecha todavía están en orden descendente, así que para ponerlos en orden ascendente, solo tenemos quereverse()
hacerlo.fuente
Combinatorics
, pero este es el más clásico.La implementación de gcc genera permutaciones en orden lexicográfico. Wikipedia lo explica de la siguiente manera:
fuente
Knuth profundiza sobre este algoritmo y sus generalizaciones en las secciones 7.2.1.2 y 7.2.1.3 de El arte de la programación informática . Lo llama "Algoritmo L", aparentemente se remonta al siglo XIII.
fuente
Aquí hay una implementación completa utilizando otros algoritmos de biblioteca estándar:
Manifestación
fuente
is_final_permutation
es más informativo quebegin == end - 1
. Llamaris_sorted_until
/upper_bound
separa la lógica de permutación de esas operaciones y hace que esto sea mucho más comprensible. Además, upper_bound es una búsqueda binaria, mientras quewhile (!(*i < *--k));
es lineal, por lo que es más eficaz .Hay una posible implementación autoexplicativa sobre el uso de cppreference
<algorithm>
.Cambie el contenido a la siguiente permutación lexicográfica (en el lugar) y devuelva verdadero si existe; de lo contrario, ordene y devuelva falso si no existe.
fuente