Technischer Neustart: Verwendung von Zensical statt MkDocs

This commit is contained in:
2025-12-10 17:17:38 +01:00
commit e24deec4b4
28 changed files with 2764 additions and 0 deletions

232
docs/zentralabitur/baum.md Normal file
View File

@@ -0,0 +1,232 @@
# 2. Baumstrukturen
Bäume gehören zu den wichtigsten Datenstrukturen der Informatik. Sie finden Anwendung in vielen Bereichen, z.B. als Suchbäume zum schnellen Finden von Elementen in geordneten Mengen.
## Binärbaum
Der Binärbaum wird in der Regel als rekursive Datenstruktur betrachtet: Ein Binärbaum besteht demnach aus einem Element (genauer einem Objekt einer zuvor festgelegten Klasse), einem linken und einem rechten Teilbaum. Eine Ordnung ist für einen Binärbaum nicht erforderlich.
### Einen Binärbaum aufbauen
Für ein erstes Beispiel soll der folgende Baum aufgebaut werden:
![](img/baum2.gif)
Zur Vereinfachung wird als Grundklasse für den Baum die Klasse *String* definiert, d.h. die Elemente des Beispielsbaums werden als Zeichenketten gespeichert. Sicherlich wäre eine Modellierung als Zahl naheliegend, doch dann wäre der Einsatz der Wrapper-Klasse Integer (vgl. Abschnitt [Objekte als Datenspeicher](linear.md#objekte-als-datenspeicher)) oder der Entwurf einer neuen Klasse erforderlich.
Der folgende Quellcode baut den Baum *bottom-up* (d.h. von unten nach oben) auf. Eine andere Vorgehensweise ist nicht möglich, da beispielsweise der Gesamtbaum (vgl. Element 23) nur dann erzeugbar ist, wenn linker und rechter Teilbaum (vgl. Elemente 17 und 38) bereits existieren.
```java
BinaryTree<String> k5 = new BinaryTree<String>("5");
BinaryTree<String> k14 = new BinaryTree<String>("14");
BinaryTree<String> k13 = new BinaryTree<String>("13",k5,k14);
BinaryTree<String> k19 = new BinaryTree<String>("19");
BinaryTree<String> k17 = new BinaryTree<String>("17",k13,k19);
BinaryTree<String> k39 = new BinaryTree<String>("39");
BinaryTree<String> k40 = new BinaryTree<String>("40",k39,null);
BinaryTree<String> k24 = new BinaryTree<String>("24");
BinaryTree<String> k38 = new BinaryTree<String>("38",k24,k40);
tree = new BinaryTree<String>("23",k17,k38);
```
### Einen Binärbaum durchsuchen (Tiefensuche)
Beim Durchsuchen eines Binärbaums kommt zunächst die Tiefensuche in Betracht. Hier wird der Baum rekursiv durchsucht, wobei dem linken Teilbaum Vorrang vor dem rechten Teilbaum gegeben wird.
Der Zeitpunkt der Verarbeitung des aktuellen Elements (hier angedeutet durch den Ausdruck auf dem Bildschirm) vor dem ersten Rekursionsaufruf, zwischen den beiden Aufrufen oder nach dem zweiten Rekursionsaufruf beeinflusst zusätzlich die Reihenfolge der Verarbeitung. Die drei Varianten werden als *PreOrder*, *InOrder* bzw. *PostOrder* bezeichnet.
Die am häufigsten eingesetzte Variante ist der InOrder-Durchlauf, da er die Elemente in ihrer natürlichen Reihenfolge verarbeitet. Im obigen Beispiel, das eine Anordnung von Zahlen im Baum vorsieht, würden die Elemente deshalb auch in aufsteigender Reihenfolge abgearbeitet.
```java
public void inorder(BinaryTree<String> tree) {
if (!tree.isEmpty()) {
inorder(tree.getLeftTree());
System.out.println(tree.getContent()); // Ausdrucken
inorder(tree.getRightTree());
}
}
```
### Einen Binärbaum durchsuchen (Breitensuche)
Als Alternative zur Tiefensuche steht die Breitensuche zur Verfügung, die die Elemente ebenenweise abarbeitet.
![](img/baum2.gif)
Für den Beispielbaum würde die Reihenfolge der Verarbeitung dann *23, 17-38, 13-19-24-40, 5-14-39* lauten. Um die Ebenen abzubilden, wird eine [Schlange](linear.md#stack-und-queue) als Hilfsdatenstruktur verwendet.
```java
public void levelorder(BinaryTree<String> tree) {
Queue<BinaryTree<String>> q = new Queue<BinaryTree<String>>();
q.enqueue(tree);
while (!q.isEmpty()) {
BinaryTree<String> t = q.front();
q.dequeue();
System.out.println(t.getContent()); // Ausdrucken
if (!t.getLeftTree().isEmpty()) {
q.enqueue(t.getLeftTree());
}
if (!t.getRightTree().isEmpty()) {
q.enqueue(t.getRightTree());
}
}
}
```
### Einen Binärbaum verarbeiten (mit Rückgabewert)
Zum Abschluss soll hier ein komplexeres Beispiel präsentiert werden. Zur Illustration dient der nachfolgende Baum. Die Aufgabe besteht darin, die Tiefe des Baums (d.h. die Anzahl der benutzten Ebenen, im Beispiel 4) zu berechnen.
![](img/baum2.gif)
Die kompakte rekursive Methode hat es durchaus in sich: Ist der aktuelle Teilbaum leer, so ist seine Tiefe 0. Ansonsten ist seine Tiefe um 1 größer als die maximale Tiefe des linken oder des rechten Teilbaums - je nachdem, welcher von beiden die größere Tiefe besitzt.
```java
public int depth(BinaryTree<String> tree) {
if (tree.isEmpty()) {
return 0;
}
else {
int left = depth(tree.getLeftTree());
int right = depth(tree.getRightTree());
if (left>right) {
return left+1;
}
else {
return right+1;
}
}
}
```
## Suchbaum
Der Suchbaum stellt einen guten Kompromiss aus den Datenstrukturen Feld und Liste dar: Er ist dynamisch, erlaubt also jederzeit nachträgliches Einfügen oder Löschen ohne zusätzlichen Speicherbedarf. Er ermöglicht zugleich eine schnelle Suche, da die Idee der binären Suche übertragbar ist, wodurch sich logarithmischer Suchaufwand ergibt.
### Eine Klasse zum Suchen vorbereiten
Voraussetzung für den Aufbau eines Suchbaums ist eine Ordnung der zu verwaltenden Elemente. Für die Klasse *BinarySearchTree* wird die erforderliche Ordnung so realisiert, dass für die Basisklasse der zu speichernden Objekte gefordert wird, dass sie das Interface *ComparableContent* implementiert. In diesem Interface wird mit den drei Methoden *isLess()*, *isGreater()* und *isEqual()* die Vergleichbarkeit der Elemente verankert.
```java
public interface ComparableContent<ContentType> {
public boolean isGreater(ContentType pContent);
public boolean isEqual(ContentType pContent);
public boolean isLess(ContentType pContent);
}
```
Die folgende Beispielklasse *Entry* speichert für jedes Objekt exemplarisch ein ganzzahliges Attribut, das über eine get-Methode abgefragt werden kann. In den drei Vergleichsmethoden muss ein Objekt die Frage beantworten, wie es sich selbst gegenüber einem zweiten Objekt (hier *pContent*) der gleichen Klasse einordnet.
```java
public class Entry implements ComparableContent<Entry> {
private int wert;
public Entry(int pWert) {
this.wert = pWert;
}
public boolean isLess(Entry pContent) {
return wert < pContent.getWert();
}
public boolean isEqual(Entry pContent) {
return wert == pContent.getWert();
}
public boolean isGreater(Entry pContent) {
return wert > pContent.getWert();
}
public int getWert() {
return wert;
}
}
```
### Einen Suchbaum aufbauen
Steht die Basisklasse, so steht dem Aufbau des Suchbaums nichts mehr im Wege. Im nachfolgenden Beispiel werden nacheinander die Elemente 45, 25 usw. in den Suchbaum eingefügt. Für das geordnete Einfügen sorgt die Methode *insert*.
Somit ergibt sich der folgende Beispielsuchbaum:
![](img/baum1.png)
```java
Entry int1 = new Entry(45);
Entry int2 = new Entry(25);
Entry int3 = new Entry(65);
Entry int4 = new Entry(15);
Entry int5 = new Entry(35);
tree = new BinarySearchTree<Entry>();
tree.insert(int1);
tree.insert(int2);
tree.insert(int3);
tree.insert(int4);
tree.insert(int5);
```
### In einem Suchbaum suchen
Um nun ein Element im Suchbaum zu suchen, ist zunächst ein Such-Objekt der Basisklasse (in unserem Beispiel *Entry*) anzulegen. Kann die Methode *search()* ein passendes Objekt im Baum finden, so wird dieses Objekt zurückgegeben. Bleibt die Suche erfolglos, so gibt sie *null* zurück. Eine Fallunterscheidung kann die beiden Fälle trennen.
```java
Entry suchitem = new Entry(77);
Entry ergitem = tree.search(suchitem);
if (ergitem!=null) {
System.out.println("Ja");
}
else {
System.out.println("Nein");
}
```
### Einen Suchbaum durchlaufen
Analog zum Binärbaum kann auch der Suchbaum rekursiv durchsucht werden. Diese Möglichkeit ist aber vorrangig für Debugging-Zwecke (z.B. zum Ausdrucken des Suchbaum-Inhalts) interessant.
```java
private void durchlaufeBaum(BinarySearchTree<Entry> mytree) {
if (!mytree.isEmpty()) {
durchlaufeBaum(mytree.getLeftTree());
System.out.println(mytree.getContent());
durchlaufeBaum(mytree.getRightTree());
}
}
```