3. Gegeben ist der folgende binäre Baum:

graph TD;
    5 --> 3;
    5 --> 9;
    9 --> 6;
    9 --> 11;
    6 --> 7;
    11 --> 10;

a) Bestimmen Sie jeweils die Durchlaufreihenfolge für die drei Traversierungsvarianten Preorder, Inorder und Postorder.

b) Gegeben sind die Preorder- und die Inordertraversierung eines binären Baumes.

Aufgabenstellung

Preorder: 1, 2, 4, 3, 5, 6, 7
Inorder: 4, 2, 1, 6, 5, 7, 3

Rekonstruieren Sie den Aufbau des binären Baums und stellen Sie ihn mit Knoten und Kanten dar.

Anleitung

  • Wir wissen, dass in Preorder die erste Zahl immer die Wurzel ist (hier → 1)
  • Wir schauen nun in Inorder wo die 1 steht
  • Alles Links von der 1 ist im linken Teilbaum, alles rechts davon im rechten Teilbaum
			1
		  /   \
	{4,2}	  {6,5,7,3}
  • Zur Hilfe streiche nun die Zahlen durch:
  • Gehe jetzt die Preorder Traversierung weiter durch und wir sehen als nächstes kommt 2 dran
  • Wir machen nun das gleiche für die 2
  • Wir schauen wo die 2 in der Inorder Schreibweise steht und markieren uns was links und rechts von steht
  • links davon steht die und rechts davon die , weil die schon benutzt wurde () können wir sie ignorieren
			1
		  /   \
		 2    {6,5,7,3}
		/       
	   4 	  
  • wir markieren die 2 nun als erledigt
  • wiederhole nun das ganze bis wir fertig sind
  • 4 hat keine freien Nachbarn mehr also fertig
			1
		  /   \
		 2     3
		/     / 
	   4 	{6,5,7}  
			1
		  /   \
		 2     3
		/     / 
	   4 	 5
	       /   \
          6     7
  • Da 6 und 7 keine freien Nachbarn in Inorder haben sind wir fertig

Finaler Baum:

			1
		  /   \
		 2     3
		/     / 
	   4 	 5
	       /  \
          6     7

Zusatzfrage 1: Was ist die Postordertraversierung davon?

Zusatzfrage 2: BB von Post-Order und In-Order

  • sehr ähnlich zu Pre-Order und In-Order
  • Wir fangen nur nun von rechts an statt von links, da bei Post-Order die Wurzel ganz rechts ist

Lösung

  1. Die letzte Zahl der Postorder ist immer die Wurzel des Baumes. Hier also:

    In der Inorder-Liste steht 1 an folgender Stelle:

    • Alles links von 1 gehört zum linken Teilbaum (4, 2).
    • Alles rechts von 1 gehört zum rechten Teilbaum (6, 5, 7, 3).
         1
       /   \
   {4,2}   {6,5,7,3}
  1. Die nächste Wurzel aus der Postorder ist 3:

    In der Inorder-Liste steht 3 hier:

    • Links von 3 steht (6, 5, 7), das der linke Teilbaum von 3 ist.
         1
       /   \
 . {4,2}    3
           /
      {6,5,7}
  1. Nun nehmen wir 5 aus der Postorder:

    In der Inorder-Liste steht 5 hier:

    • Links von 5 steht 6, das der linke Teilbaum von 5 ist.
    • Rechts von 5 steht 7, das der rechte Teilbaum von 5 ist.
         1
       /   \
   {4,2}    3
           /
          5
         / \
        6   7
  1. Als nächstes kommt 7:

    • 7 hat keine weiteren Kinder in der Inorder-Liste.
         1
       /   \
   {4,2}    3
           /
          5
         / \
        6   7
  1. Jetzt nehmen wir 6 aus der Postorder:

    • 6 hat keine weiteren Kinder, da es in der Inorder-Liste alleine steht.
         1
       /   \
   {4,2}    3
           /
          5
         / \
        6   7
  1. Zum Schluss verarbeiten wir den linken Teilbaum. Die nächste Zahl aus der Postorder ist 2:

    In der Inorder-Liste steht 2 hier:

    • Links von 2 steht 4, das der linke Teilbaum von 2 ist.
         1
       /   \
      2     3
     /     /
    4     5
         / \
        6   7
  1. Zum Schluss verarbeiten wir 4:

    • 4 hat keine weiteren Kinder.
         1
       /   \
      2     3
     /     /
    4     5
         / \
        6   7

