Ich habe n Elemente. Als Beispiel nennen wir 7 Elemente, 1234567. Ich weiß, dass es 7 gibt! = 5040 Permutationen dieser 7 Elemente möglich.
Ich möchte einen schnellen Algorithmus mit zwei Funktionen:
f (Zahl) ordnet eine Zahl zwischen 0 und 5039 einer eindeutigen Permutation zu, und
f '(Permutation) ordnet die Permutation der Zahl zu, aus der sie generiert wurde.
Die Entsprechung zwischen Nummer und Permutation ist mir egal, vorausgesetzt, jede Permutation hat ihre eigene eindeutige Nummer.
So könnte ich zum Beispiel Funktionen haben, bei denen
f(0) = '1234567'
f'('1234567') = 0
Der schnellste Algorithmus, der mir in den Sinn kommt, besteht darin, alle Permutationen aufzulisten und eine Nachschlagetabelle in beide Richtungen zu erstellen, sodass f (0) nach dem Erstellen der Tabellen O (1) und f ('1234567') a ist Suche auf einer Zeichenfolge. Dies ist jedoch speicherhungrig, insbesondere wenn n groß wird.
Kann jemand einen anderen Algorithmus vorschlagen, der schnell und ohne den Speichernachteil funktioniert?
Antworten:
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 bringtcaebd
.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:
(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:
Für unsere variable Basisnummer:
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.
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
biscaebd
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:
Das Umkehren ist sehr ähnlich:
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:
Oder Sie können die Permutation einfach direkt anwenden, indem Sie den inversen Permutationsalgorithmus verwenden:
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.
quelle
1234
f (4) = {0, 2, 0, 0} = 1342. Und f '(1423) = {0, 1 1, 0} = 3. Dieser Algorithmus ist wirklich inspirierend. Ich frage mich, ob es das Originalwerk aus dem OP ist. Ich habe es für eine Weile studiert und analysiert. Und ich glaube, es ist richtig :){1, 2, 0, 1, 0}
->{1, 3, 0, 4, 2}
? Und umgekehrt? Ist es möglich? (indem nicht zwischen{1, 2, 0, 1, 0}
<--> konvertiert wird{C, A, E, B, D}
, was O (n ^ 2) benötigt.) Wenn "unser Stil" und "gemeinsamer Stil" nicht konvertierbar sind, sind sie tatsächlich zwei verschiedene getrennte Dinge, nicht wahr? Danke xIch habe einen O (n) -Algorithmus gefunden. Hier eine kurze Erklärung: http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html
quelle
Die Komplexität kann auf n * log (n) reduziert werden, siehe Abschnitt 10.1.1 ("Der Lehmer-Code (Inversionstabelle)", S.232ff) des fxtbooks: http://www.jjj.de/fxt/ #fxtbook Fahren Sie mit Abschnitt 10.1.1.1 ("Berechnung mit großen Arrays", S. 235) für die schnelle Methode fort. Der (GPLed, C ++) Code befindet sich auf derselben Webseite.
quelle
Problem gelöst. Ich bin mir jedoch nicht sicher, ob Sie die Lösung nach diesen Jahren noch benötigen. LOL, ich trete gerade dieser Site bei, also ... Überprüfen Sie meine Java-Permutationsklasse. Sie können auf einem Index basieren, um eine Symbolpermutation zu erhalten, oder eine Symbolpermutation angeben und dann den Index abrufen.
Hier ist meine Prämutationsklasse
und hier ist meine Hauptklasse, um zu zeigen, wie man die Klasse benutzt.
Habe Spaß. :) :)
quelle
Jedes Element kann sich an einer von sieben Positionen befinden. Um die Position eines Elements zu beschreiben, benötigen Sie drei Bits. Das heißt, Sie können die Position aller Elemente in einem 32-Bit-Wert speichern. Das ist alles andere als effizient, da diese Darstellung sogar ermöglichen würde, dass sich alle Elemente an derselben Position befinden, aber ich glaube, dass die Bitmaskierung relativ schnell sein sollte.
Bei mehr als 8 Positionen benötigen Sie jedoch etwas raffinierteres.
quelle
Dies ist zufällig eine in J integrierte Funktion :
quelle
Sie können Permutationen mit einem rekursiven Algorithmus codieren. Wenn eine N-Permutation (eine Reihenfolge der Zahlen {0, .., N-1}) die Form {x, ...} hat, codiere sie als x + N * die Codierung von (N-1) -Permutation dargestellt durch "..." auf den Zahlen {0, N-1} - {x}. Klingt nach einem Schluck, hier ist ein Code:
Dieser Algorithmus ist O (n ^ 2). Bonuspunkte, wenn jemand einen O (n) -Algorithmus hat.
quelle
Was für eine interessante Frage!
Wenn alle Ihre Elemente Zahlen sind, können Sie sie von Zeichenfolgen in tatsächliche Zahlen konvertieren. Dann können Sie alle Permutationen sortieren, indem Sie sie in die richtige Reihenfolge bringen und in einem Array platzieren. Danach sind Sie offen für die verschiedenen Suchalgorithmen.
quelle
Ich war in meiner vorherigen Antwort voreilig (gelöscht), aber ich habe die eigentliche Antwort. Es wird von einem ähnlichen Konzept bereitgestellt, dem Faktoradischen , und bezieht sich auf Permutationen (meine Antwort bezog sich auf Kombinationen, ich entschuldige mich für diese Verwirrung). Ich hasse es, nur Wikipedia-Links zu posten, aber ich schreibe, dass ich es vor einiger Zeit getan habe, ist aus irgendeinem Grund unverständlich. Daher kann ich dies später auf Anfrage erweitern.
quelle
Es gibt ein Buch darüber geschrieben. Entschuldigung, aber ich erinnere mich nicht an den Namen (Sie werden ihn höchstwahrscheinlich aus Wikipedia finden). Trotzdem habe ich eine Python-Implementierung dieses Aufzählungssystems geschrieben: http://kks.cabal.fi/Kombinaattori Einiges davon ist auf Finnisch, aber kopieren Sie einfach den Code und die Namensvariablen ...
quelle
Ich hatte genau diese Frage und dachte, ich würde meine Python-Lösung bereitstellen. Es ist O (n ^ 2).
Es ist ziemlich einfach; Nachdem ich die faktoradische Darstellung der Zahl generiert habe, wähle ich einfach die Zeichen aus der Zeichenfolge aus und entferne sie. Das Löschen aus der Zeichenfolge ist der Grund, warum dies eine O (n ^ 2) -Lösung ist.
Die Lösung von Antoine ist besser für die Leistung.
quelle
Eine verwandte Frage ist die Berechnung der inversen Permutation, eine Permutation, die permutierte Vektoren in der ursprünglichen Reihenfolge wiederherstellt, wenn nur das Permutationsarray bekannt ist. Hier ist der O (n) Code (in PHP):
David Spector Frühlingssoftware
quelle