He encontrado que el uso de una función recursiva no era bueno para mayor longitudes y enteros porque mastica demasiada RAM, y el uso de un generador/función reanudable (que 'cede' valores) era demasiado lento y requería una gran biblioteca para hacerlo multiplataforma.
Así que aquí es un no-recursivo solución en C++ que produce las particiones en fin ordenados (que es ideal para las permutaciones también). He descubierto que es 10 veces más rápido que las soluciones recursivas aparentemente inteligentes y concisas que probé para longitudes de partición de 4 o más, pero para longitudes de 1-3 el rendimiento no es necesariamente mejor (y no me importa el corto longitudes porque son rápidos con cualquier enfoque).
// Inputs
unsigned short myInt = 10;
unsigned short len = 3;
// Partition variables.
vector<unsigned short> partition(len);
unsigned short last = len - 1;
unsigned short penult = last - 1;
short cur = penult; // Can dip into negative value when len is 1 or 2. Can be changed to unsigned if len is always >=3.
unsigned short sum = 0;
// Prefill partition with 0.
fill(partition.begin(), partition.end(), 0);
do {
// Calculate remainder.
partition[last] = max(0, myInt - sum); // Would only need "myInt - sum" if partition vector contains signed ints.
/*
*
* DO SOMETHING WITH "partition" HERE.
*
*/
if (partition[cur + 1] <= partition[cur] + 1) {
do {
cur--;
} while (
cur > 0 &&
accumulate(partition.cbegin(), partition.cbegin() + cur, 0) + (len - cur) * (partition[cur] + 1) > myInt
);
// Escape if seeked behind too far.
// I think this if-statement is only useful when len is 1 or 2, can probably be removed if len is always >=3.
if (cur < 0) {
break;
}
// Increment the new cur position.
sum++;
partition[cur]++;
// The value in each position must be at least as large as the
// value in the previous position.
for (unsigned short i = cur + 1; i < last; ++i) {
sum = sum - partition[i] + partition[i - 1];
partition[i] = partition[i - 1];
}
// Reset cur for next time.
cur = penult;
}
else {
sum++;
partition[penult]++;
}
} while (myInt - sum >= partition[penult]);
donde he contado hacer algo con "partición" AQUÍ. es donde realmente consumiría el valor. (En la última iteración del código continuará ejecutando el resto del bucle pero encontrado que esto es mejor que el constante control de condiciones de salida - está optimizado para operaciones de mayor envergadura)
0,0,10
0,1,9
0,2,8
0,3,7
0,4,6
0,5,5
1,1,8
1,2,7
1,3,6
1,4,5
2,2,6
2,3,5
2,4,4
3,3,4
Oh I' he usado "corto sin firmar" porque sé que mi longitud y número entero no excederán ciertos límites; cámbialo si no es adecuado para ti :) Revisa los comentarios; una variable allí (cur) tuvo que estar firmada para manejar longitudes de 1 o 2 y hay una instrucción if correspondiente que va con eso, y también he notado en un comentario que si su vector de partición tiene entradas firmadas hay otra línea eso se puede simplificar
para obtener todas las composiciones, en C++ que usarían esta estrategia de permutación sencilla que afortunadamente no produce ningún duplicado:
do {
// Your code goes here.
} while (next_permutation(partition.begin(), partition.end()));
Nido que en el hacer algo con "partición" AQUÍ lugar, y eres bueno para ir.
Una alternativa para encontrar las composiciones (basado en el código de Java aquí https://www.nayuki.io/page/next-lexicographical-permutation-algorithm) es la siguiente. He encontrado que funciona mejor que next_permutation().
// Process lexicographic permutations of partition (compositions).
composition = partition;
do {
// Your code goes here.
// Find longest non-increasing suffix
i = last;
while (i > 0 && composition[i - 1] >= composition[i]) {
--i;
}
// Now i is the head index of the suffix
// Are we at the last permutation already?
if (i <= 0) {
break;
}
// Let array[i - 1] be the pivot
// Find rightmost element that exceeds the pivot
j = last;
while (composition[j] <= composition[i - 1])
--j;
// Now the value array[j] will become the new pivot
// Assertion: j >= i
// Swap the pivot with j
temp = composition[i - 1];
composition[i - 1] = composition[j];
composition[j] = temp;
// Reverse the suffix
j = last;
while (i < j) {
temp = composition[i];
composition[i] = composition[j];
composition[j] = temp;
++i;
--j;
}
} while (true);
Se dará cuenta de algunas variables no declaradas allí, sólo los declaró anteriormente en el código antes de que todos sus bucles do-: i
, j
, pos
y temp
(unsignedshorts), y composition
(mismo tipo y duración como partition
). Puede reutilizar la declaración de i
para su uso en un for-loop en el fragmento de código de particiones. También tenga en cuenta variables como last
que se supone que este código está anidado dentro del código de particiones dado anteriormente.
Nuevamente "Tu código va aquí" es donde consumes la composición para tus propios fines.
Como referencia, aquí están mis encabezados.
#include <vector> // for std::vector
#include <numeric> // for std::accumulate
#include <algorithm> // for std::next_permutation and std::max
using namespace std;
A pesar del aumento masivo de la velocidad por medio de estos enfoques, para cualquier número entero considerables y longitudes de partición esto todavía le hará enojado con su CPU :)
¿No debería permutaciones de (2, 1, 1) estar en esa lista? –
Sabía que había olvidado algo. Gracias, agregó. – deleted77
Las permuciones de las particiones enteras se llaman "composiciones". –