Eine Permutation ist eine Anordnung von Objekten in einer bestimmten Reihenfolge.
Es gibt viele PermutationsFormen. Hier werden nur 3 besprochen, nämlich die beiden wichtigsten - die nenne ich mal "BruteForce" und "Combinatoric".
Hinzu füge ich noch "SummandAnalyse", einfach um zu zeigen, dass sich beliebig viele PermutationsFormen ausdenken lassen.
BruteForce
BruteForce ist wohl am besten bekannt: Man hat einen Zeichensatz, und eine Anzahl Stellen. Bruteforce findet nun schlicht jede mögliche Art, die Zeichen auf die Stellen zu verteilen.
Hier die Ausgabe des Zeichensatzes "0123" auf 3 Stellen: Nicht wahr: Bruteforce kann jedes Kind, sobald es zählen kann ;).
Allgemeine Permutations-Theorie am Beispiel Bruteforce
Zählen ist ein spezieller Algorithmus, um Bruteforce durchzuführen - mehr dazu später.
Her stelle ich erstmal allgemeinere Betrachtungen an:
Nämlich ich betrachte Permutationen als Bäume, und zwar als Bäume mit Knoten und Blättern.
An den Knotenpunkten im Baum gibts Unterknoten, während Blätter Endpunkte ohne Unterknoten ("Kinder") sind.
Und die Ausgabe einer Permutation wird durch die Blätter gebildet.
Betrachten wir alle Permutationen von "012" auf 3 Stellen genauer:
Auf jeder Stelle wird jedes Zeichen des Zeichensatzes durchprobiert. Aber nicht jedesmal alle Stellen gleichzeitig, sondern nach einer Änderung auf einer höherwertigen Stelle werden erstmal die dahinterliegenden niederwertigeren Stellen durchprobiert.
Nun die Darstellung als Baum:
Man erkennt: Die Änderungen der höherwertigen Stellen sind Knoten, deren jeder 3 "Kinder" gebiert. Nur die kind-losen Knoten, die Blätter, gehören zur Ausgabe der Permutation.
Damit ist ein glaub sehr allgemeiner Algorithmus gegeben zur rekursiven Implementation verschiedenster Permutationen:
Aus einer Anordnung (Permutation) können Kind-Anordnungen generiert werden, die selbst wieder zum Generieren weiterer Kind-Anordnungen verwendet werden.
"Kinderlose" Permutationen hingegen bilden die Ausgabe.
Dieses umgesetzt am Beispiel Bruteforce:
Man muß sich also 2 Dinge überlegen:
Combinatoric
"Combinatoric", die zweite wichtige Permutationsform ist viel schwieriger: Hier werden immer alle Zeichen des Zeichensatzes vollständig ausgegeben, jedoch in immer anderen Reihenfolgen.
Praktische Anwendung findet Combinatorik beim Durchprobieren aller möglichen Wege über mehrere Stationen oder beim Händeschütteln einer Gruppe von Menschen.
Da alle Zeichen in jeder Permutation enthalten sind, ist also die Stellen-Zahl bei Combinatoric mit der Zeichensatz-Anzahl identisch.
Und anders als bei BruteForce muß man immer an 2 Stellen ändern, nämlich 2 Zeichen müssen ihre Plätze tauschen.
Summanden-Analyse
Dieses Beispiel hat keinen mir bekannten Nutzen, ausser, dasses aufzeigt, dass sich alle möglichen Permutationsformen ausdenken lassen:
Ausgabe:
Anzahl der Permutationen
Optimierungen
Der outputCallback
Meine Implementations-Beispiele haben ja meist eine rekursive anonyme Methode, und daher ein Problem ein Ergebnis, nämlich ein Blatt, zurückzugeben.
Daher gebe ich den Methoden einen
Im Aufrufer der PermutationsMethode ist dann die Verarbeitung dieses Ergebnisses implementiert - hier einfach eine Ausgabe auf die Konsole:
Es gibt viele PermutationsFormen. Hier werden nur 3 besprochen, nämlich die beiden wichtigsten - die nenne ich mal "BruteForce" und "Combinatoric".
Hinzu füge ich noch "SummandAnalyse", einfach um zu zeigen, dass sich beliebig viele PermutationsFormen ausdenken lassen.
BruteForce
BruteForce ist wohl am besten bekannt: Man hat einen Zeichensatz, und eine Anzahl Stellen. Bruteforce findet nun schlicht jede mögliche Art, die Zeichen auf die Stellen zu verteilen.
Hier die Ausgabe des Zeichensatzes "0123" auf 3 Stellen: Nicht wahr: Bruteforce kann jedes Kind, sobald es zählen kann ;).
Allgemeine Permutations-Theorie am Beispiel Bruteforce
Zählen ist ein spezieller Algorithmus, um Bruteforce durchzuführen - mehr dazu später.
Her stelle ich erstmal allgemeinere Betrachtungen an:
Nämlich ich betrachte Permutationen als Bäume, und zwar als Bäume mit Knoten und Blättern.
An den Knotenpunkten im Baum gibts Unterknoten, während Blätter Endpunkte ohne Unterknoten ("Kinder") sind.
Und die Ausgabe einer Permutation wird durch die Blätter gebildet.
Betrachten wir alle Permutationen von "012" auf 3 Stellen genauer:
Nun die Darstellung als Baum:
Damit ist ein glaub sehr allgemeiner Algorithmus gegeben zur rekursiven Implementation verschiedenster Permutationen:
Aus einer Anordnung (Permutation) können Kind-Anordnungen generiert werden, die selbst wieder zum Generieren weiterer Kind-Anordnungen verwendet werden.
"Kinderlose" Permutationen hingegen bilden die Ausgabe.
Dieses umgesetzt am Beispiel Bruteforce:
VB.NET-Quellcode
- Public Sub BruteForce(charSetCount As Integer, digitCount As Integer, outputCallback As Action(Of IEnumerable(Of Integer)))
- 'Jede digit-Stelle permutiert den Charset durch, und gebiert also charSetCount "Kinder". Die Kinder werden eine Stelle weiter permutiert.
- 'Auf unterster Ebene (position=digitCount) erfolgt statt weiterer Vertiefung die Ausgabe.
- Console.WriteLine(String.Concat("BruteForce(charSetCount:=", CharSetCount, ",digitCount:=", digitCount, ")"))
- Dim recurse As Action(Of Integer, Integer()) = Nothing
- recurse = _
- Sub(position As Integer, arr As Integer())
- If position = digitCount Then 'Bedingung für "Kinderlosigkeit"
- outputCallback(arr)
- Else
- For i = 0 To charSetCount - 1
- Dim arr2 = arr.CloneX
- arr2(position) = i
- recurse(position + 1, arr2) 'selbst-Aufruf mit dem neuen Array - permutiert wird eine Stelle weiter
- Next
- End If
- End Sub
- Dim digits(digitCount - 1) As Integer
- recurse(0, digits)
- End Sub
- wie werden aus einer Permutation "Kinder" generiert?
- wann tritt "Kinderlosigkeit" ein?
Combinatoric
"Combinatoric", die zweite wichtige Permutationsform ist viel schwieriger: Hier werden immer alle Zeichen des Zeichensatzes vollständig ausgegeben, jedoch in immer anderen Reihenfolgen.
Praktische Anwendung findet Combinatorik beim Durchprobieren aller möglichen Wege über mehrere Stationen oder beim Händeschütteln einer Gruppe von Menschen.
Da alle Zeichen in jeder Permutation enthalten sind, ist also die Stellen-Zahl bei Combinatoric mit der Zeichensatz-Anzahl identisch.
Und anders als bei BruteForce muß man immer an 2 Stellen ändern, nämlich 2 Zeichen müssen ihre Plätze tauschen.
- wie werden aus einer Permutation "Kinder" generiert?
von einer Stelle aus wird sukzessive gegen alle niederwertigeren Stelle getauscht - wann tritt "Kinderlosigkeit" ein?
wenn die Stelle, von der getauscht wird, die niederwertigste ist (position = charSetCount)
VB.NET-Quellcode
- Public Sub Combinatoric(charSetCount As Integer, outputCallback As Action(Of IEnumerable(Of Integer)))
- 'Jede Stelle gebiert "Kinder", indem sie mit allen folgenden Stellen vertauscht wird. Die Kinder werden eine Stelle weiter permutiert.
- 'Auf unterster Ebene (position=charSetCount) erfolgt statt weiterer Vertiefung die Ausgabe.
- Console.WriteLine(String.Concat("Combinatoric(charSetCount:=", charSetCount, ")"))
- Dim recurse As Action(Of Integer, Integer()) = Nothing
- recurse = _
- Sub(position As Integer, arr As Integer())
- If position = charSetCount Then 'Bedingung für "Kinderlosigkeit"
- outputCallback(arr)
- Else
- recurse(position + 1, arr) 'das Original-Array ohne Tauschvorgang ist auch ein "Kind"
- For i = position + 1 To charSetCount - 1
- Dim arr2 = arr.CloneX
- arr2.Swap(position, i)
- recurse(position + 1, arr2) 'selbst-Aufruf mit dem neuen Array - permutiert wird eine Stelle weiter
- Next
- End If
- End Sub
- Dim digits = Enumerable.Range(0, charSetCount).ToArray 'Array mit charSetCount verschiedenen Zahlen
- recurse(0, digits)
- End Sub
Summanden-Analyse
Dieses Beispiel hat keinen mir bekannten Nutzen, ausser, dasses aufzeigt, dass sich alle möglichen Permutationsformen ausdenken lassen:
- wie werden aus einer Permutation "Kinder" generiert?
Bekannt sind die bisherige Summe (currSum) und die Zielsumme (targetSum)
Es werden nun sukzessive der gleichen Liste jeweils andere Summanden zugefügt, von 1 bis zur Zielsummen-Differenz - wann tritt "Kinderlosigkeit" ein?
wenn die bisherige Summe die Zielsumme erreicht hat (currSum = targetSum)
VB.NET-Quellcode
- Public Sub SummandAnalize(targetSum As Integer, outputCallback As Action(Of IEnumerable(Of Integer)))
- 'eine Liste gebiert Kinder, indem ein Summand zugefügt wird, der sukzessive von 1 bis zur Zielsummen-Differenz geändert wird.
- 'hier die Optimierung, dass immer dieselbe list verwendet wird.
- Console.WriteLine(String.Concat("SummandAnalize(targetSum:=", targetSum, ")"))
- Dim list As New List(Of Integer)
- Dim recurse As Action(Of Integer) = Nothing
- recurse = _
- Sub(currSum As Integer)
- If currSum = targetSum Then 'Bedingung für "Kinderlosigkeit"
- outputCallback(list)
- Else
- list.Add(0)
- For i = 1 To targetSum - currSum
- list(list.Count - 1) = i
- recurse(currSum + i)
- Next
- list.RemoveAt(list.Count - 1) 'da immer dieselbe list verwendet wird zum Rekursions-Ausgang die Zufügung rückgängig machen.
- End If
- End Sub
- recurse(0)
- End Sub
Anzahl der Permutationen
- Bei Bruteforce multipliziert jede Stelle die Anzahl mit der Mächtigkeit des Zeichensatzes.
Also beim Zeichensatz "0123" und 3 Stellen ergeben sich 4*4*4 = 4^3 = 64 Permutationen
- Bei Combinatoric multipliziert jede Stelle ihre Wertigkeit mit der Permutations-Anzahl, die sich aus den niedrigeren Wertigkeiten ergibt
also beim Zeichensatz "0123" ergibt sich 1*2*3*4 = 4! = 24 Permutationen
Kann man sich vlt. am Beispiel Wegfindung über Stationen plausibel machen:
Angenommen die Stationen A, B.
Da hat man 2 mögliche Startpunkte: man kann von A nach B fahren oder von B nach A.
Hinzu kommt die Station C
Nun hat man 3 mögliche Startpunkte, man kann von C zunächst nach A, dann B fahren, oder von C zunächst nach B, dann A.
Und ebenso siehts von den anneren Startpunkten aus, sodass sich insgesamt 3*2 Möglichkeiten ergeben, d.i. 3!.
Und bei 4 Stationen hat jede der 4 Stationen als Startort alle Möglichkeiten der Dreier-Variante, also 4*3*2 = 4! = 24
- Bei Summanden-Analyse weiß ich nicht, wie das ausrechnen, und obs ühaupt möglich ist.
Optimierungen
-
Bruteforce "zählen" VB.NET-Quellcode
- Public Sub CountUpBruteForce(charSetCount As Integer, digitCount As Integer, outputCallback As Action(Of IEnumerable(Of Integer)))
- 'Zählen ist eigentlich ein iterativer Vorgang, das ist garnet rekursiv:
- 'man stelle sich ein Zählwerk vor: die letzte Ziffer wird immer hochgezählt
- 'beim Nulldurchgang jedweder Ziffer wird die vorherige Ziffer auch hochgezählt
- 'zb bei 2299 ereignen sich 2 nulldurchgänge, sodass die letzten 3 ziffern hochgezählt werden -> 2300
- Console.WriteLine(String.Concat("CountUpBruteForce(charSetCount:=", charSetCount, ",digitCount:=", digitCount, ")"))
- Dim rotatingDigit = digitCount - 1
- Dim digits(rotatingDigit) As Integer
- While rotatingDigit >= 0
- outputCallback(digits)
- For rotatingDigit = digits.Count - 1 To 0 Step -1
- digits(rotatingDigit) += 1
- If (digits(rotatingDigit) < charSetCount) Then Exit For
- digits(rotatingDigit) = 0
- Next
- End While
- End Sub
-
Combinatoric InPlace VB.NET-Quellcode
- Public Sub InplaceCombinatoric(digitCount As Integer, outputCallback As Action(Of IEnumerable(Of Integer)))
- 'Optimierung: Es kann immer dasselbe Array verwendet werden, wenn nach TauschVorgang und Rekursion der Tauschvorgang wieder zurückgetauscht wird.
- Console.WriteLine(String.Concat("InplaceCombinatoric(digitCount:=", digitCount, ")"))
- Dim digits = digitCount.Times.ToArray
- Dim recurse As Action(Of Integer) = Nothing
- recurse = _
- Sub(position As Integer)
- If position = digitCount Then
- outputCallback(digits)
- Else
- recurse(position + 1)
- For i = position + 1 To digitCount - 1
- digits.Swap(position, i)
- recurse(position + 1)
- digits.Swap(position, i)
- Next
- End If
- End Sub
- recurse(0)
- End Sub
-
Summanden-Analyse ohne Test auf "Kinderlosigkeit" VB.NET-Quellcode
- Public Sub SummandAnalizeOptimized(targetSum As Integer, outputCallback As Action(Of IEnumerable(Of Integer)))
- 'Optimierung: Es werden nur die Kinder generiert, die targetSum nicht erreichen. Das Kind, welches targetSum erreicht, wird direkt ausgegeben.
- Console.WriteLine(String.Concat("SummandAnalizeOptimized(targetSum:=", targetSum, ")"))
- Dim list As New List(Of Integer)
- Dim recurse As Action(Of Integer) = Nothing
- recurse = _
- Sub(currSum As Integer)
- Dim missing = targetSum - currSum
- list.Add(0)
- For i = 1 To missing - 1
- list(list.Count - 1) = i
- recurse(currSum + i)
- Next
- list(list.Count - 1) = missing 'targetSum erreicht
- outputCallback(list)
- list.RemoveAt(list.Count - 1)
- End Sub
- recurse(0)
- End Sub
Der outputCallback
Meine Implementations-Beispiele haben ja meist eine rekursive anonyme Methode, und daher ein Problem ein Ergebnis, nämlich ein Blatt, zurückzugeben.
Daher gebe ich den Methoden einen
outputCallback As Action(Of IEnumerable(Of Integer))
mit, also ich gebe eine Methode mit hinein, die aufgerufen und der ein Blatt-Ergebnis mitgegeben werden kann, ohne die Rekursion zu verlassen.Im Aufrufer der PermutationsMethode ist dann die Verarbeitung dieses Ergebnisses implementiert - hier einfach eine Ausgabe auf die Konsole:
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()