Arte de la programación de computadoras Volumen 4: el Fascículo 3 tiene un montón de estos que podrían adaptarse mejor a su situación particular de lo que describo.
Códigos grises
Un problema que encontrará es, por supuesto, la memoria y bastante rápido, tendrá problemas con 20 elementos en su conjunto: 20 C 3 = 1140. Y si desea iterar sobre el conjunto, es mejor usar un gris modificado algoritmo de código para que no los tenga todos en la memoria. Estos generan la siguiente combinación de la anterior y evitan repeticiones. Hay muchos de estos para diferentes usos. ¿Queremos maximizar las diferencias entre combinaciones sucesivas? ¿minimizar? etcétera.
Algunos de los documentos originales que describen códigos grises:
- Algunas rutas de Hamilton y un algoritmo de cambio mínimo
- Algoritmo de generación de combinación de intercambio adyacente
Aquí hay algunos otros documentos que cubren el tema:
- Una implementación eficiente del algoritmo de generación combinada de intercambio adyacente de Eades, Hickey y lectura (PDF, con código en Pascal)
- Generadores Combinados
- Encuesta de códigos combinados grises (PostScript)
- Un algoritmo para códigos grises
Chase's Twiddle (algoritmo)
Phillip J Chase, ` Algoritmo 382: Combinaciones de M de N objetos '(1970)
El algoritmo en C ...
Índice de combinaciones en orden lexicográfico (algoritmo de hebillas 515)
También puede hacer referencia a una combinación por su índice (en orden lexicográfico). Al darnos cuenta de que el índice debería ser una cantidad de cambio de derecha a izquierda en función del índice, podemos construir algo que debería recuperar una combinación.
Entonces, tenemos un conjunto {1,2,3,4,5,6} ... y queremos tres elementos. Digamos {1,2,3} podemos decir que la diferencia entre los elementos es uno y en orden y mínima. {1,2,4} tiene un cambio y es lexicográficamente el número 2. Por lo tanto, el número de 'cambios' en el último lugar representa un cambio en el orden lexicográfico. El segundo lugar, con un cambio {1,3,4} tiene un cambio, pero representa más cambios ya que está en el segundo lugar (proporcional al número de elementos en el conjunto original).
El método que describí es una deconstrucción, como parece, desde el conjunto al índice, necesitamos hacer lo contrario, lo cual es mucho más complicado. Así es como Buckles resuelve el problema. Escribí algo de C para calcularlos , con cambios menores: utilicé el índice de los conjuntos en lugar de un rango de números para representar el conjunto, por lo que siempre estamos trabajando desde 0 ... n. Nota:
- Como las combinaciones no están ordenadas, {1,3,2} = {1,2,3} - ordenamos que sean lexicográficas.
- Este método tiene un 0 implícito para iniciar el conjunto de la primera diferencia.
Índice de combinaciones en orden lexicográfico (McCaffrey)
Hay otra forma : su concepto es más fácil de entender y programar, pero no tiene las optimizaciones de Buckles. Afortunadamente, tampoco produce combinaciones duplicadas:
El conjunto que maximiza , dónde .
Para un ejemplo: 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)
. Entonces, la 27ª combinación lexicográfica de cuatro cosas es: {1,2,5,6}, esos son los índices de cualquier conjunto que desee ver. Ejemplo a continuación (OCaml), requiere choose
función, se deja al lector:
(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
(* maximize function -- maximize a that is aCb *)
(* return largest c where c < i and choose(c,i) <= z *)
let rec maximize a b x =
if (choose a b ) <= x then a else maximize (a-1) b x
in
let rec iterate n x i = match i with
| 0 -> []
| i ->
let max = maximize n i x in
max :: iterate n (x - (choose max i)) (i-1)
in
if x < 0 then failwith "errors" else
let idxs = iterate (List.length set) x k in
List.map (List.nth set) (List.sort (-) idxs)
Un iterador de combinaciones pequeñas y simples
Los siguientes dos algoritmos se proporcionan con fines didácticos. Implementan un iterador y combinaciones generales de carpetas (más generales). Son lo más rápidos posible, tienen la complejidad O ( n C k ). El consumo de memoria está limitado por k
.
Comenzaremos con el iterador, que llamará a una función proporcionada por el usuario para cada combinación
let iter_combs n k f =
let rec iter v s j =
if j = k then f v
else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
iter [] 0 0
Una versión más general llamará a la función proporcionada por el usuario junto con la variable de estado, comenzando desde el estado inicial. Como necesitamos pasar el estado entre diferentes estados, no usaremos el bucle for, sino que usaremos recursividad,
let fold_combs n k f x =
let rec loop i s c x =
if i < n then
loop (i+1) s c @@
let c = i::c and s = s + 1 and i = i + 1 in
if s < k then loop i s c x else f c x
else x in
loop 0 0 [] x