Fazit

Der rekonstruierte Baum sieht so aus:

         1
       /   \
      2     3
     /     /
    4     5
         / \
        6   7

Zusatzfrage 3: Pre-Order und Post-Order

Achtung: Mehrdeutigkeit bei Preorder und Postorder

Die Rekonstruktion eines binären Baums nur anhand der Preorder- und Postorder-Traversierung ist nicht eindeutig. Beide Traversierungen liefern nicht genügend Informationen, um die Baumstruktur in allen Fällen klar zu definieren, insbesondere bei:

  • Knoten mit nur einem Kind (einseitige Bäume),
  • Situationen, in denen die Reihenfolge der Kinder unklar ist.

Das bedeutet, dass Preorder und Postorder zu mehreren möglichen Baumstrukturen führen können. Um einen binären Baum eindeutig zu rekonstruieren, ist die Inorder-Traversierung oder zusätzliche Informationen erforderlich.

Extraressource für die Binärbaumkonstruktion falls immernoch unklar

c) Implementieren Sie rekursiv die drei Traversierungsmethoden. Das Bearbeiten eines Knotens soll dabei darin bestehen, dass sein Inhalt auf dem Bildschirm ausgegeben wird.

Lösung

1. Knotenklasse erstellen:

Die Node-Klasse dient als Grundstruktur für die Knoten im Binärbaum. Jeder Knoten hat drei Attribute:

  • value: der Wert des Knotens (in deinem Beispiel ein Buchstabe).
  • left: Verweis auf den linken Kindknoten.
  • right: Verweis auf den rechten Kindknoten.
// Node.java
 
public class Node {  
    String value;  
    Node left;  
    Node right;  
  
    public Node(String value) {  
        this.value = value;  
    }  
}
  • Erstelle nun eine Klasse BinaryTreeSolution.java
public class BinaryTreeSolution {
 
}

2. Daten für den Binärbaum hinzufügen:

Hier wird eine Funktion erstellt, die den Baum aufbaut, indem Knoten erstellt und miteinander verbunden werden. Diese Funktion gibt den Wurzelknoten (Knoten “a”) zurück.

  • Nun fügen wir Nutzdaten in unseren BinaryTree hinzu
  • Dafür sei für dieses Beispiel folgender Baum gegeben:
        a
       / \
      b   g
     / \
    c   d
   /
  e
   \
    f
 
  • Dies implementieren wir nun in unseren Code
public class BinaryTreeSolution {
public static Node createData_alphabet() {  
    Node a = new Node("a");  
    Node b = new Node("b");  
    Node c = new Node("c");  
    Node d = new Node("d");  
    Node e = new Node("e");  
    Node f = new Node("f");  
    Node g = new Node("g");  
  
    a.left = b;  
    a.right = g;  
    b.left = c;  
    b.right = d;  
    c.left = e;  
    e.right = f;  
  
  
    return a;  
	}
}

3. Traversiermethoden:

Beim Traversieren eines Binärbaums geht es darum, alle Knoten des Baumes in einer bestimmten Reihenfolge zu besuchen. Die drei gängigen Methoden sind Preorder, Inorder und Postorder.

a) Preorder-Traversierung:

Beim Preorder-Ansatz wird zuerst der aktuelle Knoten bearbeitet, danach der linke und schließlich der rechte Kindknoten.

  • Reihenfolge: Knoten → Linker Teilbaum → Rechter Teilbaum.
public static void preorderTraversal(Node n) {  
    if (n == null) {  
        return;  
    }  
    System.out.print(n.value + " ");  // Verarbeite den Knoten
    preorderTraversal(n.left);        // Traversiere den linken Teilbaum
    preorderTraversal(n.right);       // Traversiere den rechten Teilbaum
}
b) Postorder-Traversierung:

Bei der Postorder-Traversierung wird zuerst der linke Teilbaum, dann der rechte Teilbaum und schließlich der aktuelle Knoten verarbeitet.

  • Reihenfolge: Linker Teilbaum → Rechter Teilbaum → Knoten.
public static void postorderTraversal(Node n) {  
    if (n == null) {  
        return;  
    }  
    postorderTraversal(n.left);       // Traversiere den linken Teilbaum
    postorderTraversal(n.right);      // Traversiere den rechten Teilbaum
    System.out.print(n.value + " ");  // Verarbeite den Knoten
}
c) Inorder-Traversierung:

