Algorithmen und Datenstrukturen CS1017 Th. Letschert TH Mittelhessen Gießen University of Applied Sciences Datenstrukturen II: Bäume – Datenstruktur Baum in der Java-API – Bäume traversieren – Anwendung: Hierarchisch strukturierte Daten darstellen – Anwendung: Suchbäume / Daten Speichern und Suchen Datentypen und Datenstrukturen in der Java-API Collection Framework – Das Java-API enthält – im package java.util – das Collection-Framework: Klassen die bei der Organisation von Daten helfen – Das Collection-Framework bietet fertige Lösungen für Listen Mengen Abbildungen … und vieles andere mehr – Das Collection Framework enthält Listen, Mengen, Abbildungen, ... In Form von Interfaces / Datentypen und In Form von Klassen die die Interfaces implementieren / (abstrakte) Datentypen – Das Collection Framework nutzt diverse Datenstrukturen bei der Implementierung der Klassen – Alle ernsthaften Programmiersprachen haben ihr Collection Framework, es sei denn die entsprechende Funktionalität ist direkt in der Sprache verfügbar Seite 2 Datentyp Abbildung (Map) Abbildung (Map) als Datentyp Eine Map realisiert die Zuordnung von Objekten zu einem anderen Objekt – Einem eindeutigen Schlüsselwert (key) – kann ein Wert (value) zugeordnet werden – Außerordentlich wichtiger Datentyp Alle modernen Programmiersprachen bieten eine Map-Implementierung – die eventuell anders genannt wird (assoziatives Array, Dictionary, etc.) [http://en.wikipedia.org/wiki/Comparison_of_programming_languages_%28mapping%29] – in Skript-Sprachen sind Maps elementares Sprachkonstrukt – in Java Bestandteil der Standard-Bibliothek (API) Beispiel public static void main(String[] args) { Map<String, Integer> phoneBook = new TreeMap<>(); phoneBook.put("Karla ", phoneBook.put("Egon ", phoneBook.put("Dagmar", phoneBook.put("Hugo ", 4711); 1815); 1234); 4433); Dagmar Egon Hugo Karla for (String key: phoneBook.keySet()) { System.out.println(key + " -> " + phoneBook.get(key)); } } Seite 3 -> -> -> -> 1234 1815 4433 4711 Ausgabe nach Schlüsseln sortiert Datentyp Map in der Java-API Interface java.util.Map Klasse TreeMap Implementierung des Datentyps Map mit Hilfe der Datenstruktur Baum Die Ordnung der Schlüssel (compareTo) wird bei der Speicherung berücksichtigt derart dass Speicher- und Suchfunktionen optimiert sind. Klasse HashMap Implementierung der des Datentyps Map mit Hilfe der Datenstruktur Hash-Tabelle Map <<interface>> SortedMap <<interface>> Java-API: Maps und ihre Implementierungen AbstractMap TreeMap HashMap Seite 4 Datentyp Map in der Java-API Map, TreeMap, HashMap A B C D K L → → → → → → X Y Z U V W Datentyp Map: Konzept einer Abbildung C Z K V A X TreeMap ImplementierungsVariante für Abbildungen: Datenstruktur Baum B Y L W D U D U C Z HashMap: ImplementierungsVariante für Abbildungen 01 02 03 04 K V A X B Y Seite 5 L W Datentyp Menge (Set) Menge (Set) Ein Set realisiert Kollektionen von Objekten die – Nicht mehrfach das Gleiche enthalten – Keine vom Benutzer definierte Ordnung haben Einsatz – Wiederholungen sollen vermieden werden – Der Verzicht auf eine benutzerdefinierte Ordnung gibt die Möglichkeit zu effizienter interner Darstellung. Listen haben haben eine vom Benutzer der Liste definierte Ordnung. Diese muss von der Implementierung respektiert werden. Bei Mengen ist die Implementierung frei darin, die Elemente nach Belieben zu ordnen. Beispiel public static void main(String[] args) { Set<String> set = new TreeSet<>(); for(String s : new String[]{ "eins", "zwei", "drei", "vier", "fuenf", "sechs", "sieben", "acht", "neun", "zehn", "elf", "zwoelf", "dreizehn"} ) { set.add(s); } } for (String s: set) { System.out.println(s); } Sortieren mit einem Set Seite 6 Datentyp Set in der Java-API Interface java.util.Set Klasse TreeSet Implementierung des Datentyps Set mit Hilfe der Datenstruktur Baum Die Ordnung der Elemente (compareTo) wird bei der Speicherung berücksichtigt derart dass Speicher- und Suchfunktionen optimiert sind. Klasse HashSet Implementierung der des Datentyps Set mit Hilfe der Datenstruktur Hash-Tabelle Set <<interface>> SortedSet <<interface>> Java-API: Sets und ihre Implementierungen AbstractSet TreeSet HashSet Seite 7 Datenstruktur Baum Baum – als besonderer Graph Ein Baum ist eine Sonderform eines Graphen Definitionen – Ein gerichteter Graph G = (V, E) besteht aus einer endlichen Menge V von Knoten und einer Menge E von geordneten Paaren (a, b) mit a, b ∈ V , genannt Kanten. – Zwei Knoten eines Graphen heißen zusammenhängend, wenn sie durch einen Kantenzug verbunden sind. – Ein Graph G heißt zusammenhängend, wenn zwei beliebig herausgegriffene Knoten stets zusammenhängend sind. – Ein zusammenhängender Graph, der keinen Kreis enthält, heißt ein Baum. – Wird bei einem Baum irgend ein Knoten besonders ausgezeichnet, dann nennt man den Baum einen Wurzelbaum und den ausgezeichneten Knoten die Wurzel. – In einem Wurzelbaum haben alle Kanten eine Richtung: weg von der Wurzel – Die Beziehungen zwischen verbundenen Knoten werden mit Vorgänger / Nachfolger, bzw. Eltern / Kinder bezeichnet – In einem geordneten Baum haben alle Nachfolger eine definierte Reihenfolge – Ein Blatt ist ein Knoten ohne Nachfolger – Ein innerer Knoten ist ein Knoten der kein Blatt ist. Mit Baum meinen wir im Allgemeinen einen geordneten Wurzelbaum Seite 8 vergl. Metz, Diskrete Mathematik Datenstruktur Baum Baum – als rekursiv definierte Datenstruktur Ein Baum ist eine rekursive Datenstruktur. Baum: rekursive Definition – Basis: Jeder Knoten ist ein Baum – Rekursion: Sei r ein Knoten und T1, .. Tn Bäume mit den Wurzeln r1, .. rn, dann ist der Graph bestehend aus r und T1, .. Tn ein Baum T mit: r ist die Wurzel von T T enthält jeweils eine neue Kante von r nach ri , i ∊ { 1 , .. n} r r r1 r2 r3 T1 T2 T3 r1 r2 r3 T1 T2 T3 T Bäume sind nicht irgendeine rekursive Datenstruktur, sie sind das Musterbeispiel rekursiver Datenstrukturen! Strukturelle Rekursion auf Bäumen: Da Bäume eine rekursive Struktur haben, können leicht rekursive Funktionen auf ihnen definiert werden. Seite 9 Datenstruktur Baum: Verwendung Einsatz von Bäumen Bäume sind eine wichtige Datenstruktur mit vielen Anwendungen. Es gibt kaum eine sinnvolle Verwendung eines Datentyps Baum aber die Datenstruktur Baum wird bei der Implementierung diverser anderer Datentypen genutzt. Wichtige Beispiele für den Einsatz der Datenstruktur Baum 1) Bäume werden genutzt um hierarchische Strukturen beliebiger Art darzustellen Beispiel: Syntaxbäume, Stammbäume, …. 2) Bäume werden sehr oft benutzt um Datenabstraktionen „mit Suchfunktion“ zu implementieren, z.B.: Abbildung und Menge Tabelle / Relation ... In der Java-API gibt es beispielsweise Baum-basierte Abbildungen und Mengen: java.util.TreeMap, java.util.TreeSet Seite 10 Datenstruktur Baum: Verwendung Einsatz von Bäumen Bäume sind eine wichtige Datenstruktur mit vielen Anwendungen. Beispiel: Syntaxbäume Bäume werden benutzt um die Struktur von sprachlichen Ausdrücken darzulegen. Compiler erzeugen Syntaxbäume aus Programmtext. Bei der Verarbeitung natürlicher Sprachen werden (weit komplexere) Syntaxbäume erzeugt. * 2 * (3 + 4*5) + 2 * 3 4 Seite 11 5 Datenstruktur Baum: Implementierung Syntax-Bäume als rekursive Menge* – Implementierung Ein Syntax-Baum für arithmetische Ausdrücke ist entweder – Ein Blatt mit einer Zahl als Wert oder – Ein innerer Knoten mit einem Operator und zwei Bäumen als Operanden interface ExpTree { enum Op {plus, minus, mult, div } double compute(); } Ausdrucksbäume können mit compute ausgewertet werden. Der Typ der Operatoren wird hier definiert. class ValNode implements ExpTree { Ein „Blatt“ des Baums besteht aus einer Zahl als Wert. Die Auswertung von Blättern liefert ihren Wert. double val; public ValNode(double val) { this.val = val; } } @Override public double compute() { return val; } * der Kürze wegen sagen wir (falsch) „rekursive Menge“ und meinen damit stets korrekt „rekursiv definierte Menge“ Seite 12 Datenstruktur Baum: Implementierung Syntax-Bäume als rekursive Menge – Implementierung class OpNode implements ExpTree { Op op; ExpTree left; ExpTree right; Innere Knoten haben haben einen Operator und zwei Unterbäume. public OpNode(Op op, ExpTree left, ExpTree right) { this.op = op; this.left = left; this.right = right; } @Override public double compute() { return toFun(op).apply( left.compute(), right.compute() ); } } static BiFunction<Double, Double, switch (op) { case plus : return (Double x, case minus : return (Double x, case mult : return (Double x, case div : return (Double x, default: return null; } } Bei der Auswertung werden die Unterbäume ausgewertet und die berechneten Werte entsprechend dem Operator verknüpft. Double> toFun(Op op) { Double Double Double Double y) y) y) y) -> -> -> -> x+y x-y x*y x/y ; ; ; ; Seite 13 Diese Hilfs-Funktion liefert die Verknüpfungsfunktion zu einem Operator. Datenstruktur Baum: Implementierung / Verwendung Einsatz von Bäumen Beispiel: Ausdrücke als (abstrakter) Datentyp, der intern die Datenstruktur Baum verwendet class Expression { private interface ExpTree { … } private static class OpNode implements ExpTree { … } private static class ValNode implements ExpTree { … } Datenstruktur-Definitionen wie oben, eingepackt in eine „Hüll-Klasse“ die die Nutzer-Sicht auf die Bäume darstellt. private ExpTree root = null; public Expression(double v) { root = new ValNode(v); } Konstruktoren und Auswertefunktion public Expression (Expression e1, char c, Expression e2) { this.root = new OpNode(toOp(c), e1.root, e2.root); } public double compute() { return root.compute(); } interface ExpTree {... private static ExpTree.Op toOp(char c) { switch (c) { case '+' : return ExpTree.Op.plus; case '-' : return ExpTree.Op.minus; case '*' : return ExpTree.Op.mult; case '/' : return ExpTree.Op.div; default: return null; } } } Seite 14 Datenstruktur Baum: Implementierung Varianten der Implementierung der Datenstruktur Baum Implementierung mit Zeigern Blätter und Knoten des Baums werden über Zeiger (Pointer) miteinander verbunden (siehe auch Beispiele oben und in Foliensatz 3) Implementierung mit Arrays Blätter und Knoten des Baums werden in Arrays gespeichert, die Verbindung erfolgt über Indizes Implementierung mit Matrix Bäume sind eine Sonderform der Graphen, sie können darum wie diese in einer Adjazenzmatrix gespeichert werden. Seite 15 Implementierung von Bäumen Zeiger-Implementierung von Bäumen – Sortierte Bäume: Knoten und Blätter haben unterschiedliche Typen (Sorten) Definiere eine Basisklasse (oder Interface) für das Gemeinsame aller Knoten Definiere eine abgeleitete Klasse für jede Art von Knoten D.h. Bäume werden als rekursive Menge mit OO-Mitteln implementiert abstract class Node<T> { . . . } InnerNode class InnerNode<T> extends Node<T> { private List<Node<T>> children; . . . } Leaf class Leaf<T> extends Node<T> { private T value; . . . } Node <<abstract>> Vergl. Kompositum Muster InnerNode Leaf Klassendiagramm Seite 16 Implementierung von Bäumen Beispiel: Syntax-Bäume als sortierte Zeigerbäume Beispiel mit mehreren Konstruktionsfunktionen: Ein Baum ist – – – – – eine Konstante mit einem Wert ein Add-Knoten mit zwei Unter-Bäumen ein Add-Knoten mit zwei Unter-Bäumen ein Mult-Knoten mit zwei Unter-Bäumen ein Div-Knoten mit zwei Unter-Bäumen oder oder oder oder Tree <<interface>> Mult left Sub Const 4 Const Const 5 Const 2 InnerNode right Sub Add Mult Implementierung: Eine Baum zu (4-2)*5 Klasse für jede Sorte von Knoten Seite 17 Div Implementierung von Bäumen Beispiel: ADT Expression implementiert mit der Datenstruktur Baum class Expression { public Expression(Double v) { this.root = new Const(v); } Datentyp, die „Hüll-Klasse“ die die Nutzer-Sicht darstellt. public Expression add(Expression other) { this.root = new Add(this.root, other.root); return this; } public Expression sub(Expression other) { this.root = new Sub(this.root, other.root); return this; } public Expression mult(Expression other) { this.root = new Mult(this.root, other.root); return this; } public Expression div(Expression other) { this.root = new Div(this.root, other.root); return this; } public double compute() { return root.compute(); } } private Tree root = null; Datenstruktur, die Implementierung der Funktionalität private interface Tree {...} private private private private private private Seite 18 static static static static static static abstract class InnerNode implements Tree {...} class Add extends InnerNode {...} class Sub extends InnerNode {...} class Mult extends InnerNode {...} class Div extends InnerNode {...} class Const implements Tree {...} Implementierung von Bäumen Beispiel: ADT Expression implementiert mit der Datenstruktur Baum fortgesetzt private interface Tree { double compute(); } private static abstract class InnerNode implements Tree { Tree left; Tree right; InnerNode(Tree left, Tree right) { this.left = left; this.right = right; } } private static class Add extends InnerNode { Add(Tree left, Tree right) { super(left, right); } @Override public double compute() { return left.compute() + right.compute(); } } … Sub, Mult, Div entsprechend … private static class Const implements Tree { double value; Const(double value) { this.value = value; } @Override public double compute() { return value; } } Seite 19 Die privaten Definitionen zur Konstruktion der Datenstrukturen Binäre Bäume Binärer Baum Ein binärer Baum (Binärbaum) ist ein geordneter gerichteter Wurzelbaum bei dem jeder Knoten maximal zwei Nachfolger hat: einen – linken Unterbaum und einen – rechten Unterbaum A A A A B C B B D E F vier unterschiedliche Binärbäume Seite 20 G Binäre Bäume Implementierung binärer Bäume – Zeigerdarstellung Mit Zeigern (Pointern) verbundene Knoten A B v: A, l: , r: C D E F G v: B, l: , r: v: C, l: , r: v: D, l: , r: v: E, l: , r: v: F, l: , r: v: G, l: , r: Zeiger-Implementierung Seite 21 Binäre Bäume Implementierung binärer Bäume – Tabellendarstellung Mit Indizes verbundene Tabelleneinträge A B C D E F 0 v: A l: 1 r: 2 1 v: B l: -1 r: 3 2 v: C l: -1 r: 4 3 v: D l: -1 r: -1 4 v: E l: r: 5 v: F l: -1 r: -1 6 v: G l: -1 r: -1 G Tabellen-Implementierung Seite 22 5 6 Binäre Bäume Implementierung binärer Bäume – Tupeldarstellung Baum als verschachtelte Tupel: (Wurzel, linker Unterbaum, rechter Unterbaum) A (A, B (B,(), C (D,()())), (C, (), (E, (F,(),(), D (G,(),())))) E F G Tupel-Darstellung Seite 23 Baum-Traversierungen Traversieren (Durchlaufen) von Bäumen Die Knoten eines Baums können in unterschiedlicher Reihenfolge durchlaufen werden. Rekursive Verfahren (Zuerst-in-die-Tiefe / Depth-First) sind: – Preorder: erst Mitte, dann Links, dann Rechts – Inorder: erst Links, dann Mitte, dann Rechts – Postorder: erst Links, dann Rechts, dann Mitte A B A C B C PreorderTraversierung D E F PostorderTraversierung D G E F Besuchsreihenfolge: A B D C E F G G Besuchsreihenfolge: D B F G E C A Seite 24 Baum-Traversierungen Traversieren (Durchlaufen) von Bäumen Beispiel: Rekursive (Depth-First) Traversierung zur Ausdrucksauswertung private interface Tree { double compute(); } ... private static class Add extends InnerNode { Add(Tree left, Tree right) { super(left, right); } @Override public double compute() { return left.compute() + right.compute(); } class Expression { . . . private Tree root = null; public double compute() { return root.compute(); } @Override public String toString() { return root.toString(); } } @Override public String toString() { return "(" + left.toString() + " + " + right.toString() + ")"; } ... private static class Const implements Tree { double value; Const(double value) { this.value = value; } } @Override public double compute() { return value; } } @Override public String toString() { return ""+value; } Seite 25 Baum-Traversierungen Traversierung von Bäumen Beobachtung: Die Traversierung eines Baums zum Berechnen des Werts oder der String-Darstellung hat zwei Grundbestandteile – Durchlaufen des Baums: Knoten nach Knoten besuchen – Ausführung einer Aktion: Anwenden einer Methode / Funktion auf den gerade besuchten Knoten Verallgemeinerung der Traversierung: Die Traversierung eines Baums kann darum getrennt werden in – einen (fixen) Algorithmus zum Druchlaufen der Knoten und – einem (wechselnden) Algorithmus der auf die knoten angewendet wird. Es liegt nahe dies zu organisieren als – Traversierungsfunktion, die – Eine Parameter hat, der die auszuführende Aktion beschreibt Seite 26 Besuchermuster Baum-Traversierung nach dem Besuchermuster • Muster : Vorgabe / allgemeines Beispiel wie eine normierte Problemstellung zu lösen ist • Besuchermuster : Muster zur Implementierung eines verallgemeinerten Baumdurchlaufs • Das Besuchermuster trennt – die speziellen Aktions-Algorithmen – von den allgemeinen Algorithmen des Durchlaufen eines Baums • Sein Einsatz ist vor allem dann sinnvoll, wenn ein Baum mit vielen unterschiedlichen Aktionen durchlaufen wird. • Hat man nur eine oder zwei Aktionen auf einem Baum auszuführen dann lohnt sich die zusätzliche Code-Komplexität des Besuchermusters nicht. Thema des OO-Entwurfs / Softwaretechnik, siehe entsprechende Veranstaltungen Seite 27 Binäre Bäume Baum-Traversierung Die Knoten eines (binären) Baums können in unterschiedlicher Reihenfolge durchlaufen werden. Ein etwas komplexeres weil nicht-rekursives Verfahren ist die: Breiten-Traversierung (Traversierung in level-order / Breadth-First / nicht-rekursiv) Die Knoten einer Ebene werden komplett durchlaufen bevor die Knoten der nächst-tieferen Ebene durchlaufen werden. A Lege die Wurzel in eine Warteschlange queue; while (queue ist nicht leer) { nimm Knoten aus queue; setze alle Kinder des Knotens in die queue; bearbeite Knoten; } C B D E Algorithmus zur Breitentraversierung F G Breiten-Traversierung Seite 28 Binäre Suchbäume Binärer Suchbaum Definition: Ein binärer Suchbaum ist – ein binärer Baum – bei dem jeder Knoten N einen Wert (den sogen. Schlüsselwert) NK hat – die Werte sind total geordnet (vergleichbar mit >, <) – Die Schlüsselwerte sind eindeutig: Zwei unterschiedliche Knoten haben nicht den gleichen Schlüsselwert. – für jeden Knoten N mit Nachfolger L und R gilt: LK < NK < RK 7 5 1 6 3 Seite 29 12 19 15 66 Binäre Suchbäume Einsatz Binärer Suchbaum Beobachtung 1: – In einer völlig geordneten Kollektion (sortierte Liste) können Suchoperationen schnell ausgeführt werden. – In einer völlig geordneten Kollektion (sortierte Liste) sind Speicheroperationen aufwändig. Beobachtung 2: – In einer völlig ungeordneten Kollektion (unsortierte Liste) sind Suchoperationen aufwändig. – In einer völlig ungeordneten Kollektion (unsortierte Liste) sind Speicheroperationen schnell. Binärer Suchbaum: – „Halb-geordnete Kollektion“ – sowohl Speicheroperationen als auch Suchoperationen sind einigermaßen effizient. Seite 30 Binäre Suchbäume Implementierung binärer Suchbaum public class SearchTree<T extends Comparable<T>> { private static class Node<T extends Comparable<T>> { final T key; Node<T> left; Node<T> right; } Node(T key) { this.key = key; } private Node<T> root; private void insert(Node<T> root, T key) { if (root.key.compareTo(key) == 0) { //already present return; } else if (root.key.compareTo(key) < 0) { // key > root if (root.right == null) { root.right = new Node<T>(key); } else { insert(root.right, key); } } else if (root.key.compareTo(key) > 0) { // key < root if (root.left == null) { root.left = new Node<T>(key); } else { insert(root.left, key); } } } private void insert(Node<T> root, T key) { } public void add(T key) { if (key == null) { throw new IllegalArgumentException(); } if (root == null) { root = new Node<T>(key); } else { insert(root, key); } } } Seite 31 Binäre Suchbäume Traversieren Binärer Suchbäume Beobachtung 1: – Eine Traversierung in In-Order-Reihenfolge liefert alle Elemente in sortierter Reihenfolge. Beobachtung 2: – Ein Iterator, der den Baum in In-order-Reihenfolge traversiert, liefert die Elemente in sortierter Reihenfolge. Seite 32 Binäre Suchbäume Traversieren Binärer Suchbäume Fädelung : – Der Baum wird mit zusätzlichen Zeigern („Fäden“) ausgestattet die auf den jeweils nächsten Knoten bei einer Traversierung zeigen. – Die Fädelung erleichtert das Durchlaufen des Baums. Start Stopp 7 5 Binärer Suchbaum mit in-order-Fädelung. 1 Aufgabe: 6 3 Welche Zeiger der Fädelung könnte man weglassen? Seite 33 12 19 15 66 Binäre Suchbäume Traversieren Binärer Suchbäume Iterator: – Mit Fädelung: Fädele Baum, durchlaufe die Fädelung – Ohne Fädelung: Iterator basiert auf Baumdurchlauf mit Stack statt Rekursion. Start Stopp 7 5 1 12 6 19 Beobachtung: Die In-Order-Traversierung kann mit einem Stapel unverarbeiteter Eltern-Knoten organisiert werden: Solange es ein linkes Kind gibt, staple den Knoten und gehe nach links. Danach nimm Knotenwert und gehe nach rechts. 3 Seite 34 15 66 Binäre Suchbäume Traversieren Binärer Suchbäume Rekursionsfreier in-Order-Baumdurchlauf private static <T extends Comparable<T>> void visitInOrderS(Node<T> root, Action<T> a) { List<Node<T>> stack = new LinkedList<>(); Node<T> k = root; } while (!stack.isEmpty() || k != null) { while (k != null) { stack.add(0, k); k = k.left; } k = stack.remove(0); a.act(k.key); k = k.right; } 7 5 Rekursionsfreie in-order Baum-Traversierung 1 6 3 Seite 35 12 19 15 66 Binäre Suchbäume Traversieren Binärer Suchbäume Traversieren mit Iterator – Beobachtung: hasNext ~ Schleifenkontrolle next ~ Schleifenkörper bei rekursionsfreier Traversierung HasNext while (!stack.isEmpty() || k != null) { while (k != null) { stack.add(0, k); k = k.left; } k = stack.remove(0); a.act(k.key); k = k.right; next der nächste Schlüsselwert } Seite 36 Binäre Suchbäume Traversieren Binärer Suchbäume mit Iterator public class SearchTree<T extends Comparable<T>> implements Iterable<T> { . . . @Override public Iterator<T> iterator() { return new Iterator<T>() { List<Node<T>> stack Node<T> k = new LinkedList<>(); = root; @Override public boolean hasNext() { return !stack.isEmpty() || k != null; } @Override public T next() { while (k != null) { stack.add(0, k); k = k.left; } k = stack.remove(0); T res = k.key; k = k.right; return res; } } } }; @Override public void remove() { throw new UnsupportedOperationException(); } Seite 37 Der Baum darf während des Durchlaufs natürlich nicht verändert werden! Ausgeglichene Binäre Suchbäume Baumstruktur - Bedeutung – Beobachtung Ein Suchbaum kann unter Umständen „entarten“: Er hat keine richtige Baumstruktur, sondern entspricht mehr oder weniger ganz oder in Teilbäumen einer Liste. Degenerierte Suchbäume haben schlechtere Such- und Einfügeoperationen mit geringerer Effizienz als Bäume mit ausgeglichener Baumstruktur. – Schlussfolgerung Lasse nur Bäume zu, die eine ausgeglichene Struktur haben. 7 1 3 5 5 12 6 7 1 6 19 12 15 3 19 66 Seite 38 15 66 Ausgeglichene Binäre Suchbäume Definitionen / Begriffe in Zusammenhang mit Bäumen – Tiefe (Stufe, Ebene) eines Knotens Die Tiefe eines Knotens ist definiert als die Länge des Pfades (=Anzahl der Knoten) von der Wurzel zum Knoten. – Höhe eines Baums Die Höhe h(T) eines Baums T ist das Maximum der Tiefen all seiner Knoten. Der leere Baum hat die Höhe -1 Ein Baum mit einem Knoten hat die Höhe 0 – Gewicht eines Baums Das Gewicht g(T) eines Baums T ist die Anzahl seiner Blätter. – Entarteter Baum Ein Baum ist entartet, wenn seine Höhe gleich der Zahl seiner Knoten ist. Seite 39 Ausgeglichene Binäre Suchbäume Ausgeglichene Binärbäume Zwei gängige Kriterien der Ausgeglichenheit (Balanciertheit) definieren die Ausgeglichenheit: – Höhen-balancierte Bäume Bei Höhen-balancierten Bäumen ist die Differenz der Höhe der Unterbäume beschränkt. – Gewichts-balancierte Bäume Bei Gewichts-balancierten Bäumen ist die Differenz des Gewichts der Unterbäume beschränkt. Seite 40 Ausgeglichene Binäre Suchbäume Höhen-balancierte Binärbäume k-balancierter Baum Ein k-balancierter Baum ist ein Baum, bei dem die Differenz der Höhe der beiden Unterbäume maximal k ist. Ein k-balancierter Baum T ist entweder – leer, oder – er hat zwei Unterbäume TL und TR mit | h(TL) - h(TR) | ≤ k Mit k kann das Kosten-Nutzen-Verhältnis variiert werden. Kleines k: sehr ausgewogen, aber eventuell viele Reorganisationen notwendig. Großes k: langsame Suche, aber weniger Reorganisationen notwendig. 4 2 1 6 3 5 9 8 Ist dieser Baum 2-blanaciert? 10 11 Seite 41 Ausgeglichene Binäre Suchbäume Höhen-balancierte Binärbäume k-balancierter Baum |h(TL ) – h(TR )| Zeigen Sie, dass dieser Baum 2-blanaciert ist! = |max{0, 1} – max {0,1,2,3} | = |1 - 3| =2 4 TL h(TL) = 1 1 0 1 2 0 3 1 2 TR 6 5 TL und TR sind die Unterbäume (des gleichen Knotens) mit der größten Höhendifferenz 9 10 8 11 3 Seite 42 h(TR ) = 3 Ausgeglichene Binäre Suchbäume AVL-Baum: Höhen-balancierter Binärbaum Ein 1-balancierter Baum wird AVL-Baum genannt (von Adelson-Velskii und Landis 1962 publiziert) Einfüge- und Löschoperationen sind komplex, da die Balance erhalten bleiben muss. Der Baum muss oft reorganisiert werden. Seite 43 Ausgeglichene Binäre Suchbäume Rot-Schwarz-Baum: Fast Höhen-balancierter Binärbaum Ein Rot-Schwarz-Baum hat geringere Anforderungen an die Ausgeglichenheit als ein AVL-Baum. – häufig verwendete Methode zur Speicherung von Abbildungen. – z.B. java.util.Map verwendet Rot-Schwarz-Bäume Seite 44 Ausgeglichene Binäre Suchbäume Rot-Schwarz-Baum: Fast Höhen-balancierter Binärbaum Ausgeglichene (balancierte) Bäume – sind bei Suchen um so besser, je ausgeglichener sie sind – allerdings: je ausgeglichener sie sein sollen, um so häufiger sind Reorganisationen notwendig Feststellen der Ausgeglichenheit ist aufwendig – Rot-Schwarz-Bäume haben weniger Anforderungen an die Ausgeglichenheit: Sie haben rote und schwarze Knoten Nur die „Schwarzhöhe“ muss ausgeglichen sein. – Vorteil Reorganisation bei Einfügen und Entfernen, wenn die Rot-Schwarz-Eigenschaft nicht erhalten bleibt Suchoperation O(log n) (wie bei anderen balancierten Bäumen) Einfüge- und Entferne-Operationen ebenfalls O(log n) Seite 45 Ausgeglichene Binäre Suchbäume Rot-Schwarz-Baum: Fast Höhen-balancierter Binärbaum Rot-Schwarz-Bäume – – – – – – Alle Knoten sind rot oder schwarz gefärbt Die Wurzel ist schwarz Jeder Knoten hat zwei Kinder (Endknoten haben „externe“ Kinder = null) Jedes Blatt ist schwarz Jeder rote Knoten hat nur schwarze Kinder Die „Schwarz-Höhe“ des Baums ist stets ausgeglichen. Schwarzhöhe 17 10 3 7 26 14 12 30 21 15 16 19 20 23 28 35 41 38 39 auf eine tiefergehende Diskussion wird hier verzichtet. Seite 46 47 Heap Heap: Fast Höhen-balancierter, fast sortierter Binärbaum Heap Verwendung Datenstruktur die für Prioritätswarteschlangen optimiert ist – Einfügen eines beliebigen Elements – Entfernen des Elements mit der höchsten Priorität (des größten / kleinsten) – gut geeignet für Speicherung in Arrays Definition Heap Ein Heap ist ein – 1-blancierter, links-lastiger, lückenloser Binärbaum – bei dem der Wert in jedem Knoten nicht kleiner ist als der in seinen Nachfolgern 60 42 30 5 2 4 Beispiel Heap Dieser „Heap“ hat nichts mit dem „Heap“-Speicher zu tun. Seite 47 Heap Heap: Höhen-balancierter, fast sortierter Binärbaum Heap als Array – Ein Heap kann auf Grund seiner Eigenschaften (1-blanciert, linkslastig) ebenen-weise in einem Array gespeichert werden 60 42 30 5 2 60 4 Seite 48 42 5 30 2 4 Heap Heap: Höhen-balancierter, fast sortierter Binärbaum Heap-Operationen – Einfügen durch „Hoch-Blubbern“ des neuen Elements bis die Heap-Eigenschaft wieder gilt – Entfernen durch „Runter-Blubbern“ („Versickern“) des letzten Elements bis die Heap- Eigenschaft wieder gilt Beispiel Hinzufügen: 60 42 30 5 2 4 9 kommt hinzu: Die Heap-Eigenschaft (kein Nachfolger ist größer) ist verletzt. 9 Welche Arrays entsprechen den Bäumen? 60 42 30 9 2 4 5 Die 9 wird mit ihrem Vorgänger vertauscht. Das Verfahren stoppt, da die Heap-Eigenschaft jetzt erfüllt ist. Seite 49 Heap Heap: Höhen-balancierter, fast sortierter Binärbaum Beispiel Entfernen: 60 42 30 42 60 wird entnommen. 60 30 9 2 4 5 5 9 2 4 Tausch mit dem größten Nachfolger 5 42 30 9 2 Das letzte Element wird zur Wurzel. 4 42 5 30 Aufgabe: Welche Arrays entsprechen den Bäumen? 9 2 4 Tausch mit dem größten Nachfolger Seite 50 Heap Heap: Höhen-balancierter, fast sortierter Binärbaum Heap: Baum und Array-Darstellung Die Nachfolger von a[i] sind a[2*i+1] und a[2*i+2] 60 1 3 0 42 30 9 2 60 42 9 30 2 4 5 5 4 0 6 4 v 2 2 3 4 5 6 5 i 2*i + 1 2*i + 2 W 1 i t Seite 51 2*i Heap Heap-Sort Sortieren mit einem Heap: – Schritt 1: Verwandle das vorgegebene Feld in einen Heap – Schritt 2: Wende die Entnimm-Operation an, bis alles entnommen wurden. Das Verfahren kann ohne Hilfsfelder auf dem vorgegebenen Feld ausgeführt werden. Beispiel: Schritt 1 („heapify“) 2 9 7 6 5 |8 2 9 8 6 |5 7 2 9 8 |6 5 7 2 9 |8 6 5 7 9 |2 8 6 5 7 9 |6 8 2 5 7 |9 6 8 2 5 7 im Baum! 8 ist größer als sein Vorgänger 7: tausche 7 mit seinem größten Nachfolger 5 ist kleiner als sein Vorgänger 9: OK 6 ist kleiner als sein Vorgänger 9: OK 8 ist größer als sein Vorgänger 2: tausche 2 mit seinem größten Nachfolger 2 ist kleiner als seine Nachfolger 6 und 5 : tausche 2 mit seinem größten Nachfolger 6 ist größer als seine Nachfolger: OK 9 ist größer als seine Nachfolger: OK Aufgabe: Welche Bäume entsprechen den Arrays? Seite 52 Heap Heap-Sort Beispiel: Schritt 2: (entnimm) 9 6 8 2 5 7 9 nach hinten und aus dem Baum entfernen 7 6 8 2 5 |9 Heap-Struktur wiederherstellen 8 6 7 2 5 |9 8 nach hinten und aus dem Baum entfernen 5 6 7 2 |8 9 8 nach hinten und aus dem Baum entfernen 5 6 7 2 |8 9 Heap-Struktur wiederherstellen 7 6 5 2 |8 9 7 nach hinten und aus Baum entfernen 2 6 5 |7 8 9 Heap-Struktur wiederherstellen 6 2 5 |7 8 9 6 nach hinten und aus Baum entfernen 5 2 |6 7 8 9 Heap-Struktur ist gegeben, 5 entnehmen und aus Baum entfernen 2 |5 6 7 8 9 2 entnehmen und aus Baum entfernen 2 5 6 7 8 9 Baum leer: fertig Welche Bäume entsprechen den Arrays? http://www.youtube.com/watch?v=a7aXYoFquNY https://www.youtube.com/watch?v=_bkow6IykGM Seite 53 Heap Heap-Operationen Algorithmus: Entnehmen durch „Versenken des Letzten“ procedure remove : Element = { assert n > 0 result ← h[1] h[1] ← h[n] n ← n-1 bubbleDown(1) } procedure bubbleDown(i: NatNum) = { if 2*i ≤ n if 2*i+1 > n or h[2*i] ≤ h[2*i+1] then m ← 2*i m: Index des else m ← 2*i+1 Nachfolgers mit der kleineren Priorität fi if h[i] > h[m] then swap(h[i], h[m]) bubbleDown(m) fi fi } Achtung: Indexpositionen in h beginnen mit 1. Seite 54 Heap Heap-Operationen Algorithmus: Einfügen durch „Hoch-Blubbern“ procedure insert(e: Element) = { assert n < w n++; h[n] ← e bubbleUp(n) } procedure bubbleUp(i: NatNum) = { if i=1 or h[i/2] ≤ h[i] then return fi swap(h[i], h[i/2]) bubbleUp(i/2) } Achtung: Indexpositionen in h beginnen mit 1. Seite 55