Das Powerset von {1, 2, 3}
ist:
{{}, {2}, {3}, {2, 3}, {1, 2}, {1, 3}, {1, 2, 3}, {1}}
Angenommen, ich habe eine Set
in Java:
Set<Integer> mySet = new HashSet<Integer>();
mySet.add(1);
mySet.add(2);
mySet.add(3);
Set<Set<Integer>> powerSet = getPowerset(mySet);
Wie schreibe ich die Funktion getPowerset mit der bestmöglichen Reihenfolge der Komplexität? (Ich denke, es könnte O (2 ^ n) sein.)
Antworten:
Ja, das ist es
O(2^n)
tatsächlich, da Sie2^n
mögliche Kombinationen generieren müssen . Hier ist eine funktionierende Implementierung, die Generika und Sets verwendet:Und ein Test anhand Ihrer Beispieleingabe:
quelle
O(2^n)
? Das ist die Anzahl der Sätze im Leistungssatz, aber jeder Satz muss im Speicher erstellt werden, was mindestens proportional zur Satzgröße dauert. Laut Wolfram Alpha ist es inO(n * 2^n)
: Wolfram Alpha QueryEigentlich habe ich Code geschrieben, der das tut, wonach Sie in O (1) fragen. Die Frage ist , was Sie vorhaben zu tun als nächstes mit dem Set. Wenn Sie es nur aufrufen
size()
wollen, ist das O (1), aber wenn Sie es wiederholen wollen, ist das offensichtlichO(2^n)
.contains()
wäreO(n)
usw.Benötigen Sie das wirklich?
BEARBEITEN:
Dieser Code ist jetzt in Guava verfügbar und wird über die Methode verfügbar gemacht
Sets.powerSet(set)
.quelle
Hier ist eine Lösung, bei der ich einen Generator verwende. Der Vorteil ist, dass der gesamte Stromversorgungssatz niemals auf einmal gespeichert wird. Sie können ihn also einzeln durchlaufen, ohne dass er im Speicher gespeichert werden muss. Ich würde gerne denken, dass es eine bessere Option ist ... Beachten Sie, dass die Komplexität gleich ist, O (2 ^ n), aber der Speicherbedarf reduziert ist (vorausgesetzt, der Garbage Collector verhält sich !;))
Verwenden Sie dieses Muster, um es aufzurufen:
Es ist aus meiner Project Euler Library ... :)
quelle
Wenn n <63 ist, was eine vernünftige Annahme ist, da Ihnen der Speicherplatz ausgeht (es sei denn, Sie verwenden eine Iterator-Implementierung) und Sie versuchen, den Leistungssatz trotzdem zu erstellen, ist dies eine präzisere Methode. Binäre Operationen sind viel schneller als
Math.pow()
und Arrays für Masken, aber irgendwie haben Java-Benutzer Angst vor ihnen ...quelle
i < (1 << n)
was gleichwertig ist.Hier ist ein Tutorial, das genau beschreibt, was Sie wollen, einschließlich des Codes. Sie haben insofern Recht, als die Komplexität O (2 ^ n) ist.
quelle
Ich habe eine andere Lösung gefunden, die auf den Ideen von @Harry He basiert. Wahrscheinlich nicht das eleganteste, aber hier geht es so, wie ich es verstehe:
Nehmen wir das klassische einfache Beispiel PowerSet von SP (S) = {{1}, {2}, {3}}. Wir wissen, dass die Formel zum Erhalten der Anzahl von Teilmengen 2 ^ n (7 + leere Menge) ist. Für dieses Beispiel 2 ^ 3 = 8 Teilmengen.
Um jede Teilmenge zu finden, müssen wir 0-7 Dezimalzahlen in Binärdarstellungen konvertieren, die in der folgenden Konvertierungstabelle aufgeführt sind:
Wenn wir die Tabelle zeilenweise durchlaufen, führt jede Zeile zu einer Teilmenge, und die Werte jeder Teilmenge stammen aus den aktivierten Bits.
Jede Spalte im Abschnitt Bin Value entspricht der Indexposition im ursprünglichen Eingabesatz.
Hier mein Code:
quelle
Wenn Sie Eclipse-Sammlungen (früher GS-Sammlungen ) verwenden, können Sie die
powerSet()
Methode für alle SetIterables verwenden.Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.
quelle
Ich suchte nach einer Lösung, die nicht so groß war wie die hier veröffentlichten. Dies zielt auf Java 7 ab, sodass für die Versionen 5 und 6 eine Handvoll Pasten erforderlich sind.
Hier ist ein Beispielcode zum Testen:
quelle
Einige der oben genannten Lösungen leiden, wenn die Größe des Satzes groß ist, weil sie viel Objektmüll erzeugen, der gesammelt werden muss, und das Kopieren von Daten erfordern. Wie können wir das vermeiden? Wir können die Tatsache ausnutzen, dass wir wissen, wie groß die Größe der Ergebnismenge sein wird (2 ^ n), ein so großes Array vorab zuweisen und einfach an das Ende anhängen, ohne es zu kopieren.
Die Beschleunigung wächst schnell mit n. Ich habe es mit der obigen Lösung von João Silva verglichen. Auf meiner Maschine (alle Messungen ungefähr) ist n = 13 5x schneller, n = 14 ist 7x, n = 15 ist 12x, n = 16 ist 25x, n = 17 ist 75x, n = 18 ist 140x. Das Erstellen / Sammeln und Kopieren von Müll dominiert also bei scheinbar ähnlichen Big-O-Lösungen.
Die Vorbelegung des Arrays am Anfang scheint ein Gewinn zu sein, verglichen mit dem dynamischen Wachstum. Mit n = 18 dauert das dynamische Wachstum insgesamt etwa doppelt so lange.
quelle
Die folgende Lösung stammt aus meinem Buch " Coding Interviews: Questions, Analysis & Solutions ":
Es werden einige Ganzzahlen in einem Array ausgewählt, die eine Kombination bilden. Ein Satz von Bits wird verwendet, wobei jedes Bit für eine ganze Zahl im Array steht. Wenn das i-te Zeichen für eine Kombination ausgewählt ist, ist das i-te Bit 1; Andernfalls ist es 0. Beispielsweise werden drei Bits für Kombinationen des Arrays [1, 2, 3] verwendet. Wenn die ersten beiden ganzen Zahlen 1 und 2 ausgewählt werden, um eine Kombination [1, 2] zu bilden, sind die entsprechenden Bits {1, 1, 0}. In ähnlicher Weise sind Bits, die einer anderen Kombination [1, 3] entsprechen, {1, 0, 1}. Wir können alle Kombinationen eines Arrays mit der Länge n erhalten, wenn wir alle möglichen Kombinationen von n Bits erhalten können.
Eine Zahl besteht aus einer Reihe von Bits. Alle möglichen Kombinationen von n Bits entsprechen Zahlen von 1 bis 2 ^ n -1. Daher entspricht jede Zahl im Bereich zwischen 1 und 2 ^ n -1 einer Kombination eines Arrays mit der Länge n . Beispielsweise besteht die Zahl 6 aus den Bits {1, 1, 0}, sodass das erste und das zweite Zeichen im Array [1, 2, 3] ausgewählt werden, um die Kombination [1, 2] zu erzeugen. Ebenso entspricht die Zahl 5 mit den Bits {1, 0, 1} der Kombination [1, 3].
Der Java-Code zum Implementieren dieser Lösung sieht wie folgt aus:
Das Methodeninkrement erhöht eine Zahl, die in einem Satz von Bits dargestellt wird. Der Algorithmus löscht 1 Bit aus dem Bit ganz rechts, bis ein 0-Bit gefunden wird. Es setzt dann das 0-Bit ganz rechts auf 1. Um beispielsweise die Zahl 5 mit den Bits {1, 0, 1} zu erhöhen, löscht es 1 Bit von der rechten Seite und setzt das 0-Bit ganz rechts auf 1. Die Bits werden {1, 1, 0} für die Zahl 6, was das Ergebnis einer Erhöhung von 5 um 1 ist.
quelle
Hier ist eine einfache iterative O (2 ^ n) -Lösung:
quelle
quelle
Wenn S eine endliche Menge mit N Elementen ist, enthält die Potenzmenge von S 2 ^ N Elemente. Die Zeit, um die Elemente des Powersets einfach aufzuzählen, beträgt 2 ^ N, also
O(2^N)
eine Untergrenze für die zeitliche Komplexität des (eifrigen) Aufbaus des Powersets.Einfach ausgedrückt, jede Berechnung, bei der Powersets erstellt werden, wird nicht für große Werte von N skaliert. Kein cleverer Algorithmus hilft Ihnen ... abgesehen davon, dass Sie die Powersets nicht erstellen müssen!
quelle
Ein Weg ohne Rekursion ist der folgende: Verwenden Sie eine Binärmaske und machen Sie alle möglichen Kombinationen.
quelle
Algorithmus:
Eingabe: Set [], set_size 1. Ermitteln Sie die Größe der Potenzmenge powet_set_size = pow (2, set_size) 2 Schleife für Zähler von 0 bis pow_set_size (a) Schleife für i = 0 bis set_size (i) Wenn das i-te Bit im Zähler ist set Drucken Sie das i-te Element aus der Menge für diese Teilmenge. (b) Drucken Sie den Trennzeichen für Teilmengen, dh Zeilenumbruch
quelle
Dies ist meine rekursive Lösung, mit der Sie mithilfe von Java Generics die Leistung jedes Satzes abrufen können. Die Hauptidee besteht darin, den Kopf des Eingabearrays wie folgt mit allen möglichen Lösungen des restlichen Arrays zu kombinieren.
Dies wird Folgendes ausgeben:
quelle
Eine weitere Beispielimplementierung:
quelle
Dies ist mein Ansatz mit Lambdas.
Oder parallel (siehe Kommentar parallel ()):
Größe des Eingabesatzes: 18
Logische Prozessoren: 8 bis 3,4 GHz
Leistungsverbesserung: 30%
quelle
Eine Teilmenge von t ist eine beliebige Menge, die durch Entfernen von null oder mehr Elementen von t erstellt werden kann. Die Teilmenge withoutFirst fügt die Teilmengen von t hinzu, denen das erste Element fehlt, und die for-Schleife behandelt das Hinzufügen von Teilmengen mit dem ersten Element. Wenn t beispielsweise die Elemente ["1", "2", "3"] enthält, fügt "MissingFirst" [[""], ["2"], ["3"], ["2", "3" hinzu "]] und die for-Schleife kleben die" 1 "vor dieses Element und fügen sie dem newSet hinzu. Also werden wir am Ende [[""], ["1"], ["2"], ["3"], ["1", "2"], ["1", "3"] haben. , ["2", "3"], ["1", "2", "3"]].
quelle
Set
hat weder eineget
Methode mit einem Index noch einesubSet
Methode;1st
ist keine gültige Kennung (ich nehme an,lst
war gemeint). Ändern Sie alle Sätze in Listen und es wird fast kompiliert ...quelle
Ich musste kürzlich so etwas verwenden, brauchte aber zuerst die kleinsten Unterlisten (mit 1 Element, dann 2 Elementen, ...). Ich wollte weder die leere noch die ganze Liste aufnehmen. Außerdem brauchte ich keine Liste aller zurückgegebenen Unterlisten, ich musste nur ein paar Sachen mit jeder machen.
Wollte dies ohne Rekursion tun und fand Folgendes (wobei das "Doing Stuff" in eine funktionale Schnittstelle abstrahiert wurde):
Auf diese Weise ist es auch einfach, es auf Unterlisten bestimmter Längen zu beschränken.
quelle
quelle
Noch eine andere Lösung - mit Java8 + Streaming-API Es ist faul und geordnet, so dass es korrekte Teilmengen zurückgibt, wenn es mit "limit ()" verwendet wird.
Und der Client-Code ist
/ * Drucke: [] [a] [b] [c] [d] [e] [a, b] [a, c] [b, c] * /
quelle
Wir könnten den Potenzsatz mit oder ohne Rekursion schreiben. Hier ist ein Versuch ohne Rekursion:
quelle
Hier soll ein Stromsatz erzeugt werden. Die Idee ist zuerst =
S[0]
und kleinere Mengen werdenS[1,...n]
.Berechnen Sie alle Teilmengen von kleinerem Satz und fügen Sie sie in alle Teilmengen ein.
Klonen Sie jede Teilmenge in allen Teilmengen und fügen Sie sie zuerst zur Teilmenge hinzu.
quelle
quelle