WS 2011/12 Fakultät Angewandte Informatik Programmierung verteilter Systeme 17.10.2011 Prof. Dr. Bernhard Bauer Übungen zur Vorlesung Informatik II, Blatt 1 Abgabe: Montag, 24.10.2011, 12.00 Uhr, Informatik II - Briefkasten und Programme zusätzlich per Mail an den Tutor, bitte Namen und Matrikelnummer angeben. In diesem Übungsblatt sollen die Möglichkeiten der Datenstrukturierung in der Programmiersprache C aufgefrischt werden. In Java werden diese Möglichkeiten dann erweitert, so dass dies der perfekte Einstieg in die Programmierung mit Java ist. Es soll der Datentyp einer sogenannten doppelt verketteten Liste zur Verwaltung von Mengen ganzer Zahlen betrachtet werden. Dieser ist wie folgt definiert: typedef struct node { int value; struct node * next; struct node * previous; } NODE; typedef struct list { NODE * start; int size; } NATSET; Ein Objekt vom Typ NATSET hat zwei Komponenten. Die Komponente start ist ein Zeiger auf einen Knoten, nämlich den ersten Knoten der Liste. Der Wert der Komponente size entspricht immer der Anzahl der Knoten der Liste. Jeder Knoten dient der Verwaltung einer ganzen Zahl durch die Komponente value und hat in den Komponenten next und previous Zeiger auf den nächsten und vorherigen Knoten der Liste. Damit hat also die Komponente previous des ersten Knotens der Liste den Wert NULL, ebenso wie die Komponente next des letzten Knotens. Eine Menge M ganzer Zahlen wird durch eine unsortierte Liste repräsentiert, die für jedes Element m M genau einen Knoten hat, dessen Komponente value den Wert m hat. Die leere Menge wird also durch eine leere Liste repräsentiert. Ziel ist es nun, einige Funktionen zu implementieren, die das Rechnen mit derart repräsentierten Mengen erlauben, ohne die Datenstruktur zu kennen. Dabei ist entscheidend, dass die Funktionen alle Datenstrukturinvarianten berücksichtigen. Die hier zu beachtenden Datenstrukturinvarianten sollen im Folgenden noch einmal zusammengefasst aufgezählt werden: - Der Wert der Komponente size entspricht der Anzahl der Knoten der Liste. - Der Wert der Komponente value von zwei verschiedenen Knoten ist niemals gleich. - Der Wert der Komponente previous des ersten Knotens ist NULL - Der Wert der Komponente next des letzten Knotens ist NULL Aus Gründen der Effizienz arbeiten wir natürlich nur mit Zeigern für die Parameterübergabe. 1 Aufgabe 1 * Implementieren Sie die folgenden Funktionen: int getSize (NATSET * pm) Gibt den Wert der Komponente size von *pm zurück NODE * getStart (NATSET * pm) Gibt den Wert der Komponente start von *pm zurück void setStart (NATSET * pm, NODE * l) Setzt den Wert der Komponente start von *pm auf l int getWert (NODE * k) Gibt den Wert der Komponente value von *k zurück void setWert (NODE * k, int m) Setzt den Wert der Komponente value von *k auf m NODE * getNext (NODE * k) Gibt den Wert der Komponente next von *k zurück void setNext (NODE * k, NODE * l) Setzt den Wert der Komponente next von *k auf l NODE * getPrevious (NODE * k) Gibt den Wert der Komponente previous von *k zurück void setPrevious (NODE * k, NODE * l) Setzt den Wert der Komponente previous von *k auf l Aufgabe 2 ** Implementieren Sie die folgenden Funktionen, wobei Sie bitte nicht mehr direkt auf die Komponenten der Datenstrukturen NODE und NATSET zugreifen, falls dafür eine Funktion aus Aufgabe 1) zur Verfügung steht: int iselem (int m, NATSET * pm) Soll 1 zurückgeben wenn m *pm und 0 zurückgeben wenn m *pm void printset(NATSET * pm) Gibt alle Elemente der Menge *pm in einer Zeile, jeweils durch ein Leerzeichen getrennt, in der Kommandozeile aus. NATSET * emptyset() Reserviert dynamisch Speicherplatz für eine Variable vom Typ NATSET, initialisiert diese Variable als die leere Menge und gibt einen Zeiger auf diese Variable zurück. Im Falle eines Fehlers soll NULL zurückgegeben werden. void destroy(NATSET *pm) Zerstört die übergegeben Liste indem der dynamisch reservierte Speicherplatz wieder freigegeben wird. Dazu muss insbesondere auch für alle Knoten der Liste der Speicherplatz freigegeben werden. 2 Aufgabe 3 *** Implementieren Sie die folgenden Funktionen, wobei Sie bitte nicht mehr direkt auf die Komponenten der Datenstrukturen NODE und NATSET zugreifen, falls dafür eine Funktion aus Aufgabe 1) zur Verfügung steht: int insert (int m, NATSET * pm) Einfügen von m in *pm an der letzten Stelle, falls m noch nicht vorhanden ist. Dazu muss dynamisch Speicherplatz für einen Knoten zur Repräsentation von m reserviert werden, der dann an letzter Stelle der Liste angehängt wird. Soll -1 zurückgeben bei Auftreten eines Fehlers, sonst 0. int delete (int m, NATSET * pm) Löschen von m aus *pm. Soll -1 zurückgeben bei Nichtvorhandensein eines Knotens mit dem Wert m, sonst 0. Es soll insbesondere der Speicherplatz des Knotens mit dem Wert m (so er vorhanden ist) freigegeben werden. Aufgabe 4 ** Testen Sie alle obigen Funktionen in einem Hauptprogramm. Benutzen Sie dazu nur obige Funktionen und greifen Sie nicht direkt auf Komponenten der Datenstruktur zu! Diskutieren Sie, wie ein Programmierer dummerweise Datenstruktur-Invarianten zerstören könnte, indem er doch direkt auf Komponenten der Datenstrukturen zugreift. 3