Kunst der Computerprogrammierung Band 4: Fascicle 3 enthält eine Menge davon, die möglicherweise besser zu Ihrer speziellen Situation passen, als ich es beschreibe.
Gray Codes
Ein Problem, auf das Sie stoßen werden, ist natürlich der Speicher. Ziemlich schnell treten Probleme mit 20 Elementen in Ihrem Satz auf - 20 C 3 = 1140. Wenn Sie den Satz durchlaufen möchten, verwenden Sie am besten ein modifiziertes Grau Code-Algorithmus, damit Sie nicht alle im Speicher halten. Diese erzeugen die nächste Kombination aus der vorherigen und vermeiden Wiederholungen. Es gibt viele davon für verschiedene Zwecke. Wollen wir die Unterschiede zwischen aufeinanderfolgenden Kombinationen maximieren? minimieren? und so weiter.
Einige der Originalarbeiten, die Gray-Codes beschreiben:
- Einige Hamilton-Pfade und ein Minimal-Change-Algorithmus
- Algorithmus zur Erzeugung benachbarter Austauschkombinationen
Hier sind einige andere Artikel zum Thema:
- Eine effiziente Implementierung des Eades, Hickey, Read Adjacent Interchange Combination Generation-Algorithmus (PDF, mit Code in Pascal)
- Kombinationsgeneratoren
- Übersicht über kombinatorische Gray Codes (PostScript)
- Ein Algorithmus für Gray Codes
Chase's Twiddle (Algorithmus)
Phillip J Chase, " Algorithmus 382: Kombinationen von M aus N Objekten " (1970)
Der Algorithmus in C ...
Index der Kombinationen in lexikographischer Reihenfolge (Buckles-Algorithmus 515)
Sie können eine Kombination auch anhand ihres Index (in lexikografischer Reihenfolge) referenzieren. Wenn wir erkennen, dass sich der Index basierend auf dem Index von rechts nach links ändern sollte, können wir etwas konstruieren, das eine Kombination wiederherstellen sollte.
Wir haben also eine Menge {1,2,3,4,5,6} ... und wir wollen drei Elemente. Nehmen wir an, {1,2,3} wir können sagen, dass der Unterschied zwischen den Elementen eins und in Ordnung und minimal ist. {1,2,4} hat eine Änderung und ist lexikografisch die Nummer 2. Die Anzahl der 'Änderungen' an der letzten Stelle macht also eine Änderung der lexikografischen Reihenfolge aus. Der zweite Platz mit einer Änderung {1,3,4} hat eine Änderung, führt jedoch zu mehr Änderungen, da er an zweiter Stelle steht (proportional zur Anzahl der Elemente in der ursprünglichen Menge).
Die Methode, die ich beschrieben habe, ist eine Dekonstruktion, wie es scheint, von der Menge bis zum Index müssen wir das Gegenteil tun - was viel schwieriger ist. So löst Buckles das Problem. Ich habe ein C geschrieben, um sie mit geringfügigen Änderungen zu berechnen. Ich habe den Index der Mengen anstelle eines Zahlenbereichs verwendet, um die Menge darzustellen, also arbeiten wir immer von 0 ... n. Hinweis:
- Da Kombinationen ungeordnet sind, ist {1,3,2} = {1,2,3} - wir ordnen an, dass sie lexikografisch sind.
- Diese Methode hat eine implizite 0, um die Menge für die erste Differenz zu starten.
Index der Kombinationen in lexikographischer Reihenfolge (McCaffrey)
Es gibt noch einen anderen Weg : Das Konzept ist leichter zu verstehen und zu programmieren, aber ohne die Optimierungen von Buckles. Glücklicherweise werden auch keine doppelten Kombinationen erzeugt:
Das Set , das maximiert , wo .
Zum Beispiel : 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)
. Die 27. lexikografische Kombination von vier Dingen lautet also: {1,2,5,6}, das sind die Indizes aller Mengen, die Sie betrachten möchten. Das folgende Beispiel (OCaml) erfordert eine choose
Funktion, die dem Leser überlassen bleibt:
(* 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)
Ein kleiner und einfacher Kombinationsiterator
Die folgenden zwei Algorithmen werden für didaktische Zwecke bereitgestellt. Sie implementieren einen Iterator und eine (allgemeinere) Ordner-Gesamtkombination. Sie sind so schnell wie möglich und haben die Komplexität O ( n C k ). Der Speicherverbrauch ist begrenzt durch k
.
Wir beginnen mit dem Iterator, der für jede Kombination eine vom Benutzer bereitgestellte Funktion aufruft
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
Eine allgemeinere Version ruft die vom Benutzer bereitgestellte Funktion zusammen mit der Statusvariablen ab dem Anfangszustand auf. Da wir den Zustand zwischen verschiedenen Zuständen übergeben müssen, verwenden wir nicht die for-Schleife, sondern die Rekursion.
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