Um eine Permutation von n Elementen zu beschreiben, sehen Sie, dass Sie für die Position, an der das erste Element endet, n Möglichkeiten haben, sodass Sie dies mit einer Zahl zwischen 0 und n-1 beschreiben können. Für die Position, an der das nächste Element landet, haben Sie n-1 verbleibende Möglichkeiten, sodass Sie dies mit einer Zahl zwischen 0 und n-2 beschreiben können.
Und so weiter, bis Sie n Zahlen haben.
Betrachten Sie als Beispiel für n = 5 die Permutation, die abcde
zu bringt caebd
.
a
Das erste Element landet an der zweiten Position, daher weisen wir ihm Index 1 zu .
b
landet an der vierten Position, die Index 3 wäre, aber es ist die dritte verbleibende Position, also weisen wir sie 2 zu .
c
landet an der ersten verbleibenden Position, die immer 0 ist .
d
endet an der letzten verbleibenden Position, die (von nur zwei verbleibenden Positionen) 1 ist .
e
landet an der einzigen verbleibenden Position, indiziert bei 0 .
Wir haben also die Indexsequenz {1, 2, 0, 1, 0} .
Jetzt wissen Sie, dass zum Beispiel in einer Binärzahl 'xyz' z + 2y + 4x bedeutet. Für eine Dezimalzahl ist
es z + 10y + 100x. Jede Ziffer wird mit einem gewissen Gewicht multipliziert und die Ergebnisse werden summiert. Das offensichtliche Muster im Gewicht ist natürlich, dass das Gewicht w = b ^ k ist, wobei b die Basis der Zahl und k der Index der Ziffer ist. (Ich zähle immer die Ziffern von rechts und beginne bei Index 0 für die am weitesten rechts stehende Ziffer. Wenn ich über die 'erste' Ziffer spreche, meine ich auch die am weitesten rechts stehende.)
Der Grund, warum die Gewichte für Ziffern diesem Muster folgen, ist, dass die höchste Zahl, die durch die Ziffern von 0 bis k dargestellt werden kann, genau 1 niedriger sein muss als die niedrigste Zahl, die nur durch Verwendung der Ziffer k + 1 dargestellt werden kann. In der Binärdatei muss 0111 eins niedriger als 1000 sein. In der Dezimalzahl muss 099999 eins niedriger als 100000 sein.
Codierung auf Variablenbasis
Der Abstand zwischen nachfolgenden Zahlen von genau 1 ist die wichtige Regel. Wenn wir dies erkennen, können wir unsere Indexsequenz durch eine Zahl mit variabler Basis darstellen . Die Basis für jede Ziffer ist die Anzahl der verschiedenen Möglichkeiten für diese Ziffer. Für die Dezimalstelle hat jede Ziffer 10 Möglichkeiten, für unser System hätte die Ziffer ganz rechts 1 Möglichkeit und die Ziffer ganz links n Möglichkeiten. Da die am weitesten rechts stehende Ziffer (die letzte Zahl in unserer Sequenz) immer 0 ist, lassen wir sie weg. Das heißt, wir haben die Basen 2 bis n. Im Allgemeinen hat die k'te Ziffer die Basis b [k] = k + 2. Der höchste zulässige Wert für die Ziffer k ist h [k] = b [k] - 1 = k + 1.
Unsere Regel über die Gewichte w [k] von Ziffern erfordert, dass die Summe von h [i] * w [i], wobei i von i = 0 nach i = k geht, gleich 1 * w [k + 1] ist. Wiederholt ausgedrückt ist w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). Das erste Gewicht w [0] sollte immer 1 sein. Von dort aus haben wir folgende Werte:
k h[k] w[k]
0 1 1
1 2 2
2 3 6
3 4 24
... ... ...
n-1 n n!
(Die allgemeine Beziehung w [k-1] = k! Kann leicht durch Induktion bewiesen werden.)
Die Zahl, die wir durch Konvertieren unserer Sequenz erhalten, ist dann die Summe von s [k] * w [k], wobei k von 0 bis n-1 läuft. Hier ist s [k] das k'te (ganz rechts, beginnend bei 0) Element der Sequenz. Nehmen Sie als Beispiel unser {1, 2, 0, 1, 0}, wobei das Element ganz rechts wie oben erwähnt entfernt wurde: {1, 2, 0, 1} . Unsere Summe ist 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .
Beachten Sie, dass wir, wenn wir die maximale Position für jeden Index einnehmen, {4, 3, 2, 1, 0} haben und dies in 119 konvertiert. Da die Gewichte in unserer Zahlencodierung so gewählt wurden, dass wir nicht überspringen beliebige Zahlen, alle Zahlen 0 bis 119 sind gültig. Es gibt genau 120 davon, was n ist! für n = 5 in unserem Beispiel genau die Anzahl der verschiedenen Permutationen. So können Sie sehen, dass unsere codierten Zahlen alle möglichen Permutationen vollständig spezifizieren.
Dekodierung von variabler Basis Die
Dekodierung ähnelt der Konvertierung in binär oder dezimal. Der übliche Algorithmus ist folgender:
int number = 42;
int base = 2;
int[] bits = new int[n];
for (int k = 0; k < bits.Length; k++)
{
bits[k] = number % base;
number = number / base;
}
Für unsere variable Basisnummer:
int n = 5;
int number = 37;
int[] sequence = new int[n - 1];
int base = 2;
for (int k = 0; k < sequence.Length; k++)
{
sequence[k] = number % base;
number = number / base;
base++; // b[k+1] = b[k] + 1
}
Dies dekodiert unsere 37 korrekt zurück zu {1, 2, 0, 1} ( sequence
wäre {1, 0, 2, 1}
in diesem Codebeispiel, aber was auch immer ... solange Sie entsprechend indizieren). Wir müssen nur 0 am rechten Ende hinzufügen (denken Sie daran, dass das letzte Element immer nur eine Möglichkeit für seine neue Position hat), um unsere ursprüngliche Sequenz {1, 2, 0, 1, 0} wiederherzustellen.
Permutieren einer Liste mithilfe einer Indexsequenz Mit
dem folgenden Algorithmus können Sie eine Liste gemäß einer bestimmten Indexsequenz permutieren. Es ist leider ein O (n²) -Algorithmus.
int n = 5;
int[] sequence = new int[] { 1, 2, 0, 1, 0 };
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
bool[] set = new bool[n];
for (int i = 0; i < n; i++)
{
int s = sequence[i];
int remainingPosition = 0;
int index;
// Find the s'th position in the permuted list that has not been set yet.
for (index = 0; index < n; index++)
{
if (!set[index])
{
if (remainingPosition == s)
break;
remainingPosition++;
}
}
permuted[index] = list[i];
set[index] = true;
}
Allgemeine Darstellung von Permutationen
Normalerweise würden Sie eine Permutation nicht so intuitiv darstellen wie bisher, sondern einfach durch die absolute Position jedes Elements nach dem Anwenden der Permutation. Unser Beispiel {1, 2, 0, 1, 0} für abcde
bis caebd
wird normalerweise durch {1, 3, 0, 4, 2} dargestellt. Jeder Index von 0 bis 4 (oder im Allgemeinen 0 bis n-1) kommt in dieser Darstellung genau einmal vor.
Das Anwenden einer Permutation in dieser Form ist einfach:
int[] permutation = new int[] { 1, 3, 0, 4, 2 };
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
for (int i = 0; i < n; i++)
{
permuted[permutation[i]] = list[i];
}
Das Umkehren ist sehr ähnlich:
for (int i = 0; i < n; i++)
{
list[i] = permuted[permutation[i]];
}
Konvertieren von unserer Darstellung in die allgemeine Darstellung
Beachten Sie, dass wir die erhalten, wenn wir unseren Algorithmus verwenden, um eine Liste mithilfe unserer Indexsequenz zu permutieren und sie auf die Identitätspermutation {0, 1, 2, ..., n-1} anzuwenden inverse Permutation, dargestellt in der gemeinsamen Form. ( {2, 0, 4, 1, 3} in unserem Beispiel).
Um die nicht invertierte Prämutation zu erhalten, wenden wir den gerade gezeigten Permutationsalgorithmus an:
int[] identity = new int[] { 0, 1, 2, 3, 4 };
int[] inverted = { 2, 0, 4, 1, 3 };
int[] normal = new int[n];
for (int i = 0; i < n; i++)
{
normal[identity[i]] = list[i];
}
Oder Sie können die Permutation einfach direkt anwenden, indem Sie den inversen Permutationsalgorithmus verwenden:
char[] list = new char[] { 'a', 'b', 'c', 'd', 'e' };
char[] permuted = new char[n];
int[] inverted = { 2, 0, 4, 1, 3 };
for (int i = 0; i < n; i++)
{
permuted[i] = list[inverted[i]];
}
Beachten Sie, dass alle Algorithmen für den Umgang mit Permutationen in der gemeinsamen Form O (n) sind, während das Anwenden einer Permutation in unserer Form O (n²) ist. Wenn Sie eine Permutation mehrmals anwenden müssen, konvertieren Sie sie zuerst in die allgemeine Darstellung.