Inorder bedeutet, zuerst den linken Teilbaum zu durchlaufen, dann den Knoten zu verarbeiten und danach den rechten Teilbaum zu durchlaufen.

  • Reihenfolge: Linker Teilbaum → Knoten → Rechter Teilbaum.
public static void inorderTraversal(Node n) {  
    if (n == null) {  
        return;  
    }  
    inorderTraversal(n.left);         // Traversiere den linken Teilbaum
    System.out.print(n.value + " ");  // Verarbeite den Knoten
    inorderTraversal(n.right);        // Traversiere den rechten Teilbaum
}

Rekursion:

Die Traversiermethoden funktionieren rekursiv, was bedeutet, dass eine Funktion sich selbst aufruft, um das Problem zu lösen. In diesem Fall rufen die Traversierfunktionen sich selbst für den linken und rechten Teilbaum eines Knotens auf.

Beispiel Preorder:

  • Starte bei Knoten “a”.
  • Gib “a” aus.
  • Rekursiver Aufruf für den linken Teilbaum (Knoten “b”).
  • Gib “b” aus und rufe die Methode für den linken Teilbaum von “b” auf (Knoten “c”).
  • Dieser Prozess wiederholt sich für jeden Knoten.

Durch diese rekursive Struktur wird sichergestellt, dass alle Knoten in der richtigen Reihenfolge durchlaufen werden.

  • Unsere BinaryTreeSolution Klasse sieht also nun so aus:
public class BinaryTreeSolution {
public static Node createData_alphabet() {  
    Node a = new Node("a");  
    Node b = new Node("b");  
    Node c = new Node("c");  
    Node d = new Node("d");  
    Node e = new Node("e");  
    Node f = new Node("f");  
    Node g = new Node("g");  
  
    a.left = b;  
    a.right = g;  
    b.left = c;  
    b.right = d;  
    c.left = e;  
    e.right = f;  
  
  
    return a;  
	}
	
public static void preorderTraversal(Node n) {  
    if (n == null) {  
        return;  
    }  
    System.out.print(n.value + " ");  
    preorderTraversal(n.left);  
    preorderTraversal(n.right);  
}  
  
public static void postorderTraversal(Node n) {  
    if (n == null) {  
        return;  
    }  
    postorderTraversal(n.left);  
    postorderTraversal(n.right);  
    System.out.print(n.value + " ");  
}  
  
public static void inorderTraversal(Node n) {  
    if (n == null) {  
        return;  
    }  
    inorderTraversal(n.left);  
    System.out.print(n.value + " ");  
    inorderTraversal(n.right);  
}
}
  • Um zu testen, ob sie auch gut funktioniert, fügen wir eine public static void main(String[] args) {} Methode hinzu
public class BinaryTreeSolution {  
  
  
    public static Node createData_alphabet() {  
        Node a = new Node("a");  
        Node b = new Node("b");  
        Node c = new Node("c");  
        Node d = new Node("d");  
        Node e = new Node("e");  
        Node f = new Node("f");  
        Node g = new Node("g");  
  
        a.left = b;  
        a.right = g;  
        b.left = c;  
        b.right = d;  
        c.left = e;  
        e.right = f;  
  
  
        return a;  
    }  
  
 
  
    public static void preorderTraversal(Node n) {  
        if (n == null) {  
            return;  
        }  
        System.out.print(n.value + " ");  
        preorderTraversal(n.left);  
        preorderTraversal(n.right);  
    }  
  
    public static void postorderTraversal(Node n) {  
        if (n == null) {  
            return;  
        }  
        postorderTraversal(n.left);  
        postorderTraversal(n.right);  
        System.out.print(n.value + " ");  
    }  
  
    public static void inorderTraversal(Node n) {  
        if (n == null) {  
            return;  
        }  
        inorderTraversal(n.left);  
        System.out.print(n.value + " ");  
        inorderTraversal(n.right);  
    }  
  
  
    public static void main(String[] args) {  
        Node data_alphabet = createData_alphabet();  
 
        preorderTraversal(data_alphabet);
        System.out.println();  
        postorderTraversal(data_alphabet);  
        System.out.println();
        inorderTraversal(data_alphabet);  
 
    }  
}
  • Als Ausgabe in der Kommandozeile/Terminal erhalten wir
