Kunst der Computerprogrammierung Band 4: Faszikel 3 hat eine Menge davon, die vielleicht besser zu Ihrer speziellen Situation passen als das, was ich beschrieben habe.
Graue Codes
Ein Problem, auf das Sie stoßen werden, ist natürlich der Speicherplatz und ziemlich schnell werden Sie Probleme mit 20 Elementen in Ihrer Menge haben. 20 C 3 \= 1140. Und wenn Sie die Menge iterieren wollen, verwenden Sie am besten einen modifizierten Gray-Code-Algorithmus, damit Sie nicht alle Daten im Speicher halten müssen. Diese generieren die nächste Kombination aus der vorherigen und vermeiden Wiederholungen. Es gibt viele dieser Algorithmen für verschiedene Zwecke. Wollen wir die Unterschiede zwischen aufeinander folgenden Kombinationen maximieren? minimieren? usw.
Einige der Originalarbeiten, die Gray Codes beschreiben:
- Einige Hamilton-Pfade und ein Algorithmus für minimale Änderungen
- Algorithmus zur Erzeugung von Kombinationen benachbarter Übergänge
Hier finden Sie einige andere Beiträge zu diesem Thema:
- Eine effiziente Implementierung des Algorithmus zur Erzeugung von Kombinationen aus Eades, Hickey und Read Adjacent Interchange (PDF, mit Code in Pascal)
- Kombinierte Generatoren
- Ü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 lexikografischer Reihenfolge (Buckles Algorithmus 515)
Sie können eine Kombination auch über ihren Index (in lexikografischer Reihenfolge) referenzieren. Wenn man bedenkt, dass der Index eine gewisse Veränderung von rechts nach links auf der Grundlage des Index sein sollte, kann man etwas konstruieren, das eine Kombination wiederherstellen sollte.
Wir haben also eine Menge {1,2,3,4,5,6}... und wir wollen drei Elemente. Sagen wir {1,2,3}, so können wir sagen, dass der Unterschied zwischen den Elementen eins ist und in Ordnung und minimal. {1,2,4} hat eine Änderung und ist lexikografisch die Nummer 2. Die Anzahl der "Änderungen" an der letzten Stelle entspricht also einer Änderung in der lexikographischen Ordnung. Die zweite Stelle mit einer Änderung {1,3,4} hat eine Änderung, macht aber mehr Änderungen aus, da sie an zweiter Stelle steht (proportional zur Anzahl der Elemente in der ursprünglichen Menge).
Die Methode, die ich beschrieben habe, ist eine Dekonstruktion, denn es scheint, dass wir vom Satz zum Index den umgekehrten Weg gehen müssen - was viel schwieriger ist. Dies ist wie Schnallen löst das Problem. Ich schrieb einige C, um sie zu berechnen Ich habe den Index der Mengen anstelle eines Zahlenbereichs verwendet, um die Menge darzustellen, so dass wir immer von 0...n ausgehen. Anmerkung:
- Da die Kombinationen nicht geordnet sind, {1,3,2} = {1,2,3}, ordnen wir sie lexikografisch.
- Diese Methode hat eine implizite 0, um die Menge für die erste Differenz zu beginnen.
Index der Kombinationen in lexikografischer Reihenfolge (McCaffrey)
Es gibt andere Art : Das Konzept ist einfacher zu verstehen und zu programmieren, aber es fehlen die Optimierungen von Buckles. Glücklicherweise erzeugt es auch keine doppelten Kombinationen:
Das Set die das Maximum ausmacht , donde .
Ein Beispiel: 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)
. Die 27. lexikographische Kombination von vier Dingen ist also: {1,2,5,6}, das sind die Indizes der Menge, die man sich ansehen will. Beispiel unten (OCaml), erfordert choose
Funktion, links zum Leser:
(* 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 Iterator für Kombinationen
Die folgenden beiden Algorithmen werden zu didaktischen Zwecken bereitgestellt. Sie implementieren einen Iterator und (eine allgemeinere) Ordnergesamtkombination. 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 Zustandsvariablen auf, ausgehend vom Anfangszustand. Da wir den Zustand zwischen verschiedenen Zuständen weitergeben müssen, werden wir keine for-Schleife verwenden, sondern stattdessen eine 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