Es ist eine Weile her von diesen Schuljahren. Ich habe einen Job als IT-Spezialist in einem Krankenhaus bekommen. Ich versuche mich jetzt zu bewegen, um etwas zu programmieren. Ich arbeite jetzt an binären Bäumen und habe mich gefragt, wie ich am besten feststellen kann, ob der Baum in der Höhe ausgeglichen ist.
Ich dachte an etwas dabei:
public boolean isBalanced(Node root){
if(root==null){
return true; //tree is empty
}
else{
int lh = root.left.height();
int rh = root.right.height();
if(lh - rh > 1 || rh - lh > 1){
return false;
}
}
return true;
}
Ist das eine gute Implementierung? oder fehlt mir etwas
java
algorithm
data-structures
binary-tree
user69514
quelle
quelle
Antworten:
Stolperte über diese alte Frage, während ich nach etwas anderem suchte. Ich stelle fest, dass Sie nie eine vollständige Antwort erhalten haben.
Um dieses Problem zu lösen, schreiben Sie zunächst eine Spezifikation für die Funktion, die Sie schreiben möchten.
Spezifikation: Ein wohlgeformter Binärbaum wird als "höhenausgeglichen" bezeichnet, wenn (1) er leer ist oder (2) seine linken und rechten untergeordneten Elemente höhenausgeglichen sind und die Höhe des linken Baums innerhalb von 1 liegt Höhe des rechten Baumes.
Nachdem Sie die Spezifikation haben, ist der Code trivial zu schreiben. Folgen Sie einfach der Spezifikation:
Die Übersetzung in die Programmiersprache Ihrer Wahl sollte trivial sein.
Bonusübung : Diese naive Codeskizze durchläuft den Baum bei der Berechnung der Höhen viel zu oft. Können Sie es effizienter machen?
Super Bonus Übung : Angenommen, der Baum ist massiv unausgeglichen. Eine Million Knoten tief auf der einen Seite und drei tief auf der anderen Seite. Gibt es ein Szenario, in dem dieser Algorithmus den Stapel sprengt? Können Sie die Implementierung so korrigieren, dass sie den Stapel niemals sprengt, selbst wenn ein massiv unausgeglichener Baum angegeben wird?
UPDATE : Donal Fellows weist in seiner Antwort darauf hin, dass es verschiedene Definitionen von "ausgewogen" gibt, die man wählen könnte. Zum Beispiel könnte man eine strengere Definition von "höhenausgeglichen" nehmen und verlangen, dass die Pfadlänge zum nächsten leeren Kind innerhalb eines der Pfade zum am weitesten leeren Kind liegt. Meine Definition ist weniger streng und lässt daher mehr Bäume zu.
Man kann auch weniger streng sein als meine Definition; Man könnte sagen, dass ein ausgeglichener Baum einer ist, bei dem sich die maximale Pfadlänge zu einem leeren Baum in jedem Zweig um nicht mehr als zwei oder drei oder eine andere Konstante unterscheidet. Oder dass die maximale Pfadlänge ein Bruchteil der minimalen Pfadlänge ist, wie ein halbes oder ein Viertel.
Normalerweise spielt es keine Rolle. Der Sinn eines Baumausgleichsalgorithmus besteht darin, sicherzustellen, dass Sie nicht in der Situation landen, in der Sie eine Million Knoten auf der einen und drei auf der anderen Seite haben. Donals Definition ist theoretisch in Ordnung, aber in der Praxis ist es ein Schmerz, einen Baumausgleichsalgorithmus zu entwickeln, der diese Strenge erfüllt. Die Leistungseinsparungen rechtfertigen normalerweise nicht die Implementierungskosten. Sie verbringen viel Zeit damit, unnötige Baumumlagerungen vorzunehmen, um ein Gleichgewicht zu erreichen, das in der Praxis kaum einen Unterschied macht. Wen kümmert es, wenn manchmal vierzig Äste benötigt werden, um zum entferntesten Blatt in einem unvollständig ausbalancierten Baum mit Millionen Knoten zu gelangen, wenn es in einem perfekt ausbalancierten Baum theoretisch nur zwanzig dauern könnte? Der Punkt ist, dass es nie eine Million braucht. Es ist normalerweise gut genug, von einem schlimmsten Fall von einer Million auf einen schlimmsten Fall von vierzig zu kommen. Sie müssen nicht bis zum optimalen Fall gehen.
quelle
Balance ist eine wirklich subtile Eigenschaft; Sie denken, Sie wissen, was es ist, aber es ist so leicht, sich zu irren. Insbesondere ist sogar Eric Lipperts (gute) Antwort falsch. Das liegt daran, dass der Begriff der Höhe nicht ausreicht. Sie müssen das Konzept der minimalen und maximalen Höhe eines Baumes haben (wobei die minimale Höhe die geringste Anzahl von Schritten von der Wurzel bis zum Blatt ist und die maximale ... gut, Sie erhalten das Bild). In Anbetracht dessen können wir das Gleichgewicht wie folgt definieren:
(Dies bedeutet tatsächlich, dass die Zweige selbst ausgeglichen sind. Sie können denselben Zweig sowohl für das Maximum als auch für das Minimum auswählen.)
Alles, was Sie tun müssen, um diese Eigenschaft zu überprüfen, ist eine einfache Baumdurchquerung, die die aktuelle Tiefe verfolgt. Wenn Sie das erste Mal zurückverfolgen, erhalten Sie eine Basistiefe. Jedes Mal, wenn Sie danach zurückgehen, vergleichen Sie die neue Tiefe mit der Grundlinie
In Code:
Ich nehme an, Sie könnten dies tun, ohne das Observer-Muster zu verwenden, aber ich finde es einfacher, auf diese Weise zu argumentieren.
[BEARBEITEN]: Warum Sie nicht einfach die Höhe jeder Seite nehmen können. Betrachten Sie diesen Baum:
OK, ein bisschen chaotisch, aber jede Seite der Wurzel ausgeglichen wird:
C
wird Tiefe 2,A
,B
,D
,E
ist , Tiefe 3, undF
,G
,H
,J
ist Tiefe 4. Die Höhe des linken Zweiges 2 ( zur Erinnerung , die Höhe nimmt ab , wenn man traverse der Ast), die Höhe des rechten Astes beträgt 3. Der Gesamtbaum ist jedoch nicht ausgeglichen, da zwischenC
und ein Höhenunterschied von 2 bestehtF
. Sie benötigen eine Minimax-Spezifikation (obwohl der tatsächliche Algorithmus weniger komplex sein kann, da nur zwei zulässige Höhen vorhanden sein sollten).quelle
Dies bestimmt nur, ob die oberste Ebene des Baums ausgeglichen ist. Das heißt, Sie könnten einen Baum mit zwei langen Ästen ganz links und ganz rechts haben, mit nichts in der Mitte, und dies würde wahr zurückkehren. Sie müssen die rekursiv überprüfen
root.left
und prüfenroot.right
, ob sie auch intern ausgeglichen sind, bevor Sie true zurückgeben.quelle
Bonus Übungsantwort. Die einfache Lösung. Offensichtlich könnte man in einer realen Implementierung dies oder etwas einschließen, um zu vermeiden, dass der Benutzer die Höhe in seine Antwort einbeziehen muss.
quelle
out height
" variable Notation)Nachbestellungslösung, den Baum nur einmal durchlaufen. Die zeitliche Komplexität ist O (n), der Raum ist O (1), es ist besser als eine Top-Down-Lösung. Ich gebe Ihnen eine Java-Version Implementierung.
quelle
left == -1
das Wann würde das jemals der Fall sein? Nehmen wir an, dass der rekursive Aufruf impliziert, dass diesleft == -1
wahr ist, wenn alle Teilbäume der linken Kinder unausgeglichen sind?left == 1
bedeutet, dass der linke Teilbaum nicht ausgeglichen ist, dann ist der gesamte Baum nicht ausgeglichen. Wir müssen den richtigen Teilbaum nicht mehr überprüfen und können zurückkehren-1
.Die Definition eines höhenausgeglichenen Binärbaums lautet:
Ein leerer Binärbaum ist also immer höhenausgeglichen.
Ein nicht leerer Binärbaum ist höhenausgeglichen, wenn:
Betrachten Sie den Baum:
Wie zu sehen ist, ist der linke Teilbaum von
A
höhenausgeglichen (da er leer ist), ebenso wie der rechte Teilbaum. Der Baum ist jedoch immer noch nicht höhenausgeglichen, da Bedingung 3 nicht erfüllt ist, da die Höhe des linken Teilbaums0
und die Höhe des rechten Teilbaums gleich ist2
.Auch der folgende Baum ist nicht höhenausgeglichen, obwohl die Höhe des linken und rechten Unterbaums gleich ist. Ihr vorhandener Code gibt dafür true zurück.
Das Wort jeder im Def ist also sehr wichtig.
Das wird funktionieren:
Ideone Link
quelle
Ob der Binärbaum ausgeglichen ist oder nicht, kann durch Durchlaufen der Ebenenreihenfolge überprüft werden:
quelle
Dies wird viel komplizierter als es tatsächlich ist.
Der Algorithmus ist wie folgt:
Sei B = Tiefe des Knotens der untersten Ebene
Wenn abs (AB) <= 1 ist, ist der Baum ausgeglichen
quelle
Was ausgewogen bedeutet, hängt ein wenig von der jeweiligen Struktur ab. Zum Beispiel kann ein B-Baum keine Knoten mehr als eine bestimmte Tiefe von der Wurzel haben, oder weniger, alle Daten leben in einer festen Tiefe von der Wurzel, aber es kann aus dem Gleichgewicht geraten, wenn die Verteilung von Blättern zu Blättern -aber-ein Knoten ist ungleichmäßig. Überspringlisten Haben Sie überhaupt keine Ahnung von Balance, sondern verlassen Sie sich stattdessen auf die Wahrscheinlichkeit, eine anständige Leistung zu erzielen. Fibonacci-Bäume geraten absichtlich aus dem Gleichgewicht und verschieben das Gleichgewicht, um im Gegenzug für gelegentlich längere Aktualisierungen eine überlegene asymptotische Leistung zu erzielen. AVL- und Rot-Schwarz-Bäume fügen jedem Knoten Metadaten hinzu, um eine Invariante für das Tiefengleichgewicht zu erzielen.
Alle diese Strukturen und mehr sind in den Standardbibliotheken der meisten gängigen Programmiersysteme (außer Python, RAGE!) Vorhanden. Das Implementieren von einem oder zwei ist eine gute Programmierpraxis, aber es ist wahrscheinlich keine gute Zeit, um Ihre eigenen für die Produktion zu rollen, es sei denn, Ihr Problem weist eine besondere Leistung auf, die von keiner Standardkollektion erfüllt werden muss.
quelle
Hinweis 1: Die Höhe eines Teilbaums wird nur einmal berechnet.
Anmerkung 2: Wenn der linke Teilbaum nicht ausgeglichen ist, wird die Berechnung des rechten Teilbaums, der möglicherweise Millionen Elemente enthält, übersprungen.
quelle
Das Ausbalancieren hängt normalerweise von der Länge des längsten Pfades in jeder Richtung ab. Der obige Algorithmus wird das nicht für Sie tun.
Was versuchst du zu implementieren? Es gibt selbstausgleichende Bäume (AVL / Rot-Schwarz). In der Tat sind Java-Bäume ausgeglichen.
quelle
Wenn dies für Ihren Job ist , schlage ich vor:
quelle
quelle
1
(dasselbe gilt für minDepth). Die richtige Tiefe sollte jedoch sein0
. Die Wurzel eines Baumes hat immer0
TiefeHier ist eine vollständig ausgearbeitete, getestete Lösung in C # (sorry, ich bin kein Java-Entwickler) (einfach kopieren, in die Konsolen-App einfügen). Ich weiß, dass die Definition von ausgeglichen unterschiedlich ist, so dass nicht jeder meine Testergebnisse mag. Schauen Sie sich jedoch bitte den etwas anderen Ansatz an, Tiefe / Höhe in einer rekursiven Schleife zu überprüfen und bei der ersten Nichtübereinstimmung zu beenden, ohne die Höhe / Ebene / Tiefe des Knotens auf jedem Knoten zu speichern (nur in einem Funktionsaufruf pflegen).
quelle
quelle
RE: @ glückliche Lösung mit einem BFS für eine Durchquerung der Ebenenreihenfolge.
Wir durchlaufen den Baum und behalten einen Verweis auf vars min / max-level bei, der die minimale Ebene beschreibt, auf der ein Knoten ein Blatt ist.
Ich glaube, dass die @ glückliche Lösung eine Modifikation erfordert. Wie von @codaddict vorgeschlagen, müssen wir nicht prüfen, ob ein Knoten ein Blatt ist, sondern prüfen, ob entweder das linke oder das rechte untergeordnete Element null ist (nicht beide). Andernfalls würde der Algorithmus dies als einen gültigen ausgeglichenen Baum betrachten:
In Python:
Diese Lösung sollte alle in der ursprünglichen Frage angegebenen Bestimmungen erfüllen und in O (n) Zeit und O (n) Raum arbeiten. Ein Speicherüberlauf würde auf den Heap geleitet, anstatt einen rekursiven Aufrufstapel zu sprengen.
Alternativ könnten wir zunächst den Baum durchlaufen, um die maximale Höhe + Cache für jeden Stammunterbaum iterativ zu berechnen. Überprüfen Sie dann in einem weiteren iterativen Lauf, ob sich die zwischengespeicherten Höhen der linken und rechten Teilbäume für jede Wurzel nie um mehr als eins unterscheiden. Dies würde auch in O (n) Zeit und O (n) Raum laufen, jedoch iterativ, um keinen Stapelüberlauf zu verursachen.
quelle
Nun, Sie brauchen einen Weg, um die Höhen von links und rechts zu bestimmen, und ob links und rechts ausgeglichen sind.
Und ich würde einfach
return height(node->left) == height(node->right);
height
Lesen Sie zum Schreiben einer Funktion: Rekursion verstehenquelle
Von welcher Art von Baum sprichst du? Es gibt selbstausgleichende Bäume da draußen. Überprüfen Sie ihre Algorithmen, um festzustellen, ob sie den Baum neu anordnen müssen, um das Gleichgewicht aufrechtzuerhalten.
quelle
Hier ist eine Version, die auf einer generischen Tiefenüberquerung basiert. Sollte schneller sein als die andere richtige Antwort und alle genannten "Herausforderungen" bewältigen. Entschuldigung für den Stil, ich kenne Java nicht wirklich.
Sie können es immer noch viel schneller machen, indem Sie früh zurückkehren, wenn sowohl max als auch min eingestellt sind und einen Unterschied> 1 haben.
quelle
quelle
quelle
Folgendes habe ich für Erics Bonusübung versucht. Ich versuche, meine rekursiven Schleifen abzuwickeln und zurückzukehren, sobald ich feststelle, dass ein Teilbaum nicht ausgeglichen ist.
quelle
quelle
Ein leerer Baum ist höhenausgeglichen. Ein nicht leerer Binärbaum T ist ausgeglichen, wenn:
1) Der linke Teilbaum von T ist ausgeglichen
2) Der rechte Teilbaum von T ist ausgeglichen
3) Der Höhenunterschied zwischen dem linken und dem rechten Teilbaum beträgt nicht mehr als 1.
Zeitkomplexität: O (n)
quelle
Um eine bessere Leistung speziell bei großen Bäumen zu erzielen, können Sie die Höhe in jedem Knoten speichern, sodass ein Kompromiss zwischen Leistung und Leistung besteht:
Beispiel für die Implementierung des Hinzufügens und des gleichen zum Löschen
quelle
quelle
Würde das nicht funktionieren?
Jeder unausgeglichene Baum würde dies immer scheitern lassen.
quelle