Übungspaket 32 Einfach verkettete, sortierte Liste Übungsziele: Skript: 1. Aufbau einer einfach verketteten, sortierten Liste 2. Traversieren von Listen 3. Vereinfachung durch ein Dummy-Element Kapitel: 75 und insbesondere Übungspaket 29 und 31 Semester: Wintersemester 2017/18 Betreuer: Kevin, Theo, Thomas und Ralf Synopsis: In diesem Übungspaket werden wir endlich eine einfach verkettete, sortierte Liste programmieren. Dabei orientieren wir uns an der in der Vorlesung vorgestellten Wirth schen Variante, die das Programmieren deutlich vereinfacht. Da das Programmieren sortierter Listen erst mal recht komplex ist, werden wir die wesentlichen Aspekte wiederholen bzw. gezielt darauf hin arbeiten. Also, nicht verzweifeln, sondern einfach probieren...
Teil I: Stoffwiederholung Aufgabe 1: Allgemeine Fragen zu Listen Erkläre mit eigenen Worten, was eine einfach verkettete, sortierte Liste ist. Was versteht man unter Traversieren? Was ist der Unterschied zwischen einer linearen, sortierten Liste und einem Stack? Aufgabe 2: Detailfragen zu Listen Die folgenden Fragen beziehen sich immer auf eine einfach verkettete, sortierte Liste oder deren Elemente. Wie viele Nachfolger hat jedes Element? Wie viele Vorgänger hat jedes Element? Woran erkennt man das Listenende? Wie findet man den Listenanfang? Wie kann man eine Liste ausgeben? Wie kann man sie invertiert ausgeben? Wie muss man sich das vorstellen? Einführung in die Praktische Informatik, Wintersemester 2017/18 32-1
Aufgabe 3: Einfügen neuer Elemente in eine Liste Im Skript haben wir recht ausführlich über die algorithmische Umsetzung des sortierten Einfügens geschrieben. Zur Rekapitulation haben wir eine Reihe von Fragen zusammengetragen. Welche vier Fälle muss man beim Einfügen neuer Elemente beachten? 1. 2. 3. 4. In wie vielen Fällen wird der Startzeiger verändert? Ist das programmiertechnisch gut oder schlecht? Warum? In den meisten Fällen benutzt man eine Schleife, um diejenige Stelle zu finden, an der man einfügen möchte. Welches Programmierproblem ergibt sich dadurch? Aufgabe 4: Die Wirth sche Variante Im Skript haben wir auch die Variante von Wirth diskutiert. Was ist die wesentliche Idee? Die Wirth sche Idee hat einige Besonderheiten und Vorteile. Hierzu folgende Fragen: Wird der Startzeiger verändert? Wie viele Fälle werden unterschieden? Wofür ist das Dummy Element? Wo bleibt der Suchzeiger stehen? Wie wird dann aber eingefügt? Welchen Algorithmus nehmen wir? 32-2 Wintersemester 2017/18, Einführung in die Praktische Informatik
Teil II: Quiz Aufgabe 1: Positionierung innerhalb von Listen Nehmen wir an, wir haben die folgende Definition einer Liste sowie die folgende Funktion zur Positionierung eines Zeigers. 1 typedef struct user { int i; } DATA ; 2 3 typedef struct _element { 4 struct _element * next ; 5 DATA data ; 6 } ELEMENT, *EP; 7 8 EP position ( EP listp, int val ) 9 { 10 while ( listp -> data.i < val ) 11 listp = listp -> next ; 12 return listp ; 13 } Nun gehen wir davon aus, dass wir bereits die folgende Liste (in unserem Hauptprogramm) aufgebaut haben: start i : 1 i : 3 i : 5 i : 7 Auf welche Elemente zeigen die Zeiger start und p nach folgenden Funktionsaufrufen: Funktionsaufruf start p Anmerkung p = position( start, 3 )...................................................................... p = position( start, -1 )...................................................................... p = position( start->next, -1 )...................................................................... p = position( start, 6 )...................................................................... p = position( start, 7 )...................................................................... p = position( start, 8 )...................................................................... Schlussfolgerung: Einführung in die Praktische Informatik, Wintersemester 2017/18 32-3
Aufgabe 2: Positionierung: zweiter Versuch Aufgrund des vorherigen Programmabsturzes bei Suchwerten, die größer als das größte Element der Liste waren, haben wir unser Programm wie folgt verändert: 1 typedef struct user { int i; } DATA ; 2 3 typedef struct _element { 4 struct _element * next ; 5 DATA data ; 6 } ELEMENT, *EP; 7 8 EP position ( EP listp, int val ) 9 { 10 while ( listp ->next -> data.i < val ) 11 listp = listp -> next ; 12 return listp ; 13 } Wir betrachten wieder die gleiche Liste sowie die gleichen Funktionsaufrufe: start i : 1 i : 3 i : 5 i : 7 Auf welche Elemente zeigen die Zeiger start und p nach folgenden Funktionsaufrufen: Funktionsaufruf start p Anmerkung p = position( start, 3 )...................................................................... p = position( start, -1 )...................................................................... p = position( start->next, -1 )...................................................................... p = position( start, 6 )...................................................................... p = position( start, 7 )...................................................................... p = position( start, 8 )...................................................................... Schlussfolgerung: 32-4 Wintersemester 2017/18, Einführung in die Praktische Informatik
Aufgabe 3: Positionierung: dritter Versuch So richtig schön waren die beiden vorherigen Versuche nicht. Versuchen wir es also nochmals, diesmal mit einem etwas modifizierten Algorithmus sowie einer modifizierten Liste. 1 typedef struct user { int i; } DATA ; 2 3 typedef struct _element { 4 struct _element * next ; 5 DATA data ; 6 } ELEMENT, *EP; 7 8 EP position ( EP listp, int val ) 9 { 10 while ( listp -> data.i < val && listp -> next!= 0 ) 11 listp = listp -> next ; 12 return listp ; 13 } start i : 1 i : 5 i : 7 i : BIG Auf welche Elemente zeigen die Zeiger start und p nach folgenden Funktionsaufrufen: Funktionsaufruf start p Anmerkung p = position( start, 3 ).................................................................................. p = position( start, -1 ).................................................................................. p = position( start, 6 ).................................................................................. p = position( start, 7 ).................................................................................. p = position( start, 8 ).................................................................................. Schlussfolgerung: Position von p: Einführung in die Praktische Informatik, Wintersemester 2017/18 32-5
Aufgabe 4: Speicherallokation Gegeben sei folgendes Programmstück: 1 typedef struct user { int i; } DATA ; 2 3 typedef struct _element { 4 struct _element * next ; 5 DATA data ; 6 } ELEMENT, *EP; 7 8 EP newelement ( int val, EP next ) 9 { 10 EP new = malloc ( sizeof ( ELEMENT ) ); 11 if ( new ) 12 { 13 new -> data.i = val ; new -> next = next ; 14 } 15 return new ; 16 } Vervollständige das Speicherbild für die folgenden beiden Aufrufe von newelement(). Wie bei fast allen Übungen gehen wir davon aus, dass sowohl int-werte als auch Zeiger immer vier Bytes im Arbeitsspeicher belegen. Ferner nehmen wir an, dass der Aufruf der Funktion newelement() die Adresse 0xFA00 liefert. 1. EP p = newelement( 4711, 0 ); Segment:.................. Adresse Variable Wert 0xFE7C EP p: Segment:................. Adresse Wert 0xFA04 0xFA00 2. ELEMENT el; EP p = newelement( 815, & el ); Segment:........................ Adresse Variable Wert 0xFE7C el.data.i: 0xFE78 el.next : 0xFE74 EP p : Segment:................. Adresse Wert 0xFA04 0xFA00 Vielen hilft es beim Verstehen, wenn sie zusätzlich noch die Zeiger in die Speicherbildchen einzeichnen. 32-6 Wintersemester 2017/18, Einführung in die Praktische Informatik
Aufgabe 5: Elemente einfügen Für die beiden letzten Quizaufgaben haben wir den Positionierungsalgorithmus aus Aufgabe 3 um ein paar Zeilen erweitert. Der erste Parameter dieser neuen Funktion ist die bisher im Arbeitsspeicher aufgebaute Liste. Der zweite Parameter ist ein Zeiger auf ein neues Element, wie wir es gerade eben in der vorherigen Aufgabe gesehen haben: 1 void insertelement ( EP list, EP new ) 2 { 3 DATA tmp ; 4 while ( list -> data.i < new -> data.i && list -> next!= 0 ) 5 list = list -> next ; 6 tmp = new -> data ; 7 * new = * list ; 8 list -> next = new ; 9 list -> data = tmp ; 10 } In den beiden folgenden Aufgaben wird die Funktion insertelement() immer mit der selben Liste aufgerufen. Sie hat nur ein Datenelement. Beim ersten Mal wird das Datenelement 5, beim zweiten Mal 13 eingefügt. Der in den Abbildungen verwendete start-zeiger ist der im Hauptprogramm verwaltete Startzeiger der Liste. Er dient nur zur Illustrierung und wird nicht weiter verwendet. Bearbeite nun die beiden Fälle: 1. Einfügen des Datenelementes 5: Zeile 3: start new i : 9 i : 5 i : BIG Ende Zeile 9: list start new i : i : i : Einführung in die Praktische Informatik, Wintersemester 2017/18 32-7
2. Einfügen des " Datenelementes\ 13: Zeile 3: start new i : 9 i : 13 i : BIG Ende Zeile 9: list start new i : i : i : Damit wären wir jetzt für den Anwendungsfall gerüstet. 32-8 Wintersemester 2017/18, Einführung in die Praktische Informatik
Teil III: Fehlersuche Aufgabe 1: Ausgabe von Listen Die meisten Fehler- und Problemfälle haben wir bereits besprochen. Doch Dr. L. Ist- Wirth hat das Ausgeben Wirth scher Listen noch nicht im Griff. Was lief hier schief...? 1 void prtlist ( EP p ) 2 { 3 printf ( " Liste :" ); 4 do { 5 p = p-> next ; 6 printf ( " %i", p-> data.i ); 7 } while ( p!= 0 ); 8 printf ( "\n" ); 9 } Einführung in die Praktische Informatik, Wintersemester 2017/18 32-9
Teil IV: Anwendungen Aufgabe 1: Definition einer geeigneten Datenstruktur 1. Aufgabenstellung Definiere eine Datenstruktur für lineare Listen, in der jedes Element ein einzelnes Zeichen aufnehmen kann. 2. Kodierung Aufgabe 2: Allokation eines neuen Elementes 1. Aufgabenstellung Entwickle eine Funktion, die dynamisch ein neues Listenelement generiert. 2. Entwurf Deklaration: neues Element alloziieren: 3. Kodierung 32-10 Wintersemester 2017/18, Einführung in die Praktische Informatik
Aufgabe 3: Sortiertes Einfügen eines neuen Elementes 1. Aufgabenstellung Nun wird s ernst. Entwickle eine Funktion, die ein neues Element an der richtigen Stelle in die Liste einfügt. Wir gehen wieder davon aus, dass die Elemente der Liste aufsteigend sortiert werden sollen. 2. Entwurf Vervollständige zunächst die Funktionsdeklaration: Neues Element einfügen: 3. Kodierung Einführung in die Praktische Informatik, Wintersemester 2017/18 32-11
Aufgabe 4: Ausgabe der Liste 1. Aufgabenstellung Entwickle eine Funktion, die nacheinander alle Elemente einer übergebenen Liste ausgibt. 2. Entwurf Vervollständige zunächst die Funktionsdeklaration: Liste ausgeben: 3. Kodierung Aufgabe 5: Initialisierung der Liste 1. Aufgabenstellung Jetzt fehlt noch die richtige Initialisierung einer neuen Liste. Im Skript haben wir gesehen, dass der konkrete Datenwert des Dummys unerheblich ist. Wichtig ist hingegen, dass der Next-Zeiger ein Null-Zeiger ist. Dies lässt sich einfach realisieren. 2. Kodierung 32-12 Wintersemester 2017/18, Einführung in die Praktische Informatik
Aufgabe 6: Ein Hauptprogramm zum Testen Den bereits erlernten argc/argv Mechanismus (siehe auch Übungspaket 25) können wir hier sehr gut zum intelligenten Testen unseres Listen-Programms verwenden. Wenn wir auf diesen Mechanismus zurückgreifen, brauchen wir nicht alles fest im Hauptprogramm zu kodieren und können leicht und umfangreich testen. 1. Aufgabenstellung Entwickle ein Hauptprogramm, mit dessen Hilfe wir unsere Listen-Funktionen in geeigneter Art und Weise testen können. Mit Hilfe des argc/argv-mechanismus soll das Hauptprogramm die folgenden Funktionalitäten anbieten: 1. Egal, wie wir unser Programm aufrufen, soll es die Liste direkt nach der Initialisierung sowie vor dem Programmende ausgeben. 2. Das erste Argument (argv[1]) soll alle Zeichen enthalten, die wir in die Liste aufnehmen. Beispiel:./myListe vacation soll am Ende zu folgender Ausgabe führen: a a c i n o t v 3. Sollte noch ein zweites Argument (argv[2]) angegeben werden, dann soll die Liste nach jedem Einfügen ausgegeben werden. Mittels der bisherigen Vorübungen, insbesondere Übungspaket 25, sollte die Umsetzung keine größeren Schwierigkeiten bereiten. 2. Kodierung Endlich fertig :-) Einführung in die Praktische Informatik, Wintersemester 2017/18 32-13
Aufgabe 7: Doppelt verkettete Listen In dieser letzten Übungsaufgabe geht es um doppelt verkettete Listen. Die Bearbeitung ist vor allem theoretischer Natur, weshalb das Implementieren und Testen am Rechner freiwillig sind. Wohin zeigen die Zeiger bei doppelt verketteten Listen? Definiere eine Datenstruktur für ein Element einer doppelt verketteten Liste, die ein int und zwei double Variablen aufnehmen kann. Skizziere an einem Beispiel, welche Zeiger beim Einfügen eines neuen Elementes in eine bereits vorhandene Liste in welcher Form umgehängt werden müssen. 32-14 Wintersemester 2017/18, Einführung in die Praktische Informatik