Praktische Einführung und Programmierung
Stefan Bosse
Universität Koblenz - FB Informatik
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume ::
Übergang von linearen Listen auf Baumstrukturen; Skip-Listen als "Vermittler"
Vor- und Nachteile
Algorithmen
Anwendungen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Motivation
Die bisher betrachteten Datenstrukturen waren im Wesentlichen eindimensional oder linear: sie weisen mit der Vorgänger-Nachfolger-Relation nur eine Form von Beziehungen zwischen den einzelnen Elementen auf.
In diesem Kapitel wollen wir mit den Bäumen eine Klasse von Datenstrukturen kennen lernen, die eine zweite Dimension einbeziehen und dadurch auch die Darstellung von hierarchischen Strukturen ermöglichen.
Baumstrukturen sind nicht nur in der Informatik von fundamentaler Bedeutung, etwa zur Organisation von Daten, sondern sind auch im Alltag in vielfältiger Form wiederzufinden.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Allgemeines Beispiel
Formales Modell
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Allgemein können wir unter einem Baum eine Menge von Knoten ℕ (Nodes) und Kanten 𝔼 (Edges) verstehen, die besondere Eigenschaften aufweisen:
So besitzt jeder Baum genau einen ausgezeichneten Knoten, der als Wurzel bezeichnet wird.
Weiterhin ist jeder Knoten außer der Wurzel durch genau eine Kante mit seinem Vorgängerknoten verbunden.
Jeder Knoten na kann 0..k Nachfolgerknoten u besitzen die durch Kanten ka,u verbunden sind.
Die Kanten sind (zunächst noch) gerichtet ("von oben nach unten")
Ein Knoten oder der Baum haben eine Ordnung d: Die maximale Anzahl von Kanten pro Knoten.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
https://ac.informatik.uni-freiburg.de/lak_teaching/ss_06/info2/Slides/18_Baeume_Grundlagen_Natuerliche_Suchbaeume.pdf
Nicht jede Struktur ist ein Baum (aber ein Graph)
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Ein Knoten ohne Nachfolger kann als Blatt(knoten) bezeichnet werden.
Ein Pfad in einem Baum ist eine Folge von unterschiedlichen Knoten, in der die aufeinander folgenden Knoten durch Kanten miteinander verbunden sind.
Weitere Eigenschaften (Parameter) von Bäumen: Höhe und Niveau.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Es muss im Gegensatz zu lineare Listen Entscheidungsfunktionen f geben, die einen Übergang von Knoten na auf einen Nachfolgerknoten nb berechenn.
Ein Baum ist neben einer Beziehungsrelation (Stammbaum) eine Suchmaschine.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Höhe und Niveau von Bäumen
Bottom-up Berechnungsbaum
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Jeder Baum kann in Teilbäume zerlegt werden. Der atomare Baum besteht aus einem Knoten. Bäume sind rekursive Datenstrukturen. Unterbäume sind auch Bäume.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Suche nach Schlüssel s endet beim Suchbaum in Knoten k mit k.key == s oder in Blatt, dessen Intervall s enthält.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Taxonomie
Zusammenfassung:
Baumstrukturen dienen im Wesentlichen zur hierarchischen Repräsentation und Organisation von Daten. Eine der wichtigsten Einsatzgebiete von Bäumen ist jedoch die Unterstützung einer effizienten Suche. Hierfür werden in den einzelnen Knoten eines Baumes neben den eigentlichen Nutzdaten (bzw. einen Verweis darauf) zusätzlich noch Schlüsselwerte gespeichert, über die die Suche erfolgt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Abstrakter Datentyp Binärer Baum
Für einen aus den Teilbäumen x und y sowie dem Element b konstruierten Baum bin(x,b,y) liefert left demnach den Teilbaum x, right den Teilbaum y und value das Element b. Die beiden weiteren Funktionen empty und is_empty werden zur Konstruktion eines leeren Baumes bzw. zum Test auf einen leeren Baum benötigt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Abstrakter Datentyp Binärer Baum
type Tree(T)import Booloperators empty : → Tree bin : Tree × T × Tree → Tree left : Tree → Tree right : Tree → Tree value : Tree → T isempty : Tree → Boolaxioms ∀x,y: Tree, ∀b: T left (bin (x, b, y)) = x right (bin (x, b, y)) = y value (bin (x, b, y)) = b isempty (empty) = true isempty (bin (x, b, y)) = false
ADT Tree(T) mit Operatoren und Axiomen die das Verhalten der Operationen definieren
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Datenstrukturen
typeof root = nodetypeof node = { left : node, right : node, data : data, key : number}
typeof root = nodetypeof node = { leaf = { left : node|leaf, key : number, right : node|leaf, data : data key : number }}
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Es gibt wieder den bisher bekannten Satz an Basisoperationen die auf Bäume angewendet werden:
search(k)
insert(node | ⟨data,k⟩)
remove(node|data|k)
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function Node(data,k) { return { left:null,right:null,data:data,key:k }}function search (node,k) { if (node.key==k) return node if (node.left && node.left.key<k) return search(node.left,k) if (node.right && node.right.key<k) return search(node.right,k) throw NOTFOUND}search(root,100)
Suche in einem Bereichsbaum nach dem Schlüüsel k
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function node(data,k) { return { children:[],data:data,key:k,keyRange:[k,k] }}function search (node,k) { if (node.key==k) return node for(i=0;i<length(node.children);i++) { if (node.children[i].keyRange[0]>=k && node.children[i].keyRange[1]<=k) return search(node.children[i],k) } throw NOTFOUND}
Suche in einem Bereichsbaum nach dem Schlüüsel k
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Es gibt verschiedene Strategien zur Traversierung (für Suche, Einfügen, Löschen von Knoten) eines Baums.
Ein Beispielsbaum mit den Knoten A,B,C,D,E,F und G
Hier wird zuerst rekursiv der linke Teilbaum besucht, dann der Knoten selbst und anschließend der rechte Teilbaum. Für den binären Baum aus der Abbildung entspricht das der Reihenfolge:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
D → B → E → A → F → C → G
Ein Beispiel für die Anwendung dieser Strategie ist das Auslesen eines Baumes für einen arithmetischen Ausdruck in Infixschreibweise.
Der Algorithmus für einen Inorder-Durchlauf lässt sich am einfachsten rekursiv formulieren. Der gesamte Baum wird nach der Inorder-Strategie durchlaufen, wenn der Algorithmus mit der Wurzel des Baumes als Parameter aufgerufen wird.
function Inorder (k) { // Eingabe: Knoten k eines binären Baumes mit Verweis auf // linken (k.left) und rechten (k.right) Teilbaum sowie // dem Element k seleber (Daten data und Schlüssel k) Inorder (k.left) /* besuche linken Teilbaum */ Process (k.data,k.key) /* Verarbeite aktuellen Knoten */ Inorder (k.right) /* besuche rechten Teilbaum */}
Inorder-Traversierung
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Bei dieser Strategie wird zuerst der Knoten selbst besucht und erst danach erfolgt das Traversieren des linken bzw. rechten Teilbaumes. Für den Beispielbaum liefert das die Reihenfolge:
A → B → D → E → C → F → G
Neben der Ausgabe eines arithmetischen Ausdrucks in Präfixschreibweise ist die Erzeugung einer eingerückten Baumdarstellung für ein Dateisystem ein typisches Anwendungsbeispiel. Hier wird zuerst der Name des Verzeichnisses ausgegeben und darunter eingerückt eine Liste der darin enthaltenen Dateien und Verzeichnisse.
Der Algorithmus für den Preorder-Durchlauf kann wiederum rekursiv formuliert werden
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function Preorder (k) { // Eingabe: Knoten k eines binären Baumes mit Verweis auf // linken (k.left) und rechten (k.right) Teilbaum sowie // dem Element k seleber (Daten data und Schlüssel k) Process (k.data,k.key) /* Verarbeite aktuellen Knoten */ Preorder (k.left) /* besuche linken Teilbaum */ Preorder (k.right) /* besuche rechten Teilbaum */}
Preorder-Traversierung
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Beim Postorder-Durchlauf werden erst beide Teilbäume durchlaufen, bevor der Knoten selbst besucht wird. Dies führt in unserem Beispiel zur Reihenfolge
D → E → B → F → G → C → A
Ein Beispiel für die Anwendung dieser Strategie ist die Erstellung einer Stückliste mit Aufsummierung, etwa beim UNIX-Kommando du (disk usage), das den Platzbedarf eines Verzeichnisses und aller Unterverzeichnisse im Dateisystem ermittelt. Hierbei werden zuerst die Größen der Einträge in den Blättern (d.h. der Dateien) bestimmt und für den direkten Vaterknoten (d.h. das enthaltende Verzeichnis) addiert. Der Platzbedarf dieser Verzeichnisse wird wiederum zum Platzbedarf des übergeordneten Verzeichnisses zusammengefasst usw., bis die Gesamtgröße der Wurzel bestimmt werden kann.
Wie bereits bei den anderen beiden Strategien wird der Algorithmus zum Postorder-Durchlauf am einfachsten rekursiv formuliert
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function Postorder (k) { // Eingabe: Knoten k eines binären Baumes mit Verweis auf // linken (k.left) und rechten (k.right) Teilbaum sowie // dem Element k seleber (Daten data und Schlüssel k) Postorder (k.left) /* besuche linken Teilbaum */ Postorder (k.right) /* besuche rechten Teilbaum */ Process (k.data,k.key) /* Verarbeite aktuellen Knoten */}
Postorder-Traversierung
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Während bei den ersten drei Strategien der Baum zuerst in der Tiefe durchwandert wird, entspricht der Levelorder-Durchlauf einer Breitensuche, d.h., auf jedem Niveau des Baumes werden erst alle Knoten besucht, bevor auf das nächste Niveau gewechselt wird. Danach ergibt sich die Reihenfolge:
A → B → C → D → E → F → G
Der Algorithmus zum Levelorder-Durchlauf ist im Gegensatz zu den bisher betrachteten Strategien nicht rekursiv. Stattdessen wird eine Warteschlange verwendet, in der die noch zu verarbeitenden Knoten abgelegt werden. Der Baum wird durchlaufen, indem der nächste zu besuchende Knoten aus der Warteschlange entnommen wird. Nach dem Besuch dieses Knotens werden die Kinder am Ende der Warteschlange eingetragen, bevor mit dem nächsten Knoten fortgefahren wird.
Die Levelorder-Strategie wird iterativ implementiert.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function Levelorder (k) { // Eingabe: Knoten k eines binären Baumes q = [] // leere Warteschlange; addTail (q, k) /* Wurzel in die Warteschlange aufnehmen */ while (length(q)!=0) { n = removeHead(q); Process (n.data, n.key) addTail(q, n.left) /* linken Knoten eintragen */ addTail(q, n.right) /* rechten Knoten eintragen */ }}
Levelorder-Traversierung
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Wenn der Baum nicht geordnet oder die Knoten nicht mit Schlüssel assoziert sind (generischer Baum) müssen alle Teilbäume und Pfade durchsucht werden.
Das klinkt sinnlos, da das Suchen scheinbar wieder einer lineare Liste entspricht.
Aber bei Parallelisierung der Suche in Teilbäume (Divide-and-Conquer Methode) könnte dennoch eine geringere Suchzeit notwendig sein!
function parSearch(node,k) { if (node.key==k) terminateSearchWith(node) startProcess(parSearch,node.left,k) startProcess(parSearch,node.right,k)}parSearch(root,k)
Parallele Suche. Es werden bis zu N-1-L Prozesse gestartet, bei N Knoten und L Endknoten ohne Nachfolger.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Bäume sind daher selber Organisationsstrukturen für Algorithmen die einen Programmablauf steuern können!
Z.B. auch Suche im Internet (Suchmaschine!)
Verteilung von Berechnungen auf Rechner mittels hierarchischer Teilung
Baumstrukturen stellen verteilte Kommunikationsnetze dar (Verbindung von Rechnern), die Kanten sind Kommunikationskanäle.
In Betriebssystemen (UNIX) können Prozesse weietere Prozesse starten. Die neuen Prozesse werden Kindprozesse des Elternprozesses ⇒ Baumstruktur!
Dateisysteme sind Baumstrukturen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Höhe eines symmetrischen ideal besetzten Binärbaums: H=O(log N).
Die Baum-Struktur hängt von Einfügereihenfolge in anfangs leeren Baum ab
Einfügen eines neues Elements (Schlüssel k=5) in einen bestehenden Baum verändert die Höhe des Baums und dessen Symmetrie
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function Node(data,k) { return { left:null,right:null,data:data,key:k }}function insert(node,data,k) { if (node.key==k) throw Error; if (k>node.key) { if (!node.right) node.right=Node(data,k) else insert(node.right,data,k) } else { if (!node.left) node.left=Node(data,k) else insert(node.left,data,k) }}if (!root) root=Node(data,k)else insert(root,data,k)
Einfügen eines neuen Datensatzs in einen Binärbaum
Hier wird die Preorder Traversierung angewendet.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Beispiel des Einfügens eines neuen Knotens mit dem Schlüssel k=4
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Die komplizierteste Operation im binären Suchbaum ist das Löschen. Dies liegt daran, dass beim Entfernen eines inneren Knotens einer der beiden Teilbäume des Knotens »hochgezogen« und gegebenenfalls umgeordnet werden muss.
Grundsätzlich müssen beim Löschen eines Knotens k drei Fälle berücksichtigt werden:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
Für das Ersetzen eines Knotens gibt es wiederum zwei Varianten:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function removeNode(root,k) { // Eingabe: Wurzel vom Suchbaum T, Schlüssel k des zu löschenden Elementes node = searchNode (root, k) if (node = null) throw Error parent = searchParent (root, node) // Arrg, sollte mit Search kombiniert werden /* 1. Fall */ if (!node.left && !node.right) { // node ist Blattknoten if (parent.left===node) // node ist linkes Kind parent.left = null else parent.right = null /* 2. Fall */ } else if (!node.left) { if (parent.left===node) // node ist linkes Kind parent.left = node.right else parent.right = node.right } else if (!node.right) { if (parent.left===node) // node ist linkes Kind parent.left = node.left else parent.right = node.left } else /* 3. Fall */ child = minElement (node.right) replaceNode(node,child) // Ersetze node durch child }
Entfernen eines Knotens mit dem Schlüssel k
Stefan Bosse - Algorithmen und Datenstrukturen - Modul T Bäume :: Algorithmen
function minElement(node) { if (!node.left && !node.right) return node; if (node.left) return minElement(node.left) else return node}
Minimales Element eines Teilbaums
asdp