Binärbaum (Methoden): Unterschied zwischen den Versionen
(8 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 3: | Zeile 3: | ||
[[Kategorie:Informatik-Abitur]] | [[Kategorie:Informatik-Abitur]] | ||
[[Kategorie:Datenstrukturen(IF)]] | [[Kategorie:Datenstrukturen(IF)]] | ||
Hier werden die '''Standard-Strategien''' für das Durchlaufen von [[allgemeiner Binärbaum ab Abi 2017|Binärbäumen]] vorgestellt: | Hier werden die '''Standard-Strategien''' für das Durchlaufen von [[allgemeiner Binärbaum ab Abi 2017|Binärbäumen]] vorgestellt: | ||
Zeile 12: | Zeile 11: | ||
Für das '''Suchen''' und '''Löschen''' von Elementen: siehe [[binärer Suchbaum ab Abi 2017]] | Für das '''Suchen''' und '''Löschen''' von Elementen: siehe [[binärer Suchbaum ab Abi 2017]] | ||
=Erklärvideos= | |||
* [https://youtu.be/to2supzEiO4 rekursive Methoden für Binärbäume implementieren (Youtube)] | |||
* [https://youtu.be/SjniaclqvFs Prinzip und Implementierung der Levelorder-Traversierung (Youtube)] | |||
* [https://youtu.be/UGrrFuV3zjY Pfaddurchlauf durch einen Binärbaum implementieren (Youtube)] | |||
=Fachbegriffe= | =Fachbegriffe= | ||
Rekursion, Abbruchbedingung, rekursiver Aufruf, Sachlogik, Rahmenmethode, Pfaddurchlauf, Linearisierung, Baumliste | Rekursion, Abbruchbedingung, rekursiver Aufruf, Sachlogik, Rahmenmethode, Pfaddurchlauf, Linearisierung, Baumliste | ||
Zeile 56: | Zeile 60: | ||
Das rekursive Vorgehen wird hier beispielhaft an der Methode <code>public int summe(BinaryTree<Integer> pTree)</code> aufgezeigt: | Das rekursive Vorgehen wird hier beispielhaft an der Methode <code>public int summe(BinaryTree<Integer> pTree)</code> aufgezeigt: | ||
<code> | <code> | ||
public int summe(BinaryTree<Integer> pTree){ | public int summe(BinaryTree<Integer> pTree){ | ||
int ergebnis = 0; | int ergebnis = 0; | ||
Zeile 79: | Zeile 83: | ||
return ergebnis; | return ergebnis; | ||
} | } | ||
</code> | </code> | ||
==Durchlaufen eines Pfades== | ==Durchlaufen eines Pfades== | ||
Zeile 93: | Zeile 97: | ||
Als Beispiel wird die Methode '''einfuegen''' für einen mit Zahlen gefüllten Suchbaum implementiert. | Als Beispiel wird die Methode '''einfuegen''' für einen mit Zahlen gefüllten Suchbaum implementiert. | ||
<code> | [https://youtu.be/UGrrFuV3zjY Erklärvideo: Pfaddurchlauf durch einen Binärbaum implementieren (Youtube)] | ||
<code> | |||
public void einfuegen(BinaryTree<Integer> pTree, int pZahl) { | public void einfuegen(BinaryTree<Integer> pTree, int pZahl) { | ||
//Hilfsbaum definieren, damit man pTree nicht "zersägt": | //Hilfsbaum definieren, damit man pTree nicht "zersägt": | ||
Zeile 110: | Zeile 116: | ||
b.setContent(pZahl); | b.setContent(pZahl); | ||
} | } | ||
</code> | </code> | ||
==Linearisierung== | ==Linearisierung== | ||
<font color='red'>'''nur relevant für den Leistungskurs!'''</font> | <font color='red'>'''nur relevant für den Leistungskurs!'''</font> | ||
'''Linearisierung''' ist eine Strategie, wie man rekursive Strukturen (z.B. einen Binärbaum) komplett durchlaufen kann, '''''OHNE eine rekursive Methode zu verwenden'''''. | [https://youtu.be/SjniaclqvFs Erklärvideo: Prinzip und Implementierung der Levelorder-Traversierung] | ||
'''Linearisierung''' ist eine Strategie, wie man rekursive Strukturen (z.B. einen Binärbaum) komplett durchlaufen kann, <br/>'''''OHNE eine rekursive Methode zu verwenden'''''. | |||
===Vorgehensweise=== | ===Vorgehensweise=== | ||
Zeile 139: | Zeile 147: | ||
===Implementierung=== | ===Implementierung=== | ||
<code> | <code> | ||
public List<Object> levelorder(BinaryTree<Object> pTree){ | public List<Object> levelorder(BinaryTree<Object> pTree){ | ||
'''//Linearisierung''' | '''//Linearisierung''' | ||
Zeile 169: | Zeile 177: | ||
return ergebnisListe; | return ergebnisListe; | ||
} | } | ||
</code> | </code> | ||
=Rahmenmethode= | =Rahmenmethode= | ||
Zeile 176: | Zeile 184: | ||
=weitere beispielhafte Methoden= | =weitere beispielhafte Methoden= | ||
==Tiefe== | ==Tiefe== | ||
<code> | <code> | ||
public int tiefe(BinaryTree pTree) { | public int tiefe(BinaryTree pTree) { | ||
int ergebnis = <b>-1</b>; | int ergebnis = <b>-1</b>; | ||
Zeile 195: | Zeile 203: | ||
return ergebnis; | return ergebnis; | ||
} | } | ||
</code> | </code> | ||
==Knotenzahl== | ==Knotenzahl== | ||
<code> | <code> | ||
public int knotenzahl(BinaryTree pTree) { | public int knotenzahl(BinaryTree pTree) { | ||
int ergebnis = 0; | int ergebnis = 0; | ||
Zeile 211: | Zeile 219: | ||
return ergebnis; | return ergebnis; | ||
} | } | ||
</code> | </code> | ||
==Blätter== | ==Blätter== | ||
Zeile 222: | Zeile 230: | ||
* für ein Blatt -> eine Liste mit nur einem Blatt drin zurückgeben | * für ein Blatt -> eine Liste mit nur einem Blatt drin zurückgeben | ||
<code> | <code> | ||
public List blaetter(BinaryTree<Object> pTree) { | public List blaetter(BinaryTree<Object> pTree) { | ||
List ergebnis = new List(); | List<Object> ergebnis = new List<>(); | ||
if (pTree.isEmpty()) { | if (pTree.isEmpty()) { | ||
Zeile 232: | Zeile 240: | ||
if (pTree.getLeftTree().isEmpty() && pTree.getRightTree().isEmpty()){ | if (pTree.getLeftTree().isEmpty() && pTree.getRightTree().isEmpty()){ | ||
// pTree ist ein Blatt! | // pTree ist ein Blatt! | ||
ergebnis.append(pTree); | ergebnis.append(pTree.getContent()); | ||
return ergebnis; | return ergebnis; | ||
} | } | ||
Zeile 244: | Zeile 252: | ||
return ergebnis; | return ergebnis; | ||
} | } | ||
</code> | </code> | ||
==Berechnen== | ==Berechnen== | ||
Zeile 250: | Zeile 258: | ||
Diese Methode berechnet das Ergebnis eines Strukturbaumes aus den Rechenzeichen +, -, *, / und Zahlen. | Diese Methode berechnet das Ergebnis eines Strukturbaumes aus den Rechenzeichen +, -, *, / und Zahlen. | ||
<code> | <code> | ||
public double berechnen(BinaryTree<String> b) { | public double berechnen(BinaryTree<String> b) { | ||
double ergebnis = 0; | double ergebnis = 0; | ||
Zeile 289: | Zeile 297: | ||
return ergebnis; | return ergebnis; | ||
} | } | ||
</code> | </code> |
Aktuelle Version vom 15. Februar 2023, 20:35 Uhr
Hier werden die Standard-Strategien für das Durchlaufen von Binärbäumen vorgestellt:
- Rekursion
- Durchlaufen eines Pfades mit einer
while
-Schleife - Linearisierung
Außerdem wird erklärt, was eine Rahmenmethode ist und wie man sie implementiert.
Für das Suchen und Löschen von Elementen: siehe binärer Suchbaum ab Abi 2017
Erklärvideos
- rekursive Methoden für Binärbäume implementieren (Youtube)
- Prinzip und Implementierung der Levelorder-Traversierung (Youtube)
- Pfaddurchlauf durch einen Binärbaum implementieren (Youtube)
Fachbegriffe
Rekursion, Abbruchbedingung, rekursiver Aufruf, Sachlogik, Rahmenmethode, Pfaddurchlauf, Linearisierung, Baumliste
Standard-Strategien
Für das Durchlaufen von Binärbäumen gibt es drei Standard-Strategien:
- Rekursion
- Durchlaufen eines Pfades mit einer
while
-Schleife - Linearisierung
Rekursion
Binärbäume sind rekursive Datenstrukturen, denn jeder Binärbaum hat zwei Teilbäume - und die sind selber wieder Binärbäume.
Deswegen bietet es sich an, Binärbäume rekursiv zu durchlaufen.
Strategie
Bei der Implementierung einer rekursiven Methode für Binärbäume wird der gesamte Baum nur sehr grob betrachtet: Er besteht aus
- der Wurzel,
- dem linken Teilbaum (für den die Methode rekursiv aufgerufen wird)
- dem rechten Teilbaum (für den die Methode nochmal rekursiv aufgerufen wird).
In der Sachlogik muss man folgende Frage beantworten:
Wie setzt sich das Gesamtergebnis aus der Wurzel, dem Ergebnis des linken Teilbaumes und dem Ergebnis des rechten Teilbaumes zusammen?
Erklärvideo zum Vorgehen bei rekursiven Methoden (11:23min)
Bestandteile einer rekursiven Methode:
- eine Abbruchbedingung oder mehrere Abbruchbedingungen.
Diese hängen vom Sachzusammenhang ab - in der Regel braucht man mindestens eine Abbruchbedingung für einen leeren Binärbaum. - Wurzel auslesen (=Wurzelbehandlung)
- rekursive Aufrufe: Die Methode ruft sich selber auf.
- Meistens braucht man zwei rekursive Aufrufe: einen für den linken Teilbaum und einen für den rechten Teilbaum.
- Bei Methoden, die etwas zurückgeben, muss man sich für den Rückgabewert interessieren!
- Sachlogik: Hier werden die Wurzel und die Ergebnisse der rekursiven Aufrufe behandelt.
Implementierung
Das rekursive Vorgehen wird hier beispielhaft an der Methode public int summe(BinaryTree<Integer> pTree)
aufgezeigt:
public int summe(BinaryTree<Integer> pTree){
int ergebnis = 0;
//Abbruchbedingung
if(pTree.isEmpty()){
return ergebnis;
}
//Wurzel auslesen
int wurzel = pTree.getContent();
//rekursive Aufrufe
int summeLinks = summe(pTree.getLeftTree());
int summeRechts = summe(pTree.getRightTree());
//Sachlogik
ergebnis += wurzel;
ergebnis += summeLinks;
ergebnis += summeRechts;
return ergebnis;
}
Durchlaufen eines Pfades
In manchen Situationen, vor allem in Bäumen mit Suchbaumstruktur, reicht es, wenn man einen Pfad von der Wurzel bis zu einem Blatt durchläuft. Das lässt sich mit einer while
-Schleife realisieren, d.h. Rekursion ist hier nicht nötig.
Strategie
Um einen Pfad in dem Binärbaum pTree
von der Wurzel zu einem Blatt zu durchlaufen, geht man wie folgt vor:
- Es wird eine
while
-Schleife geöffnet, die so lange läuft, bispTree
leer ist. - In der
while
-Schleife wird - abhängig von der Sachlogik - nach links oder nach rechts abgebogen. Das realisiert man, indem manpTree
durch seinen linken oder rechten Teilbaum updated. - Nach Beendigung der
while
-Schleife ist man dann bei einem leeren Knoten unterhalb eines Blattes angekommen.
Implementierung
Als Beispiel wird die Methode einfuegen für einen mit Zahlen gefüllten Suchbaum implementiert.
Erklärvideo: Pfaddurchlauf durch einen Binärbaum implementieren (Youtube)
public void einfuegen(BinaryTree<Integer> pTree, int pZahl) {
//Hilfsbaum definieren, damit man pTree nicht "zersägt":
BinaryTree<Integer> b = pTree;
while(!b.isEmpty()){
int wurzel = b.getContent();
// UPDATE von pTree
if(pZahl < wurzel){
b = b.getLeftTree();
}
else{
b = b.getRightTree();
}
}
// jetzt ist man beim richtigen leeren Knoten angekommen!
b.setContent(pZahl);
}
Linearisierung
nur relevant für den Leistungskurs!
Erklärvideo: Prinzip und Implementierung der Levelorder-Traversierung
Linearisierung ist eine Strategie, wie man rekursive Strukturen (z.B. einen Binärbaum) komplett durchlaufen kann,
OHNE eine rekursive Methode zu verwenden.
Vorgehensweise
Die Vorgehensweise wird hier am Beispiel Levelorder aufgezeigt. In Levelorder wird der Binärbaum "schichtenweise" von oben nach unten durchlaufen, d.h. es handelt sich hier um eine Breitensuche.
Die Idee der Linarisierung ist die folgende:
Linearisierung:
- eine Hilfsliste
baumListe
wird angelegt; in diese Hilfsliste kommen nur Bäume! - der ganze Baum wird in
baumListe
gepackt, d.h.baumListe
hat jetzt ein Element. - dann wird
baumListe
mit einer Schleife von vorne bis zum Ende durchlaufen; dabei wirdbaumListe
ständig ergänzt!- bei jedem Schleifen-Durchlauf wird das aktuelle Element (=ein Baum) aus
baumListe
entnommen. - die beiden Teilbäume (wenn sie nicht leer sind) werden hinten an
baumListe
angehängt.
- bei jedem Schleifen-Durchlauf wird das aktuelle Element (=ein Baum) aus
- Jetzt hat man in
baumListe
eine Liste aller Teilbäume vonpTree
.- Diese Liste kann jetzt für die Sachlogik verwendet werden.
Sachlogik:
- eine Ergebnisliste
ergebnisListe
wird angelegt; inergebnisListe
kommen die Knoten in der Levelorder-Reihenfolge. baumListe
wird mit einer Schleife durchlaufen. Bei jedem Schleifendurchlauf wird...- der aktuelle Baum aus
baumListe
ausgelesen. - die Wurzel des aktuellen Baumes in
ergebnisListe
eingefügt.
- der aktuelle Baum aus
Implementierung
public List<Object> levelorder(BinaryTree<Object> pTree){
//Linearisierung
List<BinaryTree<Object>> baumListe = new List<>();
baumListe.append(pTree);
baumListe.toFirst();
while(baumListe.hasAccess()){
BinaryTree<Object> aktuell = baumListe.getContent();
if(!aktuell.getLeftTree().isEmpty()){
baumListe.append(aktuell.getLeftTree());
}
if(!aktuell.getRightTree().isEmpty()){
baumListe.append(aktuell.getRightTree());
}
baumListe.next();
}
// Sachlogik:
// die baumListe durchlaufen,
// von jedem Element (Typ: BinaryTree!)
// die Wurzel auslesen und an ergebnisListe anhaengen
List<Object> ergebnisListe = new List<>();
baumListe.toFirst();
while(baumListe.hasAccess()){
BinaryTree<Object> aktuellerBaum = baumListe.getContent();
Object aktuelleWurzel = aktuellerBaum.getContent();
ergebnisListe.append(aktuelleWurzel);
baumListe.next();
}
return ergebnisListe;
}
Rahmenmethode
siehe Rahmenmethode im Artikel Binärbaum.
weitere beispielhafte Methoden
Tiefe
public int tiefe(BinaryTree pTree) {
int ergebnis = -1;
if (pTree.isEmpty()) {
// leere Teilbaeume haben Tiefe -1
// denn Baeume, die nur aus einem Blatt bestehen,
// haben Tiefe 0!
return ergebnis;
}
int tiefeLinks = tiefe(pTree.getLeftTree());
int tiefeRechts = tiefe(pTree.getRightTree());
ergebnis = tiefeLinks;
if (ergebnis < tiefeRechts) {
ergebnis = tiefeRechts;
}
// der gesamte Baum ist um eins tiefer, wegen der Wurzel
ergebnis++;
return ergebnis;
}
Knotenzahl
public int knotenzahl(BinaryTree pTree) {
int ergebnis = 0;
if (pTree.isEmpty()) {
return ergebnis;
}
ergebnis += 1; // die Wurzel zaehlen!
int knotenLinks = knotenzahl(pTree.getLeftTree());
int knotenRechts = knotenzahl(pTree.getRightTree());
ergebnis += knotenLinks;
ergebnis += knotenRechts;
return ergebnis;
}
Blätter
Diese Methode gibt alle Blätter eines Baumes in einer Liste zurück.
Die Methode funktioniert rekursiv.
Man braucht zwei Abbruchbedingungen:
- für einen leeren Baum -> eine leere Liste zurückgeben
- für ein Blatt -> eine Liste mit nur einem Blatt drin zurückgeben
public List blaetter(BinaryTree<Object> pTree) {
List<Object> ergebnis = new List<>();
if (pTree.isEmpty()) {
return ergebnis;
}
if (pTree.getLeftTree().isEmpty() && pTree.getRightTree().isEmpty()){
// pTree ist ein Blatt!
ergebnis.append(pTree.getContent());
return ergebnis;
}
List<Object> linkesErgebnis = blaetter(pTree.getLeftTree());
List<Object> rechtesErgebnis = blaetter(pTree.getRightTree());
ergebnis.concat(linkesErgebnis);
ergebnis.concat(rechtesErgebnis);
return ergebnis;
}
Berechnen
Diese Methode berechnet das Ergebnis eines Strukturbaumes aus den Rechenzeichen +, -, *, / und Zahlen.
public double berechnen(BinaryTree<String> b) {
double ergebnis = 0;
if (b.isEmpty()) {
return ergebnis;
}
String wurzelString = b.getContent();
if ( !wurzelString.equals("+") &&
!wurzelString.equals("-") &&
!wurzelString.equals("*") &&
!wurzelString.equals("/")
)
{
double wurzelDouble = Double.parseDouble(wurzelString);
ergebnis = wurzelDouble;
return ergebnis;
}
double ergebnisLinks = berechnen(b.getLeftTree());
double ergebnisRechts = berechnen(b.getRightTree());
if(wurzelString.equals("+")){
ergebnis = ergebnisLinks + ergebnisRechts;
}
else if((wurzelString.equals("-")){
ergebnis = ergebnisLinks - ergebnisRechts;
}
else if((wurzelString.equals("*")){
ergebnis = ergebnisLinks * ergebnisRechts;
}
else if((wurzelString.equals("/")){
ergebnis = ergebnisLinks / ergebnisRechts;
}
else{
System.err.println("unbekanntes Rechenzeichen!");
ergebnis = 0;
}
return ergebnis;
}