a b c e f d g 
f e c d b g a 
e f c b d a g 

d) Gegeben ist die folgende Methode:

public void wasTueIch(BinaryTree<Integer> b) {
    Stack<BinaryTree<Integer>> stapel = 
        new Stack<BinaryTree<Integer>>();
    
    while (!stapel.isEmpty() || !b.isEmpty()) {
        if (!b.isEmpty()) {
            stapel.push(b);
            b = b.getLeftTree();
        } else {
            b = stapel.top();
            stapel.pop();
            System.out.println(b.getContent());
            b = b.getRightTree();
        }
    }
}

Aufgabenstellung

Analysieren Sie die Arbeitsweise der Methode wasTueich, indem Sie die Bele- gungen des Stapels sowie die Ausgabe auf dem Bildschirm für den abgebildeten Baum notieren. Erläutern Sie, was die Methode leistet.

public void wasTueIch(BinaryTree<Integer> b) {
    // Erstelle einen leeren Stapel zum Speichern der Knoten
    Stack<BinaryTree<Integer>> stapel = new Stack<BinaryTree<Integer>>();
 
    // Solange entweder der Stapel nicht leer ist oder der aktuelle Baum nicht leer ist
    while (!stapel.isEmpty() || !b.isEmpty()) {
        if (!b.isEmpty()) {
            // Wenn der aktuelle Knoten existiert (nicht null ist)
            // - Füge ihn dem Stapel hinzu für späteren Zugriff
            stapel.push(b);
            // - Bewege dich zum linken Kindknoten
            b = b.getLeftTree();
        } else {
            // Wenn der aktuelle Knoten null ist (kein linker Kindknoten mehr)
            // - Hole den obersten Knoten vom Stapel (zurück zum Elternknoten)
            b = stapel.top();
            stapel.pop();
            // - Gib den Inhalt des Knotens aus (Verarbeitung des Knotens)
            System.out.println(b.getContent());
            // - Bewege dich zum rechten Kindknoten
            b = b.getRightTree();
        }
    }
}
 

Die Methode wasTueIch führt eine in-order Traversierung (symmetrischer Durchlauf) des binären Baums b durch und gibt die Inhalte der Knoten in dieser Reihenfolge auf dem Bildschirm aus.

Arbeitsweise der Methode:

  1. Initialisierung:

    • Ein leerer Stapel stapel wird erstellt.
    • Die Methode beginnt mit dem übergebenen Baum b.
  2. Schleife:

    • Die while-Schleife läuft, solange entweder der Stapel nicht leer ist oder der aktuelle Knoten b nicht leer ist.
    • Wenn b nicht leer ist:
      • Der aktuelle Knoten b wird auf den Stapel gelegt.
      • b wird auf seinen linken Kindknoten gesetzt (b = b.getLeftTree()).
      • Dies wiederholt sich, bis der linke äußerste Knoten erreicht ist.
    • Wenn b leer ist:
      • Der oberste Knoten wird vom Stapel genommen (stapel.pop()).
      • Der Inhalt dieses Knotens wird ausgegeben (System.out.println(b.getContent())).
      • b wird auf seinen rechten Kindknoten gesetzt (b = b.getRightTree()).

Erläuterung:

  • Die Methode traversiert den Baum so weit wie möglich nach links und speichert dabei die Knoten auf dem Stapel.
  • Wenn kein linker Knoten mehr vorhanden ist, wird der letzte gespeicherte Knoten vom Stapel genommen, sein Wert ausgegeben, und die Traversierung setzt sich mit seinem rechten Teilbaum fort.
  • Dieses Vorgehen führt dazu, dass die Knoten in der Reihenfolge “links - Wurzel - rechts” besucht werden.

Fazit:

Die Methode wasTueIch realisiert eine in-order Traversierung des binären Baums und gibt dabei die Inhalte der Knoten in aufsteigender Reihenfolge aus (sofern der Baum sortiert ist). Diese Traversierungsmethode wird häufig verwendet, um die Elemente eines binären Suchbaums sortiert auszugeben.

e) Entwickeln Sie eine Strategie für eine Methode, die die Anzahl aller Knoten eines binären Baums bestimmt, Implementieren Sie Ihre Methode.

public static int countNodes(Node n) {  
    if (n == null) return 0;  
    return 1 + countNodes(n.left) + countNodes(n.right);  
}