Praktische Informatik I - Algorithmen und Datenstrukturen 3 Wintersemester 2006/07 Dynamische Datenstrukturen Beispiele für dynamische Datenstrukturen sind Lineare Listen Schlangen Stapel Bäume Prof. Dr. Dietmar Seipel 128 Praktische Informatik I - Algorithmen und Datenstrukturen 3.1 Wintersemester 2006/07 Lineare Listen Eine Folge kann implementiert werden als eine lineare Liste von Knoten von folgendem Typ: Node public class Node { int key; Node next; Node(int k) { key = k; next = null; } } Prof. Dr. Dietmar Seipel 129 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Diese Klassendefinition zeigt an, dass ein Knoten aus zwei Komponenten besteht: die key – Komponente ist eine ganze Zahl die next – Komponente ist ein Verweis auf einen (anderen) Knoten. Veranschaulichung L: a1 a2 ... an head Prof. Dr. Dietmar Seipel 130 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Bereitstellen von Speicherplatz für ein Listenelement durch die Konstruktorfunktion : Node x = new Node(v); Die Konstruktorfunktion liefert als Resultat einen Verweis auf einen freien Speicherplatz für ein Listenelement. Prof. Dr. Dietmar Seipel 131 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Implementierung für lineare Listen: Kopfzeiger head Anzahl count der gespeicherten Elemente Die leere Liste ist gegeben durch den Verweis head = null und count = 0. Sie wird durch folgende Konstruktorfunktion erzeugt: Konstruktorfunktion List() { head = null; count = 0; } Prof. Dr. Dietmar Seipel 132 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Zum Einfügen eines neuen Elements v hinter dem Knoten t einer linearen Liste verwenden wir die Funktion insert. t ai ai+1 x v Zum Entfernen eines Knotens t aus einer linearen Liste verwenden wir die Funktion remove. x Prof. Dr. Dietmar Seipel ai-1 t ai t’ ai+1 133 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Falls t nicht der Kopf der Liste ist, so bestimmen wir den Vorgängerknoten x von t und setzen dessen Verweis auf den Nachfolgerknoten t’ von t. Zum Verketten zweier linearer Listen L1 und L2 gegeben durch head1, count1 und head2, count2 verwenden wird die Funktion concat. Um das Entfernen von Listenelementen bei gegebener Position möglichst einfach ausführen zu können, kann man zu jedem Listenelement nicht nur einen Verweis next auf das nächstfolgende, sondern auch einen Verweis prior auf das vorhergehende Listenelement abspeichern. ( doppelt verkette Speicherung) Prof. Dr. Dietmar Seipel 134 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Double Node public class DoubleNode { int key; DoubleNode next, prior; DoubleNode(int k) { key = k; next = prior = null; } } next .. .. .. prior Prof. Dr. Dietmar Seipel 135 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Bei doppelt verketteter Speicherung können das Entfernen und das Verketten einfacher realisiert werden. Wir haben die nach dem Entfernen nicht mehr benötigten Knoten nicht zur neuen – und eventuell anderen – Verwendung explizit freigegeben, sondern sie nur aus der Liste durch Umlegen von Verweisen entfernt. Man muss in C diese Knoten explizit freigeben. In JAVA werden Elemente, auf die keine Verweise mehr existieren vom Garbage – Kollektor automatisch aus dem Speicher entfernt. Prof. Dr. Dietmar Seipel 136 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Node /** Ein Listen- bzw. Stackelement. */ public class Node { /** Schluesselwert */ int key; /** Das naechste Element */ Node next; /** * Ein einfachern Konstruktor mit Schluessel. */ Node(int k) { key = k; next = null; } Prof. Dr. Dietmar Seipel 137 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Node /** * Liefert eine Textausgabe des Objektes, siehe * {@link java.lang.Object#toString} */ public String toString() { return "[" + key + "]"; } } Prof. Dr. Dietmar Seipel 138 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Double Node /** Ein Listenelement */ public class DoubleNode { /** Schluesselwert */ int key; /** Das naechste Element */ DoubleNode next; /** Das vorhergehende Element */ DoubleNode prior; Prof. Dr. Dietmar Seipel 139 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Double Node /** * Ein einfacher Konstruktor mit Schluessel. */ DoubleNode(int k) { key = k; next = prior = null; } } Prof. Dr. Dietmar Seipel 140 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** Eine einfach verkettete lineare Liste. */ public class List { /** Das erste Element der Liste */ Node head; /** Die Anzahl der gespeicherten Elemente */ int count; /** * Konstruktor fuer eine leere Liste */ List() { head = null; count = 0; } Prof. Dr. Dietmar Seipel 141 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** * Fuegt einen neuen Knoten mit Schluessel v * nach dem Knoten t in die Liste ein. */ void insert(int v, Node t) { Node x = new Node(v); count++; if (t == null) { //Einfuegen am Anfang x.next = head; head = x; } else { x.next = t.next; t.next = x; } } Prof. Dr. Dietmar Seipel 142 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** * Haengt einen neuen Knoten mit Schluessel v an das * Ende der Liste an (unter Benutzung von insert). */ void add(int v) { Node last = head; if (last == null) { insert(v, null); } else { //Letztes Listenelement suchen while (last.next != null) last = last.next; insert(v, last); } } Prof. Dr. Dietmar Seipel 143 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** * Loescht den Knoten aus der Liste. */ void remove(Node t) { count--; if (t == head) { head = t.next; } else { Node x = head; while (x.next != t) x = x.next; x.next = t.next; } } Prof. Dr. Dietmar Seipel 144 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** * Haengt die Listen von head1 und head2 aneinander * und speichert sie in dieser Liste. */ void concat (Node head1, int count1, Node head2, int count2) { count = count1 + count2; if (head1 == null) { head = head2; } else { head = head1; Node x = head1; while (x.next != null) x = x.next; x.next = head2; } } Prof. Dr. Dietmar Seipel 145 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** Liefert eine Textausgabe des Objektes */ public String toString() { String s = getClass().getName() + "[count=" + count + ",{"; for (Node i = head; i != null; i = i.next) { s += i.toString() + "->"; } s += "null}]"; return s; } Prof. Dr. Dietmar Seipel 146 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Lineare Liste /** Kleines Testprogramm. */ public static void main(String[] args) { List l = new List(); for (int i = 0; i < args.length; i++) { l.add(Integer.parseInt(args[i])); } System.out.println(l); } } Prof. Dr. Dietmar Seipel 147 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Doppelt verkettete lineare Liste /** Eine doppelt verkettete lineare Liste.*/ public class DoubleList { /** Das erste Element der Liste */ DoubleNode head; /** Die Anzahl der gespeicherten Elemente */ int count; /** Konstruktor fuer eine leere Liste */ DoubleList() { head = null; count = 0; } Prof. Dr. Dietmar Seipel 148 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Doppelt verkettete lineare Liste void insert(int v, DoubleNode t) { DoubleNode x = new DoubleNode(v); count++; if (t == null) { //Einfuegen am Anfang x.next = head; x.prior = null; head.prior = x; head = x; } else { x.next = t.next; x.prior = t; t.next = x; x.next.prior = x; } } Prof. Dr. Dietmar Seipel 149 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Doppelt verkettete lineare Liste void remove (DoubleNode t) { count--; if (t.prior != null) t.prior.next = t.next; if (t.next != null) t.next.prior = t.prior; if (t == head) head = t.next; } } Prof. Dr. Dietmar Seipel 150 Praktische Informatik I - Algorithmen und Datenstrukturen 3.2 Wintersemester 2006/07 Schlangen Eine Schlange (engl.: queue) ist eine lineare Liste, bei der das Einfügen und Entfernen von Listenelementen auf die beiden extremalen Listenelemente (d.h. Listenanfang und Listenende) beschränkt ist. Prof. Dr. Dietmar Seipel 151 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Bezeichnungen pushhead(v), pushtail(v): fügt das Element v am Anfang bzw. am Ende der Schlange ein. pophead(), poptail(): entfernt das erste bzw. letzte Element der Schlange und gibt seinen Wert als Resultat zurück; undefiniert, falls die Schlange leer ist. v = top(): gibt das erste Element der Schlange als Resultat zurück; ebenfalls undefiniert, falls die Schlange leer ist. empty(): testet ob die Schlange leer ist. init(): erzeugt eine Leere Schlange Prof. Dr. Dietmar Seipel 152 Praktische Informatik I - Algorithmen und Datenstrukturen 3.3 Wintersemester 2006/07 Stapel Ein Stapel (oder Keller; engl.: stack) ist eine lineare Liste, bei der das Einfügen und Entfernen eines Elements auf den Listenkopf beschränkt ist. Realisierung mit verketteten Listen top a1 a2 ... an Prof. Dr. Dietmar Seipel head null top 153 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Implementierung Stack /** * Stack-Klasse */ public class Stack { /** Oberster Knoten des Stacks */ Node top; /** * Konstruktor. Initialisiert den Stack (leer). */ Stack() { top = null; } Prof. Dr. Dietmar Seipel 154 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Stack /** * Legt ein neues Element auf den Stack */ void push(int v) { Node t = new Node(v); t.next = top; top = t; } /** * Liefert das oberste Element des Stacks */ int top() { return top.key; } Prof. Dr. Dietmar Seipel 155 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Stack /** * Liefert das oberste Element des Stacks und * loescht dieses vom Stack */ int pop() { int v = top.key; top = top.next; return v; } /** * Liefert true, falls der Stack leer ist. */ boolean empty() { return (top == null); } Prof. Dr. Dietmar Seipel 156 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Stack /** * Liefert eine Textausgabe des Objektes, siehe * {@link java.lang.Object#toString} */ public String toString() { String s = getClass().getName() + "["; for (Node i = top; i != null; i = i.next) { s += i.toString() + "<-"; } s += "null]"; return s; } Prof. Dr. Dietmar Seipel 157 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Stack /** * Kleines Testprogramm. */ public static void main (String[] args) { Stack s = new Stack(); for (int i = 0; i < args.length; i++) { s.push(Integer.parseInt(args[i])); } System.out.println(s); } } Prof. Dr. Dietmar Seipel 158 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Anwendung Speicherung der Rücksprungadressen bei geschachtelten Unterprogrammaufrufen. Hauptprogramm Unterprogramm 1 Unterprogramm 2 (2) push (A2) (1) push (A1) A2 (3) A2 = pop Unterprogramm 3 A1 (4) push (A3) A3 (6) A1 = pop Prof. Dr. Dietmar Seipel (5) A3 = pop 159 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Beim Sprung zum Unterprogramm wird die aktuelle Adresse A auf den Stapel gelegt ( push(A) ). Beim Rücksprung wird die ehemals aktuelle Adresse des aufrufenden Programms vom Stapel geholt ( A = pop() ). Wir erhalten folgende Folge von Stapelzuständen: A2 (0) Prof. Dr. Dietmar Seipel A3 A1 A1 A1 A1 A1 (1) (2) (3) (4) (5) (6) 160 Praktische Informatik I - Algorithmen und Datenstrukturen 3.4 Wintersemester 2006/07 Geordnete binäre Wurzelbäume 3.4.1 Der Wurzelbaum Beispiel Knoten: 4 Kante: (4,5) (5 ist Nachfolger von 4) 4 5 7 3 8 6 2 11 10 1 9 Prof. Dr. Dietmar Seipel 161 Praktische Informatik I - Algorithmen und Datenstrukturen besteht aus Ein Wurzelbaum Wintersemester 2006/07 einer Menge von Kanten, von Knoten, und einer Menge , so dass es einen eindeutigen Knoten, genannt Wurzel gibt, von dem alle Knoten über jeweils eindeutige Wege erreichbar sind. ist dabei eine Folge von Knoten, Ein Weg von nach so dass für je zwei aufeinanderfolgende Knoten und eine Kante existiert. kann als Weg von nach aufgefasst werden!) (Merke: Prof. Dr. Dietmar Seipel 162 Praktische Informatik I - Algorithmen und Datenstrukturen Für einen Knoten Wintersemester 2006/07 bezeichnet die Menge der Vorgänger bzw. der Nachfolger von in . Offensichtlich gilt in einem Wurzelbaum: nicht die Wurzel ist, die Wurzel ist. falls falls Ein Wurzelbaum heißt binär , falls jeder Knoten höchstens zwei Nachfolger hat: Prof. Dr. Dietmar Seipel 163 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Ein binärer Wurzelbaum heißt geordnet , falls für jeden Knoten injektive Abbildung eine auf seiner Nachfolgermenge gegeben ist. mit heißt linker Sohn, mit heißt rechter Sohn von v. v v’ Prof. Dr. Dietmar Seipel v’’ 164 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 3.4.2 Baumdarstellung mittels einer Zeigerstuktur Ein Baum kann also durch die Angabe eines Zeigers root auf den Wurzelknoten angegeben werden. Die Zeiger lSon bzw. rSon werden in einem Knoten auf null gesetzt, wenn die entsprechenden Söhne nicht existieren. Für die Klasse BinTree wird ein Stack benötigt, der BinTreeNodes oder allgemein Objects speichern kann (im Gegensatz zu dem oben erstellten Stack für ints.) Falls eine Stack-Implementierung mit Objects gewählt wird, muss in der Methode wlr_durchlauf die Zeile t = s.pop() durch t = (BinTreeNode) s.pop() ersetzt werden. Prof. Dr. Dietmar Seipel 165 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Implementierung BinTreeNode /** * Knoten eines binaeren Baumes */ public class BinTreeNode { /** Schluesselwert */ int key; /** Linker Sohnknoten */ BinTreeNode lson; /** Rechter Sohnknoten */ BinTreeNode rson; Prof. Dr. Dietmar Seipel 166 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTreeNode /** * Konstruktor. */ BinTreeNode(int v) { key = v; lson = rson = null; } Prof. Dr. Dietmar Seipel 167 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTreeNode /** * Gibt die Anzahl der Soehne des Knotens * zurueck (0, 1 oder 2) */ int countSons() { if (lson == null) return (rson == null)?0:1; else return (rson == null)?1:2; } Prof. Dr. Dietmar Seipel 168 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTreeNode /** * Liefert true, falls der Knoten ein * Blatt ist, d.h. keine Kinder hat */ boolean isLeaf() { return (lson == null) && (rson == null); } Prof. Dr. Dietmar Seipel 169 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTreeNode /** * Liefert true, falls der Knoten keinen * Sohn auf der Seite hat, in der der Schluessel * s zu suchen waere */ boolean isLeaf(int s) { return ( (key > s) && (lson == null) ) || ( (key < s) && (rson == null) ); } Prof. Dr. Dietmar Seipel 170 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTreeNode /** * Liefert eine Textausgabe des Objektes, siehe * {@link java.lang.Object#toString} */ public String toString() { return "[" + key + "]"; } } Prof. Dr. Dietmar Seipel 171 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTree /** * Binaerer Baum */ public class BinTree { /** * Baumdurchlauf in LWR-Ordnung */ void lwr_durchlauf(BinTreeNode t) { if (t != null) { lwr_durchlauf(t.lson); System.out.println(t); lwr_durchlauf(t.rson); } } Prof. Dr. Dietmar Seipel 172 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTree /** * Generische Methode zum Baumdurchlauf entsprechend * String id (von der Form "LWR", "WLR" oder "LRW") */ void durchlauf(String id, BinTreeNode t) { if (t != null) { for (int i = 0; i < id.length(); i++) { switch (id.charAt(i)) { case ’L’: durchlauf(id, t.lson);break; case ’R’: durchlauf(id, t.rson);break; case ’W’: System.out.println(t);break; } } } } Prof. Dr. Dietmar Seipel 173 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTree /** * Nicht-rekursiver Baumdurchlauf in WLR-Ordnung */ void wlr_durchlauf(BinTreeNode t) { Stack s = new Stack(); while (t != null) { System.out.println(t); if (t.rson != null) s.push(t.rson); Prof. Dr. Dietmar Seipel 174 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 BinTree if (t.lson != null) { t = t.lson; } else { if (!s.empty()) t = s.pop(); else t = null; } } } } Prof. Dr. Dietmar Seipel 175 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 3.4.3 Ordnungen und Durchlaufprizipien 1. Inordnungen LWR, RWL Beim Durchlaufen der Knoten in LWR- bzw. RWL-Ordnung wird zunächst der linke bzw. rechte Teilbaum, dann die Wurzel, und dann der rechte, bzw. linke Teilbaum durchlaufen. Im Beispiel erhalten wir: Prof. Dr. Dietmar Seipel LWR RWL 176 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Die Ordnungen LWR und RWL sind stets invers zueinander, d.h. ist LWR , so ist RWL 4 5 7 3 8 6 2 11 10 1 9 Prof. Dr. Dietmar Seipel 177 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 2. Randordnungen (a) Präordnungen WLR, WRL Hier wird die Wurzel vor den beiden Teilbäumen durchlaufen. im Beispiel: WLR WRL 4 5 7 3 8 6 2 11 10 1 9 Prof. Dr. Dietmar Seipel 178 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 (b) Postordnungen LRW, RLW Hier werden die beiden Teilbäume vor der Wurzel durchlaufen. im Beispiel: LRW RLW Offenbar sind WLR und RLW bzw. WRL und LRW jeweils zueinander invers . Prof. Dr. Dietmar Seipel 179 Praktische Informatik I - Algorithmen und Datenstrukturen Lemma Wintersemester 2006/07 (Durchlaufordnungen) (i) Ein geordneter binärer Wurzelbaum ist eindeutig bestimmt durch die Angabe einer Inordnung zusammen mit einer Randordnung. z.B. durch LWR und WLR oder durch LWR und LRW. (ii) Die Angabe zweier Inordnungen bzw. zweier Randordnungen reicht für die eindeutige Charakterisierung eines geordneten Wurzelbaumes i.a. nicht aus. z.B. reichen LWR und RWL nicht aus, und ebenso reichen WLR und LRW nicht aus. Prof. Dr. Dietmar Seipel 180 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Beweis Wegen der Äquivalenz von LWR und RWL bzw. von WLR und RLW reicht es aus, den Fall zu betrachten, dass die Inordnung LWR ist und die Randordnung WLR. Wir zeigen die Behauptung durch vollständige Induktion über die Eckenzahl des Baums. : trivial Induktionsanfang, : Induktionsschluss, bereits Sei die Behauptung für alle – eckigen Bäume mit gezeigt. – eckigen Baum . Die Wurzel von Wir betrachten nun einen ist offenbar das erste Element in der WLR – Ordnung. Prof. Dr. Dietmar Seipel 181 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Für die beiden Teilbäume der Wurzel – welche eventuell auch leer sein können – kann nun wie folgt die LWR – Ordnung und die WLR – Ordnung aus den Ordnungen des gesamten Baumes eindeutig bestimmt werden: , so dass a) Die LWR – Folge kann mittels zerlegt werden in bzw. die LWR – Folge für den linken bzw. den rechten Teilbaum von ist. b) Da man jetzt weiß, welche Elemente im linken bzw. rechten Teilbaum von liegen, kann man nun auch die WLR – Folge zerlegen in , so dass bzw. die WLR – Folge für den linken bzw. rechten Teilbaum von ist. Dann können wir die Induktionsannahme auf die beiden Teilbäume von bzw. deren Folgen und bzw. und anwenden (denn die beiden Teilbäume haben jeweils maximal Knoten). Also kann der gesamte Baum eindeutig rekonstruiert werden. Prof. Dr. Dietmar Seipel 182 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Beispiel zu (i) LWR WLR die Wurzel des linken Teilbaumes. Also ist – LWR: – WLR: die Wurzel. Also ist 1 2 5 Prof. Dr. Dietmar Seipel 3 4 183 Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Folgende Bäume haben sowohl die gleiche WLR – als auch die gleiche LRW – Ordnung: T1: T2: 1 2 3 1 2 5 5 WLR LRW Prof. Dr. Dietmar Seipel 3 184