3 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 Wintersemester 2006/07 3.1 Lineare Listen Eine Folge Liste von Knoten von folgendem Typ: kann implementiert werden als eine lineare public class Node { Node int key; Node next; Node(int k) { key = k; next = null; Prof. Dr. Dietmar Seipel 129
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: a 1 a 2... a n head Prof. Dr. Dietmar Seipel 130
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
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: List() { head = null; count = 0; Konstruktorfunktion Prof. Dr. Dietmar Seipel 132
Zum Einfügen eines neuen Elements v hinter dem Knoten t einer linearen Liste verwenden wir die Funktion insert. t a i a i+1 x v Zum Entfernen eines Knotens t aus einer linearen Liste verwenden wir die Funktion remove. x a t i-1 a t i a i+1 Prof. Dr. Dietmar Seipel 133
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
public class DoubleNode { Double Node int key; DoubleNode next, prior; DoubleNode(int k) { key = k; next = prior = null; prior next...... Prof. Dr. Dietmar Seipel 135
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
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
Node /** * Liefert eine Textausgabe des Objektes, siehe * {@link java.lang.object#tostring */ public String tostring() { return "[" + key + "]"; Prof. Dr. Dietmar Seipel 138
/** Ein Listenelement */ public class DoubleNode { Double Node /** Schluesselwert */ int key; /** Das naechste Element */ DoubleNode next; /** Das vorhergehende Element */ DoubleNode prior; Prof. Dr. Dietmar Seipel 139
Double Node /** * Ein einfacher Konstruktor mit Schluessel. */ DoubleNode(int k) { key = k; next = prior = null; Prof. Dr. Dietmar Seipel 140
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
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
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
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
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
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
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
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
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
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
3.2 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
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
3.3 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 a 1 a 2... a n null head top Prof. Dr. Dietmar Seipel 153
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
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
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
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
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
Anwendung Speicherung der Rücksprungadressen bei geschachtelten Unterprogrammaufrufen. Hauptprogramm Unterprogramm 1 (2) push (A2) (1) push (A1) A2 Unterprogramm 2 A1 (3) A2 = pop (4) push (A3) Unterprogramm 3 (6) A1 = pop A3 (5) A3 = pop Prof. Dr. Dietmar Seipel 159
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 A3 A1 A1 A1 A1 A1 (0) (1) (2) (3) (4) (5) (6) Prof. Dr. Dietmar Seipel 160
3.4 Geordnete binäre Wurzelbäume 3.4.1 Der Wurzelbaum Beispiel 4 Knoten: 4 Kante: (4,5) (5 ist Nachfolger von 4) 5 3 7 8 6 11 2 10 1 9 Prof. Dr. Dietmar Seipel 161
Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Ein Wurzelbaum besteht aus einer Menge von Knoten, und einer Menge von Kanten,, so dass es einen eindeutigen Knoten, genannt Wurzel gibt, von dem alle Knoten über jeweils eindeutige Wege erreichbar sind. Ein Weg von ist dabei eine Folge so dass für je zwei aufeinanderfolgende Knoten existiert. (Merke: nach aufgefasst werden!) nach kann als Weg von und von Knoten, eine Kante Prof. Dr. Dietmar Seipel 162
Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Für einen Knoten bezeichnet die Menge der Vorgänger bzw. der Nachfolger von in. Offensichtlich gilt in einem Wurzelbaum: falls falls nicht die Wurzel ist, die Wurzel ist. 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, von v. mit heißt rechter Sohn v v v Prof. Dr. Dietmar Seipel 164
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
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
/** * Konstruktor. */ BinTreeNode(int v) { key = v; lson = rson = null; BinTreeNode Prof. Dr. Dietmar Seipel 167
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
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
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
BinTreeNode /** * Liefert eine Textausgabe des Objektes, siehe * {@link java.lang.object#tostring */ public String tostring() { return "[" + key + "]"; Prof. Dr. Dietmar Seipel 171
/** * Binaerer Baum */ public class BinTree { 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
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
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
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: LWR RWL Prof. Dr. Dietmar Seipel 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 3 7 8 6 11 2 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 3 7 8 6 11 2 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
Lemma (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. Induktionsanfang, : trivial Induktionsschluss, : Sei die Behauptung für alle eckigen Bäume mit gezeigt. Wir betrachten nun einen eckigen Baum bereits. Die Wurzel von 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: a) Die LWR Folge kann mittels von ist. zerlegt werden in, so dass bzw. die LWR Folge für den linken bzw. den rechten Teilbaum 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 rechten Teilbaum von ist. bzw. die WLR Folge für den linken bzw. Dann können wir die Induktionsannahme auf die beiden Teilbäume von bzw. deren Folgen beiden Teilbäume haben jeweils maximal Knoten). Also kann der gesamte Baum eindeutig rekonstruiert werden. und bzw. und anwenden (denn die Prof. Dr. Dietmar Seipel 182
die Wurzel des linken Teilbaumes. Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Beispiel zu (i) LWR WLR Also ist die Wurzel. LWR: WLR: Also ist 1 2 3 5 4 Prof. Dr. Dietmar Seipel 183
Praktische Informatik I - Algorithmen und Datenstrukturen Wintersemester 2006/07 Folgende Bäume haben sowohl die gleiche WLR als auch die gleiche LRW Ordnung: T 1 : T 1 2 : 2 3 5 1 2 3 5 WLR LRW Prof. Dr. Dietmar Seipel 184