Der Binärbaum hier muss nicht unbedingt ein Binärsuchbaum sein.
Die Struktur könnte angenommen werden als -
struct node {
int data;
struct node *left;
struct node *right;
};
Die maximale Lösung, die ich mit einem Freund finden konnte, war etwas in dieser Art -
Betrachten Sie diesen Binärbaum :
Die Inorder Traversal ergibt - 8, 4, 9, 2, 5, 1, 6, 3, 7
Und die Nachbestellungsdurchquerung ergibt - 8, 9, 4, 5, 2, 6, 7, 3, 1
Wenn wir zum Beispiel den gemeinsamen Vorfahren der Knoten 8 und 5 finden wollen, erstellen wir eine Liste aller Knoten, die zwischen 8 und 5 in der Inorder-Tree-Traversal liegen, in diesem Fall [4, 9 , 2]. Dann prüfen wir, welcher Knoten in dieser Liste zuletzt in der Nachbestellungsdurchquerung erscheint, nämlich 2. Daher ist der gemeinsame Vorfahr für 8 und 5 2.
Die Komplexität für diesen Algorithmus ist meines Erachtens O (n) (O (n) für Inorder / Postorder-Traversale, der Rest der Schritte ist wiederum O (n), da es sich nur um einfache Iterationen in Arrays handelt. Es besteht jedoch eine große Wahrscheinlichkeit, dass dies falsch ist. :-)
Aber dies ist ein sehr grober Ansatz, und ich bin mir nicht sicher, ob er in einigen Fällen zusammenbricht. Gibt es eine andere (möglicherweise optimalere) Lösung für dieses Problem?
Antworten:
Nick Johnson hat Recht, dass ein O (n) -Zeitkomplexitätsalgorithmus das Beste ist, was Sie tun können, wenn Sie keine übergeordneten Zeiger haben.) Eine einfache rekursive Version dieses Algorithmus finden Sie im Code in Kindings Beitrag , der in O (n) -Zeit ausgeführt wird .
Beachten Sie jedoch, dass ein verbesserter Algorithmus möglich ist, wenn Ihre Knoten übergeordnete Zeiger haben. Erstellen Sie für beide fraglichen Knoten eine Liste mit dem Pfad von der Wurzel zum Knoten, indem Sie am Knoten beginnen und das übergeordnete Element von vorne einfügen.
Für 8 in Ihrem Beispiel erhalten Sie (zeigt Schritte): {4}, {2, 4}, {1, 2, 4}
Machen Sie dasselbe für Ihren anderen fraglichen Knoten, was zu (nicht gezeigten Schritten) führt: {1, 2}
Vergleichen Sie nun die beiden Listen, die Sie erstellt haben, und suchen Sie nach dem ersten Element, bei dem sich die Liste unterscheidet, oder nach dem letzten Element einer der Listen, je nachdem, was zuerst eintritt.
Dieser Algorithmus benötigt O (h) Zeit, wobei h die Höhe des Baums ist. Im schlimmsten Fall entspricht O (h) O (n), aber wenn der Baum ausgeglichen ist, ist dies nur O (log (n)). Es benötigt auch O (h) Platz. Es ist eine verbesserte Version möglich, die nur konstanten Speicherplatz verwendet, wobei der Code im Beitrag von CEGRD angezeigt wird
Unabhängig davon, wie der Baum aufgebaut ist, können Sie andere Algorithmen verwenden, die eine O (n) [lineare] Zeitvorbereitung erfordern, aber dann eine finden, wenn dies eine Operation ist, die Sie mehrmals am Baum ausführen, ohne sie dazwischen zu ändern Paar benötigt nur O (1) [konstant] Zeit. Verweise auf diese Algorithmen finden Sie auf der Seite mit dem niedrigsten Problem mit gemeinsamen Vorfahren auf Wikipedia . (Dank an Jason für die ursprüngliche Veröffentlichung dieses Links)
quelle
O(h)
ist nur,O(log(n))
wenn der Baum ausgeglichen ist. Ob binär oder nicht, für jeden Baum können Sie, wenn Sie übergeordnete Zeiger haben, den Pfad von einem Blatt zur WurzelO(h)
rechtzeitig bestimmen , indem Sie einfach dem übergeordneten Zeiger bis zu bestimmtenh
Zeiten folgen . Das gibt Ihnen den Weg vom Blatt zur Wurzel. Wenn die Pfade als Stapel gespeichert sind, erhalten Sie durch Iterieren des Stapels den Pfad von der Wurzel zum Blatt. Wenn Ihnen übergeordnete Zeiger fehlen und Sie keine spezielle Struktur für den Baum haben, dauert es einige Zeit, den Pfad von der Wurzel zum Blatt zu findenO(n)
.Ausgehend vom
root
Knoten und nach unten bewegend, wenn Sie einen Knoten finden, der entwederp
oderq
als direktes untergeordnetes Element hat, dann ist es die Ökobilanz. (edit - dies sollte , wenn seinp
oderq
ist der Wert des Knotens, es zurückgeben Sonst wird es fehlschlagen , wenn man von.p
oderq
ein direktes Kind des anderen ist.)Andernfalls ist es die Ökobilanz, wenn Sie einen Knoten mit
p
seinem rechten (oder linken) Teilbaum undq
seinem linken (oder rechten) Teilbaum finden.Der feste Code sieht folgendermaßen aus:
Der folgende Code schlägt fehl, wenn einer der beiden das direkte Kind eines anderen ist.
Code in Aktion
quelle
Hier ist der Arbeitscode in JAVA
quelle
Die bisher gegebenen Antworten verwenden eine Rekursion oder speichern beispielsweise einen Pfad im Speicher.
Beide Ansätze können fehlschlagen, wenn Sie einen sehr tiefen Baum haben.
Hier ist meine Meinung zu dieser Frage. Wenn wir die Tiefe (Abstand von der Wurzel) beider Knoten überprüfen und diese gleich sind, können wir uns sicher von beiden Knoten nach oben zum gemeinsamen Vorfahren bewegen. Wenn eine der Tiefen größer ist, sollten wir uns vom tieferen Knoten nach oben bewegen, während wir im anderen bleiben.
Hier ist der Code:
Die zeitliche Komplexität dieses Algorithmus ist: O (n). Die räumliche Komplexität dieses Algorithmus ist: O (1).
In Bezug auf die Berechnung der Tiefe können wir uns zunächst an die Definition erinnern: Wenn v Wurzel ist, ist Tiefe (v) = 0; Andernfalls ist Tiefe (v) = Tiefe (Eltern (v)) + 1. Wir können die Tiefe wie folgt berechnen:
quelle
Nun, diese Art hängt davon ab, wie Ihr Binärbaum strukturiert ist. Vermutlich haben Sie eine Möglichkeit, den gewünschten Blattknoten anhand der Wurzel des Baums zu finden. Wenden Sie diesen einfach auf beide Werte an, bis die von Ihnen ausgewählten Zweige voneinander abweichen.
Wenn Sie angesichts der Wurzel keine Möglichkeit haben, das gewünschte Blatt zu finden, ist Ihre einzige Lösung - sowohl im normalen Betrieb als auch zum Auffinden des letzten gemeinsamen Knotens - eine Brute-Force-Suche des Baums.
quelle
Dies finden Sie unter: - http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
quelle
Tarjans Offline-Algorithmus für die am wenigsten verbreiteten Vorfahren ist gut genug (vgl. Auch Wikipedia ). Es gibt mehr über das Problem (das niedrigste gemeinsame Vorfahrenproblem) auf Wikipedia .
quelle
So ermitteln Sie den gemeinsamen Vorfahren zweier Knoten: -
Dies würde für den binären Suchbaum funktionieren.
quelle
Ich habe einen Versuch mit illustrativen Bildern und Arbeitscode in Java gemacht,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
quelle
Der folgende rekursive Algorithmus wird in O (log N) für einen ausgeglichenen Binärbaum ausgeführt. Wenn einer der an die Funktion getLCA () übergebenen Knoten mit dem Stamm identisch ist, ist der Stamm die Ökobilanz, und es ist keine erneute Diskussion erforderlich.
Testfälle. [1] Beide Knoten n1 und n2 befinden sich im Baum und befinden sich auf beiden Seiten ihres übergeordneten Knotens. [2] Entweder ist der Knoten n1 oder n2 die Wurzel, die Ökobilanz ist die Wurzel. [3] Nur n1 oder n2 befindet sich im Baum. Die Ökobilanz ist entweder der Wurzelknoten des linken Teilbaums der Baumwurzel oder die Ökobilanz ist der Wurzelknoten des rechten Teilbaums der Baumwurzel.
[4] Weder n1 noch n2 befinden sich im Baum, es gibt keine Ökobilanz. [5] Sowohl n1 als auch n2 liegen in einer geraden Linie nebeneinander. Die Ökobilanz besteht entweder aus n1 oder n2, je nachdem, welcher Wert nahe an der Wurzel des Baums liegt.
quelle
Gehen Sie einfach vom ganzen Baum herunter,
root
solange beide Knoten vorhanden sind, sagen wirp
undq
, für die Ancestor gefunden werden muss, im selben Unterbaum befinden (was bedeutet, dass ihre Werte beide kleiner oder beide größer als die Wurzeln sind).Dies geht direkt von der Wurzel zum kleinsten gemeinsamen Vorfahren, ohne auf den Rest des Baumes zu schauen, also ist es so schnell wie es nur geht. Ein paar Möglichkeiten, es zu tun.
im Falle eines Überlaufs würde ich tun (root.val - (long) p.val) * (root.val - (long) q.val)
quelle
quelle
Betrachten Sie diesen Baum
Wenn wir Nachbestellungen und Vorbestellungen durchführen und den ersten gemeinsamen Vorgänger und Nachfolger finden, erhalten wir den gemeinsamen Vorfahren.
Nachbestellung => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 Vorbestellung => 7,3,1,0,2,6,4 5,12,9,8,11,10,13,15,14
Am wenigsten gemeinsamer Vorfahr von 8,11
in der Nachbestellung haben wir => 9,14,15,13,12,7 nach 8 & 11 in der Vorbestellung haben wir => 7,3,1,0,2,6,4,5,12,9 vor 8 & 11
9 ist die erste gebräuchliche Zahl, die nach 8 und 11 in der Nachbestellung und vor 8 und 11 in der Vorbestellung auftritt, daher ist 9 die Antwort
Am wenigsten gemeinsamer Vorfahr von 5,10
11,9,14,15,13,12,7 in Nachbestellung 7,3,1,0,2,6,4 in Vorbestellung
7 ist die erste Zahl, die nach 5,10 in der Nachbestellung und vor 5,10 in der Vorbestellung auftritt, daher ist 7 die Antwort
quelle
Wenn es sich um einen vollständigen Binärbaum mit untergeordneten Knoten des Knotens x als 2 * x und 2 * x + 1 handelt, gibt es einen schnelleren Weg, dies zu tun
Wie funktioniert es
Dies funktioniert, weil die größere Zahl im Grunde rekursiv durch zwei geteilt wird, bis beide Zahlen gleich sind. Diese Zahl ist der gemeinsame Vorfahr. Teilen ist effektiv die richtige Verschiebung. Wir müssen also ein gemeinsames Präfix von zwei Zahlen finden, um den nächsten Vorfahren zu finden
quelle
In Scala können Sie:
quelle
quelle
Hier ist die C ++ - Methode. Habe versucht, den Algorithmus so einfach wie möglich zu halten:
Wie man es benutzt:
quelle
Der einfachste Weg, den niedrigsten gemeinsamen Vorfahren zu finden, ist der folgende Algorithmus:
quelle
Ich habe eine Lösung gefunden
Abhängig von 3 Durchquerungen können Sie entscheiden, wer die Ökobilanz ist. Von der Ökobilanz finden Sie die Entfernung beider Knoten. Addiere diese beiden Abstände, das ist die Antwort.
quelle
Hier ist was ich denke,
Komplexität: Schritt 1: O (n), Schritt 2 = ~ O (n), gesamt = ~ O (n).
quelle
Hier sind zwei Ansätze in c # (.net) (beide oben diskutiert) als Referenz:
Rekursive Version des Findens der Ökobilanz im Binärbaum (O (N) - da höchstens jeder Knoten besucht wird) (Hauptpunkte der Lösung ist die Ökobilanz ist (a) der einzige Knoten im Binärbaum, bei dem sich beide Elemente auf beiden Seiten der Teilbäume befinden (links) und richtig) ist LCA. (b) Und es spielt auch keine Rolle, welcher Knoten auf beiden Seiten vorhanden ist - anfangs habe ich versucht, diese Informationen beizubehalten, und offensichtlich wurde die rekursive Funktion so verwirrend. Als ich sie erkannte, wurde sie sehr elegant.
Das Durchsuchen beider Knoten (O (N)) und das Verfolgen von Pfaden (verwendet zusätzlichen Speicherplatz - daher ist # 1 wahrscheinlich überlegen, auch wenn der Speicherplatz wahrscheinlich vernachlässigbar ist, wenn der Binärbaum gut ausbalanciert ist, da dann nur zusätzlicher Speicherverbrauch erforderlich ist O (log (N)).
so dass die Pfade verglichen werden (im Wesentlichen ähnlich der akzeptierten Antwort - aber die Pfade werden berechnet, indem angenommen wird, dass der Zeigerknoten nicht im binären Baumknoten vorhanden ist)
Nur für den Abschluss ( nicht im Zusammenhang mit Frage ), Ökobilanz in BST (O (log (N))
Tests
Rekursiv:
Dabei wird die oben genannte private rekursive Version mit der folgenden öffentlichen Methode aufgerufen:
Lösung durch Verfolgen der Pfade beider Knoten:
Dabei ist FindNodeAndPath definiert als
BST (LCA) - nicht verwandt (nur zur Vervollständigung als Referenz)
Unit Tests
quelle
Wenn jemand an Pseudocode interessiert ist (für Hausarbeiten an der Universität), ist hier einer.
quelle
Obwohl dies bereits beantwortet wurde, ist dies mein Ansatz für dieses Problem mit der Programmiersprache C. Der Code zeigt zwar einen binären Suchbaum (soweit insert () betroffen ist), aber der Algorithmus funktioniert auch für einen binären Baum. Die Idee ist, alle Knoten, die von Knoten A zu Knoten B liegen, bei der Inorder Traversal zu durchlaufen und die Indizes für diese in der Post Order Traversal nachzuschlagen. Der Knoten mit dem maximalen Index beim Durchlaufen der Nachbestellung ist der niedrigste gemeinsame Vorfahr.
Dies ist ein funktionierender C-Code zum Implementieren einer Funktion zum Finden des niedrigsten gemeinsamen Vorfahren in einem Binärbaum. Ich biete auch alle Dienstprogrammfunktionen usw. an, springe aber zum schnellen Verständnis zu CommonAncestor ().
quelle
Es kann noch einen Ansatz geben. Es ist jedoch nicht so effizient wie das, was bereits in den Antworten vorgeschlagen wurde.
Erstellen Sie einen Pfadvektor für den Knoten n1.
Erstellen Sie einen zweiten Pfadvektor für den Knoten n2.
Ein Pfadvektor, der impliziert, dass die gesetzten Knoten von diesem durchlaufen werden, um den fraglichen Knoten zu erreichen.
Vergleichen Sie beide Pfadvektoren. Der Index, bei dem sie nicht übereinstimmen, gibt den Knoten an diesem Index zurück - 1. Dies würde die Ökobilanz ergeben.
Nachteile für diesen Ansatz:
Sie müssen den Baum zweimal durchlaufen, um die Pfadvektoren zu berechnen. Benötigen Sie zusätzlichen O (h) Speicherplatz, um Pfadvektoren zu speichern.
Dies ist jedoch auch leicht zu implementieren und zu verstehen.
Code zur Berechnung des Pfadvektors:
quelle
Versuchen Sie es so
quelle
Grober Weg:
Das Problem mit der obigen Methode ist, dass wir das "Finden" mehrmals durchführen, dh es besteht die Möglichkeit, dass jeder Knoten mehrmals durchlaufen wird. Wir können dieses Problem lösen, wenn wir die Informationen aufzeichnen können, um sie nicht erneut zu verarbeiten (denken Sie an dynamische Programmierung).
Anstatt jeden Knoten zu finden, führen wir Aufzeichnungen darüber, was bereits gefunden wurde.
Besserer Weg:
Code:
quelle
Code für eine Breite Erste Suche, um sicherzustellen, dass sich beide Knoten im Baum befinden. Erst dann fahren Sie mit der LCA-Suche fort. Bitte kommentieren Sie, wenn Sie Verbesserungsvorschläge haben. Ich denke, wir können sie wahrscheinlich als besucht markieren und die Suche an einem bestimmten Punkt neu starten, an dem wir aufgehört haben, um den zweiten Knoten zu verbessern (falls er nicht BESUCHT gefunden wird).
quelle
Sie haben Recht, dass eine Lösung mit Durchquerung ohne einen übergeordneten Knoten eine Komplexität von O (n) Zeit ergibt.
Traversal-Ansatz Angenommen, Sie finden eine Ökobilanz für Knoten A und B. Der einfachste Ansatz besteht darin, zuerst den Pfad von Root zu A und dann den Pfad von Root zu B abzurufen. Sobald Sie diese beiden Pfade haben, können Sie sie problemlos durchlaufen und finde den letzten gemeinsamen Knoten, der der niedrigste gemeinsame Vorfahr von A und B ist.
Rekursive Lösung Ein anderer Ansatz ist die Verwendung der Rekursion. Erstens können wir die Ökobilanz sowohl vom linken als auch vom rechten Baum abrufen (falls vorhanden). Wenn entweder A oder B der Wurzelknoten ist, dann ist die Wurzel die Ökobilanz und wir geben nur die Wurzel zurück, die der Endpunkt der Rekursion ist. Wenn wir den Baum weiter in Unterbäume unterteilen, treffen wir schließlich entweder A oder B.
Um Teilproblemlösungen zu kombinieren, wissen wir, dass sowohl A als auch B im linken Baum lokalisiert sind und der zurückgegebene Knoten das Endergebnis ist, wenn LCA (linker Baum) einen Knoten zurückgibt. Wenn sowohl LCA (links) als auch LCA (rechts) nicht leere Knoten zurückgeben, bedeutet dies, dass sich A und B im linken bzw. rechten Baum befinden. In diesem Fall ist der Wurzelknoten der niedrigste gemeinsame Knoten.
Überprüfen Sie den niedrigsten gemeinsamen Vorfahren auf detaillierte Analyse und Lösung.
quelle
Bei einigen Lösungen wird davon ausgegangen, dass auf den Stammknoten verwiesen wird, bei anderen wird davon ausgegangen, dass der Baum eine BST ist. Das Teilen meiner Lösung mithilfe von Hashmap ohne Bezug auf
root
Knoten und Baum kann BST oder Nicht-BST sein:quelle
Lösung 1: Rekursiv - Schneller
Lösung 2: Iterativ - Verwenden von übergeordneten Zeigern - Langsamer
quelle