Arte da programação de computadores Volume 4: Fascículo 3 tem uma tonelada dessas que podem se encaixar melhor em sua situação específica do que a que eu descrevo.
Códigos Cinzentos
Um problema que você encontrará é a memória e, rapidamente, você terá problemas em 20 elementos em seu conjunto - 20 C 3 = 1140. E se você quiser percorrer o conjunto, é melhor usar um cinza modificado algoritmo de código para que você não esteja mantendo todos eles na memória. Eles geram a próxima combinação da anterior e evitam repetições. Existem muitos deles para diferentes usos. Queremos maximizar as diferenças entre combinações sucessivas? minimizar? et cetera.
Alguns dos documentos originais que descrevem códigos em cinza:
- Alguns caminhos de Hamilton e um algoritmo de mudança mínima
- Algoritmo de geração de combinação de intercâmbio adjacente
Aqui estão alguns outros artigos que abordam o tópico:
- Uma implementação eficiente do algoritmo de geração de combinação de intercâmbio adjacente Eades, Hickey, Read (PDF, com código em Pascal)
- Geradores combinados
- Pesquisa de códigos combinatórios de cinza (PostScript)
- Um algoritmo para códigos cinza
Chase's Twiddle (algoritmo)
Phillip J Chase, ` Algoritmo 382: Combinações de M de N Objetos '(1970)
O algoritmo em C ...
Índice de combinações em ordem lexicográfica (algoritmo de curvatura 515)
Você também pode fazer referência a uma combinação por seu índice (em ordem lexicográfica). Percebendo que o índice deve sofrer alguma alteração da direita para a esquerda com base no índice, podemos construir algo que deve recuperar uma combinação.
Então, temos um conjunto {1,2,3,4,5,6} ... e queremos três elementos. Digamos que {1,2,3} podemos dizer que a diferença entre os elementos é uma e em ordem e mínima. {1,2,4} tem uma alteração e é lexicograficamente o número 2. Portanto, o número de 'alterações' no último local é responsável por uma alteração na ordem lexicográfica. O segundo lugar, com uma alteração {1,3,4}, tem uma alteração, mas é responsável por mais alterações, pois fica em segundo lugar (proporcional ao número de elementos no conjunto original).
O método que descrevi é uma desconstrução, ao que parece, do conjunto para o índice, precisamos fazer o inverso - o que é muito mais complicado. É assim que a Buckles resolve o problema. Escrevi C para computá-los , com pequenas alterações - usei o índice dos conjuntos em vez de um intervalo numérico para representar o conjunto, por isso estamos sempre trabalhando de 0 ... n. Nota:
- Como as combinações são desordenadas, {1,3,2} = {1,2,3} - ordenamos que sejam lexicográficas.
- Este método possui um 0 implícito para iniciar o conjunto para a primeira diferença.
Índice de combinações em ordem lexicográfica (McCaffrey)
Existe outra maneira : seu conceito é mais fácil de entender e programar, mas sem as otimizações do Buckles. Felizmente, também não produz combinações duplicadas:
O conjunto que maximiza , onde .
Para um exemplo: 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)
. Portanto, a 27ª combinação lexicográfica de quatro coisas é: {1,2,5,6}, esses são os índices de qualquer conjunto que você queira examinar. O exemplo abaixo (OCaml) requer choose
função, deixada para o leitor:
(* 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)
Um iterador de combinações pequenas e simples
Os dois algoritmos a seguir são fornecidos para fins didáticos. Eles implementam um iterador e combinações gerais de pastas (mais gerais). Eles são o mais rápido possível, tendo a complexidade O ( n C k ). O consumo de memória é limitado por k
.
Começaremos com o iterador, que chamará uma função fornecida pelo usuário para cada combinação
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
Uma versão mais geral chamará a função fornecida pelo usuário juntamente com a variável state, iniciando no estado inicial. Como precisamos passar o estado entre estados diferentes, não usaremos o loop for, mas usar recursão,
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