Programming lesson
Binärbaum-Nachrichtenrekonstruktion: Eine praktische Anleitung für Com S 2280
Lerne, wie man mit Java einen Binärbaum aus einer Präorder-String-Repräsentation aufbaut, um archivierte Nachrichten zu dekodieren – Schritt für Schritt erklärt mit Beispielen und Tipps für die Hausaufgabe.
Einleitung: Was steckt hinter der archivierten Nachricht?
Stell dir vor, du erhältst eine alte Nachricht, die mit einem Baum-Algorithmus komprimiert wurde. Deine Aufgabe ist es, sie zu entschlüsseln – genau wie bei der Com S 2280 Spring 2025 Project 4: Archived Message Reconstruction. In diesem Tutorial zeigen wir dir, wie du mit Java einen Binärbaum aus einer Präorder-String-Repräsentation erstellst und damit codierte Botschaften dekodierst. Das ist nicht nur eine typische Programmieraufgabe, sondern auch ein cooles Beispiel für Datenkompression – ähnlich wie bei modernen Komprimierungsalgorithmen in Apps oder Spielen.
Im Mai 2026, wo täglich riesige Datenmengen ausgetauscht werden, ist das Verständnis von Binärbäumen und Präorder-Traversierung wichtiger denn je. Egal ob du später an KI-Modellen, Finanz-Apps oder Spiele-Engines arbeitest – dieses Wissen ist Gold wert.
Grundlagen: Der Binärbaum und die Kodierung
Die archivierte Nachricht wird mit einem Binärbaum kodiert. Jeder Blattknoten enthält ein Zeichen, und der Pfad von der Wurzel zum Blatt ergibt die Bitfolge (0 für links, 1 für rechts). Ein Beispielbaum könnte so aussehen:
Wurzel
/ \
a ^
/ \
! ^
/ \
d c
/ \
r b
Die Kodierung ist präfixfrei: Kein Code ist der Anfang eines anderen. Das ermöglicht eindeutiges Dekodieren. In der Datei wird der Baum als Präorder-String dargestellt: '^' kennzeichnet einen inneren Knoten, Buchstaben sind Blätter. Beispiel: ^a^^!^dc^rb.
Das ist ähnlich wie bei Huffman-Kodierung, die in vielen Komprimierungstools steckt. Wenn du schon mal eine ZIP-Datei entpackt hast, hast du indirekt mit solchen Bäumen gearbeitet.
Schritt 1: Die MsgTree-Klasse implementieren
Wir erstellen eine Klasse MsgTree mit den geforderten Attributen:
public class MsgTree {
public char payloadChar;
public MsgTree left;
public MsgTree right;
private static int staticCharIdx = 0;
public MsgTree(String encodingString) {
// Rekursiver Aufbau
}
public MsgTree(char payloadChar) {
this.payloadChar = payloadChar;
this.left = null;
this.right = null;
}
}
Der Konstruktor MsgTree(String encodingString) baut den Baum rekursiv auf. Wir verwenden staticCharIdx, um die aktuelle Position im String zu verfolgen. Bei jedem Aufruf lesen wir ein Zeichen: Ist es '^', erstellen wir einen inneren Knoten und rufen rekursiv linken und rechten Teilbaum auf. Sonst ist es ein Blatt.
public MsgTree(String encodingString) {
if (staticCharIdx >= encodingString.length()) return;
char c = encodingString.charAt(staticCharIdx++);
if (c == '^') {
payloadChar = '^'; // oder ein beliebiges Zeichen
left = new MsgTree(encodingString);
right = new MsgTree(encodingString);
} else {
payloadChar = c;
left = null;
right = null;
}
}
Beachte: Der staticCharIdx muss vor dem ersten Aufruf auf 0 gesetzt werden. Diese rekursive Lösung ist einfach und elegant – perfekt für Einsteiger in die Binärbaum-Programmierung.
Schritt 2: Die Kodierungstabelle ausgeben mit printCodes()
Die Methode printCodes(MsgTree root, String code) durchläuft den Baum preorder und gibt für jedes Blatt das Zeichen und den bisherigen Code aus.
public static void printCodes(MsgTree root, String code) {
if (root == null) return;
if (root.left == null && root.right == null) {
System.out.println(root.payloadChar + " " + code);
} else {
printCodes(root.left, code + "0");
printCodes(root.right, code + "1");
}
}
Das ist ein klassisches rekursives Preorder-Traversal. Die Ausgabe zeigt, welches Zeichen welche Bitfolge hat – nützlich zum Debuggen und Verstehen der Kodierung.
Schritt 3: Die Nachricht dekodieren mit decode()
Die Methode decode(MsgTree codes, String msg) liest die Bitfolge Zeichen für Zeichen. Starte an der Wurzel, folge den Bits (0 = links, 1 = rechts), bis du ein Blatt erreichst, gib das Zeichen aus und beginne wieder von vorne.
public void decode(MsgTree codes, String msg) {
MsgTree current = codes;
for (int i = 0; i < msg.length(); i++) {
char bit = msg.charAt(i);
if (bit == '0') {
current = current.left;
} else {
current = current.right;
}
if (current.left == null && current.right == null) {
System.out.print(current.payloadChar);
current = codes;
}
}
System.out.println();
}
Dieser Algorithmus ist effizient und direkt. Wenn die Bitfolge gültig ist, erhältst du die ursprüngliche Nachricht. In unserem Beispiel: 10110101011101101010100 -> cadbard!.
Schritt 4: Datei einlesen und Sonderfälle behandeln
Die Archivdatei kann zwei oder drei Zeilen haben, wenn ein Zeilenumbruch im Kodierungsbaum vorkommt. Lies die Datei zeilenweise ein, und wenn die erste Zeile mit '^' beginnt, ist es der Anfang des Baums. Füge bei Bedarf die nächste Zeile hinzu, bis der Baum vollständig ist (alle '^' und Buchstaben). Dann lies die letzte Zeile als codierte Nachricht.
Beachte: Der Baum-String kann Leerzeichen enthalten. Diese müssen als Teil des Baums behandelt werden (also als Blatt mit Leerzeichen).
Ein Beispiel für eine Datei mit drei Zeilen:
^ ^a
^^!^dc^rb
10110101011101101010100
Hier ist das erste Zeichen ein '^' (Wurzel), dann ein Leerzeichen (Blatt), dann ein '^' usw. Dein Parser muss das erkennen.
Bonus: Iterativer Baumaufbau (15%)
Für die Extra-Herausforderung kannst du den Baum iterativ aufbauen. Das ist schwieriger, aber eine tolle Übung für fortgeschrittene Java-Programmierung. Du benötigst einen Stack, um den Elternknoten zu speichern, während du den Präorder-String durchläufst. Jedes Mal, wenn du ein '^' siehst, pushst du den Knoten auf den Stack und gehst nach links. Bei einem Blatt popst du vom Stack, bis du einen Knoten findest, der noch keinen rechten Teilbaum hat, und gehst dann nach rechts.
Dieser Ansatz ist vergleichbar mit dem iterativen Preorder-Traversal, das in vielen Algorithmen verwendet wird, z.B. in Compilern oder bei der Syntaxanalyse.
Statistiken ausgeben (5% Extra)
Nach der Dekodierung kannst du Statistiken anzeigen: durchschnittliche Bits pro Zeichen, Gesamtzeichen und Speicherersparnis (angenommen 16 Bit pro unkomprimiertem Zeichen).
double avgBits = (double) totalBits / totalChars;
double savings = (1 - (double) totalBits / (totalChars * 16)) * 100;
System.out.println("STATISTICS:");
System.out.println("Avg bits/char: " + avgBits);
System.out.println("Total characters: " + totalChars);
System.out.println("Space savings: " + savings + "%");
Das gibt dir Einblicke in die Effizienz der Kodierung – ähnlich wie bei Datenkompression in der Praxis.
Zusammenfassung und Tipps
- Rekursive Lösung ist einfacher zu implementieren und zu verstehen.
- Iterative Lösung bringt 15% Bonus, erfordert aber mehr Denkarbeit.
- Achte auf Sonderzeichen wie Leerzeichen und Zeilenumbrüche im Baum.
- Teste mit den bereitgestellten Testdateien aus HW4S2021_Test_Files.zip.
- Nutze Javadoc-Kommentare für jede Klasse.
Mit diesem Wissen bist du bestens gerüstet, um die Com S 2280 Project 4 Aufgabe zu lösen. Viel Erfolg beim Binärbaum-Programmieren!