Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.

Ähnliche Präsentationen


Präsentation zum Thema: "Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund."—  Präsentation transkript:

1 Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund

2 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 2 Kapitel 9: Elementare Datenstrukturen Inhalt Definition: Abstrakter Datentyp (ADT) ADT Stapel ADT Schlange ADT Liste ADT Binärer Suchbaum ADT Graph Exkurse: - Einfache Dateibehandlung - C++ Strings

3 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 3 Elementare Datenstrukturen Definition: Abstrakter Datentyp (ADT) ist ein Tripel (T, F, A), wobei T eine nicht leere Menge von Datenobjekten F eine Menge von Operationen, A eine nicht leere Menge von Axiomen, die die Bedeutung der Operationen erklären. Abstrakt? Datenobjekte brauchen keine konkrete Darstellung (Verallgemeinerung). Die Wirkung der Operationen wird beschrieben, nicht deren algorithmische Ausprägung. WAS, nicht WIE!

4 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 4 Beispiel: ADT bool F: Operationen true: bool false: bool not: bool bool and: bool x bool bool or: bool x bool bool A: Axiome not(false)= true not(true) = false and(false, false)= false and(false, true)= false and(true, false)= false and(true, true)= true or(x, y)= not(and(not(x), not(y))) Festlegung, welche Methoden es gibt Festlegung, was die Methoden bewirken Elementare Datenstrukturen

5 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 5 Wenn man ADT kennt, dann kann man ihn überall verwenden. Implementierung der Funktionen für Benutzer nicht von Bedeutung. Trennung von Spezifikation und Implementierung. Ermöglicht späteren Austausch der Implementierung, ohne dass sich der Ablauf anderer Programme, die ihn benutzen, ändert! Nur Operationen geben Zugriff auf Daten! Stichwort: Information Hiding! Eigenschaften Elementare Datenstrukturen

6 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 6 Lineare Datenstrukturen: Keller bzw. Stapel (engl. stack) Aufräumen: Kiste in Keller, oben auf Haufen. Etwas aus Keller holen: Zuerst Kiste, weil oben auf Haufen. Stapel Teller create: Stapel push: Stapel x T Stapel pop: Stapel Stapel top: Stapel T empty: Stapel bool empty(create) = true empty(push(k, x))= false pop(push(k, x))= k top(push(k, x)) = x LIFO: Last in, first out. Elementare Datenstrukturen

7 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 7 Klassendefinition: (Version 1) typedef int T; class Stapel { public: Stapel();// Konstruktor void push(T &x);// Element auf den Stapel legen void pop();// oberstes Element entfernen T top();// oberstes Element ansehen bool empty();// Stapel leer? private: static unsigned int const maxSize = 100; int sz;// Stapelzeiger T data[maxSize];// Speichervorat für Nutzdaten }; Elementare Datenstrukturen

8 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 8 Implementierung: (Version 1) Stapel::Stapel() { sz = -1; } void Stapel::push(T &x) { data[++sz] = x; } void Stapel::pop() { sz--; } T Stapel::top() { return data[sz]; } bool Stapel::empty() { return (sz == -1); } Probleme: Arraygrenzen! Elementare Datenstrukturen Idee: unzulässiger Arrayindex kennzeichnet leeren Stapel

9 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 9 Wann können Probleme auftreten? Bei pop, falls Stapel leer ist: Stackpointer wird -2, anschließendes push versucht auf data[-1] zu schreiben Bei top, falls Stapel leer ist: es wird undefinierter Wert von data[-1] zurückgegeben Bei push, falls Stapel voll ist: es wird versucht auf data[maxSize] zu schreiben ) diese Fälle müssen abgefangen werden! Fehlermeldung! void error(char *info) { cerr << info << endl; exit(1); } gibt Fehlermeldung info aus und bricht das Programm durch exit(1) sofort ab und liefert den Wert des Arguments (hier: 1) an das Betriebssystem zurück Elementare Datenstrukturen

10 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 10 Klassendefinition: (Version 2; Ergänzungen in rot) typedef int T; class Stapel { public: Stapel();// Konstruktor void push(T &x);// Element auf den Stapel legen void pop();// oberstes Element entfernen T top();// oberstes Element ansehen bool empty();// Stapel leer? bool full();// Stapel voll? private: static unsigned int const maxSize = 100; int sz;// Stapelzeiger T data[maxSize];// Speichervorat für Nutzdaten void error(char *info); // Fehlermeldung + Abbruch }; Elementare Datenstrukturen

11 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 11 Implementierung: (Version 2, Änderungen und Zusätze in rot) Stapel::Stapel() { sz = -1; } void Stapel::push(T &x) { if (full()) error("voll"); data[++sz] = x; } void Stapel::pop() { if (empty()) error("leer"); sz--; } Elementare Datenstrukturen T Stapel::top() { if (empty()) error("leer"); return data[sz]; } bool Stapel::empty() { return (sz == -1); } bool Stapel::full() { return (sz == maxSize - 1); } void Stapel::error(char* info) { cerr << info << endl; exit(1); } private Methode: kann nur innerhalb der Klasse aufgerufen werden!

12 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 12 Elementare Datenstrukturen #include #include "Stapel.h" using namespace std; int main() { Stapel s; for (int i = 0; i < 100; i++) s.push(i); cout << s.top() << endl; for (int i = 0; i < 90; i++) s.pop(); while (!s.empty()) { cout << s.top() << endl; s.pop(); } return 0; } Erster Test... Ausgabe:

13 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 13 Lineare Datenstrukturen: Schlange (engl. queue) Schlange an der Supermarktkasse: Wenn Einkauf fertig, dann hinten anstellen. Der nächste Kunde an der Kasse steht ganz vorne in der Schlange. create: Schlange enq: Schlange x T Schlange deq: Schlange Schlange front: Schlange T empty: Schlange bool empty(create) = true empty(enq(s, x))= false deq(enq(s, x))= empty(s) ? s : enq(deq(s), x) front(enq(s, x)) = empty(s) ? x : front(s) Eingehende Aufträge werden geparkt, und dann nach und nach in der Reihenfolge des Eingangs abgearbeitet. FIFO: First in, first out. Elementare Datenstrukturen

14 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 14 Klassendefinition: (Version 1; schon mit Fehlerbehandlung) typedef int T; class Schlange { public: Schlange();// Konstruktor void enq(T &x);// Element anhängen void deq();// erstes Element entfernen T front();// erstes Element ansehen bool empty();// Schlange leer? bool full();// Schlange voll? private: static unsigned int const maxSize = 100; int ez;// Endezeiger T data[maxSize];// Array für Nutzdaten void error(char *info); // Fehlermeldung }; Elementare Datenstrukturen

15 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 15 Implementierung: (Version 1; Fehler bei Arraygrenzen werden abgefangen) void Schlange::enq(T &x) { if (full()) error("voll"); data[++ez] = x; } void Schlange::deq() { if (empty()) error("leer"); for (int i = 0; i < ez; i++) data[i] = data[i+1]; ez--; } Elementare Datenstrukturen Schlange::Schlange():ez(-1) { } T Schlange::front() { if (empty()) error("leer"); return data[0]; } bool Schlange::empty() { return (ez == -1); } bool Schlange::full() { return (ez == maxSize - 1); } void Schlange::error(char *info) { cerr << info << endl; exit(1); } private Methode: kann nur innerhalb der Klasse aufgerufen werden!

16 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 16 Erster Test... Elementare Datenstrukturen #include #include "Schlange.h" using namespace std; int main() { Schlange s; for (int i = 0; i < 100; i++) s.enq(i); cout << s.front() << endl; for (int i = 0; i < 90; i++) s.deq(); while (!s.empty()) { cout << s.front() << endl; s.deq(); } return 0; } Ausgabe:

17 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 17 Laufzeit / Effizienz der Operation deq void Schlange::deq() { if (empty()) error(leer); for (int i = 0; i < ez; i++) data[i] = data[i+1]; ez--; } ez = Anzahl Elemente in Schlange ez viele Datenverschiebungen Worst case: ( maxSize – 1) mal Benutzer des (abstrakten) Datentyps Schlange wird feststellen, dass 1.fast alle Operationen schnell sind, aber 2.die Operation deq vergleichsweise langsam ist. Elementare Datenstrukturen

18 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 18 Idee: Array zum Kreis machen; zusätzlich Anfang/Start markieren (sz) sz ez Elementare Datenstrukturen

19 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 19 Implementierung: (Version 2; mit Ringspeicher) typedef int T; class Schlange { public: Schlange(); void enq(T &x); void deq(); T front(); bool empty(); bool full(); private: static unsigned int const maxSize = 100; int ez;// Endezeiger int sz;// Startzeiger T data[maxSize]; void error(char *info); }; Elementare Datenstrukturen

20 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 20 Implementierung: (Version 2; mit Ringspeicher) Schlange::Schlange() { sz = 0; ez = -1; } T Schlange::front() { if (empty()) error("leer"); return data[sz]; } bool Schlange::empty() { return (ez == -1); } bool Schlange::full() { if (empty()) return false; return ((ez + 1) % maxSize) == sz; } Elementare Datenstrukturen

21 Kapitel 9 G. Rudolph: Einführung in die Programmierung WS 2009/10 21 Implementierung: (Version 2; mit Ringspeicher) void Schlange::enq(T &x) { if (full(s)) error(full); ez = (ez + 1) % maxSize; data[ez] = x; } void Schlange::deq() { if (empty(s)) error(leer); if (sz == ez) { sz = 0; ez = -1; } else sz = (sz + 1) % maxSize; } Laufzeit: unabhängig von Größe der Schlange Laufzeit: unabhängig von Größe der Schlange Elementare Datenstrukturen

22 Kapitel 9 Unbefriedigend bei der Implementierung: Maximale festgelegte Größe des Stapels bzw. der Schlange! Liegt an der unterliegenden Datenstruktur Array: Array ist statisch, d.h. Größe wird zur Übersetzungszeit festgelegt und ist während der Laufzeit des Programms nicht veränderbar! Schön wären dynamische Datenstrukturen, d.h. Größe wird zur Übersetzungszeit nicht festgelegt und ist während der Laufzeit des Programms veränderbar! ) Dynamischer Speicher! (Stichwort: new / delete ) Elementare Datenstrukturen

23 Kapitel 9 Wiederholung: ADT Schlange Lineare Datenstrukturen: Schlange (engl. queue) create: Schlange enq: Schlange x T Schlange deq: Schlange Schlange front: Schlange T empty: Schlange bool create: erzeugt leere Schlange enq: hängt Element ans Ende der Schlange deq: entfernt Kopf der Schlange front: gibt im Kopf der Schlange gespeichertes Element zurück empty: prüft, ob Schlange leer ist Implementierung mit statischen Speicher ersetzen durch dynamischen Speicher

24 Kapitel 9 Wiederholung: Dynamischer Speicher Bauplan: Datentyp * Variable = new Datentyp;(Erzeugen) delete Variable;(Löschen) Bauplan für Arrays: Datentyp * Variable = new Datentyp [ Anzahl ] ;(Erzeugen) delete[] Variable;(Löschen) Achtung: Dynamisch erzeugte Objekte müssen auch wieder gelöscht werden! Keine automatische Speicherbereinigung!

25 Kapitel 9 Vorüberlegungen für ADT Schlange mit dynamischen Speicher: Wir können bei der Realisierung der Schlange statt statischen (Array) nun dynamischen Speicher verwenden … Ansatz: new int[oldsize+1] … bringt uns das weiter? Größe kann zwar zur Laufzeit angegeben werden, ist aber dann fixiert! Falls maximale Größe erreicht, könnte man 1.größeres Array anlegen 2.Arraywerte ins größere Array kopieren und 3.kleineres Array löschen. ineffizient! Elementare Datenstrukturen

26 Kapitel 9 Vorüberlegungen für ADT Schlange mit dynamischen Speicher: Objekt DatenfeldZeiger 0 zeigt auf Null (nichts): Nullpointer Start Ende Schlange Kopf +Schwanz der Schlange Elementare Datenstrukturen

27 Kapitel 9 Klassendefinition: (Version 3; mit dynamischem Speicher) typedef int T; class Schlange { public: Schlange();// Konstruktor void enq(T &x); void deq(); T front(); bool empty(); void clear();// löscht alle Einträge ~Schlange();// Destruktor private: struct Objekt {// interner Datentyp Objekt *tail;// Zeiger auf Schlangenschwanz T data;// Datenfeld } *sz, *ez;// Zeiger auf Start + Ende void error(char *info);// Hilfsmethode: Fehlermeldung }; Elementare Datenstrukturen

28 Kapitel 9 Implementierung: (Version 3) Schlange::Schlange() { ez = NULL; } T Schlange::front() { if (empty()) error("leer"); return sz->data; } bool Schlange::empty() { return (ez == NULL); } void Schlange::clear() { while (!empty()) deq(); } void Schlange::error(char *info) { cerr << info << endl; exit(1); } Elementare Datenstrukturen NULL ist der Nullzeiger! Schlange::~Schlange() { clear(); }

29 Kapitel 9 Implementierung: (Version 3) void Schlange::enq(T &x) { Objekt *obj = new Objekt;// neues Objekt anlegen obj->data = x;// Nutzdaten speichern obj->tail = NULL; if (empty()) sz = obj;// falls leer nach vorne, else ez->tail = obj; // sonst hinten anhängen ez = obj;// Endezeiger aktualisieren } void Schlange::deq() { if (empty()) error("leer"); Objekt *obj = sz;// Zeiger auf Kopf retten sz = sz->tail;// Start auf 2. Element if (sz == NULL) ez = NULL;// Schlange leer! delete obj;// ehemaliges 1. Element }// löschen Elementare Datenstrukturen

30 Kapitel 9 int main() { Schlange s; if (s.empty()) cout << "Schlange leer" << endl; for (int i = 0; i < 10; i++) s.enq(i); if (!s.empty()) cout << "Schlange nicht mehr leer" << endl; cout << "vorderstes Element: " << s.front() << endl; while (!s.empty()) { cout << s.front() << " "; s.deq(); } cout << endl; if (s.empty()) cout << "Schlange jetzt leer" << endl; for (i = 0; i < 100; i++) s.enq(i); if (!s.empty()) cout << "Schlange nicht mehr leer" << endl; s.clear(); if (s.empty()) cout << "Schlange wieder leer" << endl; return 0; } Testprogramm! Elementare Datenstrukturen

31 Kapitel 9 Elementare Datenstrukturen

32 Kapitel 9 Elementare Datenstrukturen Kopieren von Klassenobjekten class Schlange { T data[100]; int sz, ez; }; Schlange s1; for (int i=0;i<10;i++) s1.enq(i); Schlange s2 = s1; statischer Speicher: byteweises Speicherabbild! ) OK! dynam. Speicher: byteweises Speicherabbild! ) Problem! class Schlange { struct Objekt { Objekt *tail; T data; } *sz, *ez; }; Schlange s1; for (int i=0;i<10;i++) s1.enq(i); Schlange s2 = s1; Es werden nur die Inhalte der Zeiger kopiert! Bei Verwendung von dynamischem Speicher muss auch dieser kopiert werden! ) In C++ kann das durch den Kopierkonstruktor realisiert werden!

33 Kapitel 9 Elementare Datenstrukturen Kopierkonstruktor (copy constructor) Wird für eine Klasse kein Kopierkonstruktur implementiert, dann erzeugt ihn der Compiler automatisch! Achtung! Es wird dann ein byteweises Speicherabbild des Objektes geliefert! ) flache Kopie (engl. shallow copy) Problem: - Konstruktor fordert dynamischen Speicher an - Konstruktor öffnet exklusive Datei (o.a. Resource) nur Kopie des Zeigers nicht teilbar! Crash! ) dann tiefe Kopie (engl. deep copy) nötig! ) man muss Kopierkonstruktor (und Destruktor) implementieren!

34 Kapitel 9 Elementare Datenstrukturen Programmiertes Unheil: #include #include "Stapel.h" // statischer Speicher #include "Schlange.h" // dynamischer Speicher using namespace std; int main() { Stapel stack1; Schlange queue1; for (int i = 0; i < 10; i++) { stack1.push(i); queue1.enq(i); } Stapel stack2 = stack1; Schlange queue2 = queue1; while (!stack1.empty()) stack1.pop(); while (!queue1.empty()) queue1.deq(); while (!stack2.empty()) { cout << stack2.top() << endl; stack2.pop(); } while (!queue2.empty()) { cout << queue2.front() << endl; queue2.deq(); } return 0; } Stapel1 / Schlange1 mit Daten belegen. Stapel1 / Schlange1 löschen kopierten Stapel ausgeben kopierte Schlange ausgeben... crash! Stapel1 / Schlange1 kopieren

35 Kapitel 9 Elementare Datenstrukturen Kopierkonstruktor (copy constructor) class Schlange { public: Schlange(); // Konstruktor Schlange(const Schlange & s); ~Schlange(); // Destruktor }; Kopierkonstruktor Schlange::Schlange(const Schlange& s){ ez = NULL; Objekt *ptr = s.sz; while (ptr != NULL) { enq(ptr->data); ptr = ptr->tail; } Kann wie eine Zuweisung interpretiert werden! Entstehendes Objekt wird mit einem bestehenden Objekt initialisiert!

36 Kapitel 9 Elementare Datenstrukturen Kopierkonstruktor (copy constructor) Bauplan: ObjektTyp ( const ObjektTyp & bezeichner) ; Kopierkonstruktor liefert / soll liefern byteweises Speicherabbild des Objektes Wird automatisch aufgerufen, wenn: 1.ein neues Objekt erzeugt und mit einem bestehenden initialisiert wird; 2.ein Objekt per Wertübergabe an eine Funktion gereicht wird; 3.ein Objekt mit return als Wert zurückgegeben wird. Punkt a(1.2, 3.4); // Neu Punkt b(a); // Kopie: direkter Aufruf des Kopierkonstruktors Punkt c = b; // Kopie: bewirkt Aufruf des Kopierkonstruktors b = a; // Zuweisung! Keine Kopie! gleiche Problematik!

37 Kapitel 9 Elementare Datenstrukturen Wenn für eine Klasse der Zuweisungsoperator nicht überschrieben wird, dann macht das der Compiler automatisch! Vorsicht! Speicher des Objektes wird byteweise überschrieben! Problem: z.B. wenn Objekt dynamischen Speicher verwendet ) gleiche Problematik wie beim Kopierkonstruktor Merke: Wenn die Implementierung eines Kopierkonstruktors nötig ist, dann höchstwahrscheinlich auch Destruktor und überschriebene Zuweisung!

38 Kapitel 9 Elementare Datenstrukturen Operator ist eine Verknüpfungsvorschrift! Kann man auffassen als Name einer Funktion: Bsp: Addition a + b interpretieren als + (a, b) in C++ als: c = operator+ (a, b) Überladen von Operatoren FunktionsnameArgumente Zweck: eine Klasse mit Funktionalität ausstatten, die vergleichbar mit elementarem Datentyp ist! Vorteil: Quellcode wird übersichtlicher insbesondere bei Zuweisung und Gleichheit

39 Kapitel 9 Elementare Datenstrukturen Überladen von Operatoren Welche? +^==+=^=!=<<() -&>-=&=&&<<=new *|>=*=|=||>>delete /~>>== %!<=%=--->*[] Wie? Objekttyp& operator (const ObjektTyp& bezeichner) Objekttyp operator (const ObjektTyp& bezeichner)

40 Kapitel 9 Elementare Datenstrukturen Überladen von Operatoren Schlange& operator= (const Schlange& s) { clear(); // Speicher freigeben Objekt *ptr = s.sz; while (ptr != NULL) { enq(ptr->data); ptr = ptr->tail; } return *this; } Zuweisung this ist ein Zeiger auf das Objekt selbst! bei der Zuweisung wird ja keine neue Instanz erzeugt; tatsächlich wird vorhandene Instanz verändert; deshalb ist Rückgabewert eine Referenz auf sich selbst!

41 Kapitel 9 Elementare Datenstrukturen Überladen von Operatoren bool operator== (const Schlange& s) { Objekt *ptr1 = sz; // this->sz Objekt *ptr2 = s.sz; while (ptr1 != NULL && ptr2 != NULL) { if (ptr1->data != ptr2->data) return false; ptr1 = ptr1->tail; ptr2 = ptr2->tail; } return (ptr1 == ptr2); } Test auf Gleichheit Zwei Schlangen sind gleich genau dann, wenn sie 1.gleich viele Elemente haben und 2.die Inhalte in gleicher Reihenfolge paarweise gleich sind.

42 Kapitel 9 Elementare Datenstrukturen Unterschied zwischen Kopierkonstruktor und Zuweisung Kopierkonstruktor: Initialisierung einer neu deklarierten Variable von existierender Variable Zuweisung: wirkt zwar wie Kopierkonstruktor (flache Kopie bzw. tiefe Kopie), überschreibt jedoch Speicher der existierenden Variable mit dem Speicher der zuweisenden, existierenden Variable zusätzlich ggf. Aufräumen: Freigabe dynamischer Speicher! außerdem: Rückgabe einer Referenz auf sich selbst

43 Kapitel 9 ADT Liste (1. Version) Liste wird nur durch einen Zeiger auf ihren Listenkopf repräsentiert Operationen: create: Liste empty: Liste bool append: T x Liste Listehängt am Ende an prepend: T x Liste Listevor Kopf einfügen clear: Liste is_elem: T x Liste boolist Element enthalten? Elementare Datenstrukturen KopfFuß

44 Kapitel 9 ADT Liste (1. Version) typedef int T; class Liste { public: Liste(); // Konstruktor Liste(const Liste& liste); // Kopierkonstruktor void append(const T& x); // hängt hinten an void prepend(const T& x); // fügt vorne ein bool empty(); // Liste leer? bool is_elem(const T& x); // ist Element x in Liste? void clear(); // Liste leeren ~Liste(); // Destruktor private: struct Objekt { // privater Datentyp T data; // Nutzdaten Objekt *next; // Zeiger auf nächstes Objekt } *sz; // Startzeiger auf Listenkopf void clear(Objekt *obj); // Hilfsmethode zum Leeren }; Elementare Datenstrukturen

45 Kapitel 9 ADT Liste (1. Version) Liste::Liste() { sz = NULL; } void Liste::clear(Objekt *obj) { if (obj == NULL) return; clear(obj->next); delete obj; } void Liste::clear() { clear(sz); sz = NULL; } Liste::~Liste() { clear(); } rekursives Löschen von hinten nach vorne Laufzeit: proportional zur Listenlänge Laufzeit: unabhängig von Listenlänge Elementare Datenstrukturen

46 Kapitel 9 ADT Liste (1. Version) bool Liste::is_elem(const T& x) { Objekt *ptr = sz; while (ptr != NULL) { if (ptr->data == x) return true; ptr = ptr->next; } return false; } void Liste::prepend(const T& x){ Objekt *obj = new Objekt; obj->data = x; obj->next = sz; sz = obj; } iterativer Durchlauf von vorne nach hinten Laufzeit: proportional zur Listenlänge Laufzeit: unabhängig von Listenlänge Elementare Datenstrukturen Laufzeit: unabhängig von Listenlänge bool Liste::empty() { return (sz == NULL); }

47 Kapitel 9 ADT Liste (1. Version) Laufzeit: proportional zur Listenlänge iterativer Durchlauf von vorne nach hinten void Liste::append(const T& x) { Objekt *obj = new Objekt; obj->data = x; obj->next = NULL; if (empty()) sz = obj; else { Objekt *ptr = sz; while (ptr->next != NULL) ptr = ptr->next; ptr->next = obj; } neuen Eintrag erzeugen Elementare Datenstrukturen Liste leer? Kopf = neuer Eintrag Liste::Liste(const Liste& liste) : sz(NULL) { for (Objekt *ptr = liste.sz; ptr != NULL; ptr = ptr->next) append(ptr->data); } Laufzeit: quadratisch proportional zur Listenlänge!

48 Kapitel 9 ADT Liste (1. Version) Zusammenfassung: 1.Laufzeit von clear proportional zur Listenlänge 2.Laufzeit des Kopierkonstruktors quadratisch proportional zur Listenlänge 3.Laufzeit von is_elem proportional zur Listenlänge 4.Laufzeit von append proportional zur Listenlänge kann nicht verbessert werden, weil ja jedes Element gelöscht werden muss unproblematisch, weil nur selten aufgerufen kann bei dieser Datenstruktur nicht verbessert werden später verbessert durch ADT BinärerSuchbaum kann durch Veränderung der Implementierung verbessert werden zusätzlicher Zeiger auf das Ende der Liste Elementare Datenstrukturen kann nur verbessert werden, wenn append verbessert werden kann bestenfalls Laufzeit proportional zur Listenlänge: muss alle Elemente kopieren!

49 Kapitel 9 ADT Liste (2. Version) class Liste { public: // keine Änderungen private: struct Objekt { T data; Objekt *next; } *sz, *ez; // sonst keine Änderungen }; Liste besteht aus 2 Zeigern: Zeiger auf Listenkopf (Start) Zeiger auf Listenfuß (Ende) Elementare Datenstrukturen KopfFuß szez Kennzeichnung der leeren Liste jetzt durch Nullzeiger bei ez. Liste::Liste() { ez = NULL; } bool Liste::empty() { return (ez == NULL); } Liste::~Liste() { clear(); }

50 Kapitel 9 ADT Liste (2. Version) void Liste::clear(Objekt *obj) { if (obj == NULL) return; clear(obj->next); delete obj; } void Liste::clear() { clear(sz); ez = NULL; } bool Liste::is_elem(const T& x) { Objekt *ptr = sz; while (ptr != NULL) { if (ptr->data == x) return true; ptr = ptr->next; } return false; } keine Änderungen! Laufzeit: proportional zur Listenlänge keine Verbesserung (OK) Elementare Datenstrukturen keine Änderungen! Laufzeit: proportional zur Listenlänge keine Verbesserung (OK)

51 Kapitel 9 ADT Liste (2. Version) void Liste::append(const T& x) { Objekt *obj = new Objekt; obj->data = x; obj->next = NULL; if (empty()) sz = obj; else ez->next = obj; ez = obj; } Laufzeit: unabhängig von Listenlänge Elementare Datenstrukturen void Liste::prepend(const T& x){ Objekt *obj = new Objekt; obj->data = x; obj->next = sz; sz = obj; } keine Änderungen! Laufzeit: unabhängig von Listenlänge Verbesserung!

52 Kapitel 9 Version 1 ADT Liste (2. Version) Liste::Liste(const Liste& liste) { ez = NULL; for (Objekt *ptr = liste.sz; ptr != NULL; ptr = ptr->next) append(ptr->data); } Laufzeit: proportional zur Listenlänge, weil append verbessert wurde Verbesserung! Elementare Datenstrukturen ElementeDebugReleaseDebugRelease Laufzeit in sek. für Kopieroperation Version 2 Anzahl Elemente mal 4 ) Laufzeit mal 4 2 =16 (Version 1) Laufzeit mal 4 (Version 2)

53 Kapitel 9 ADT Liste (2. Version) Zusammenfassung: 1.Laufzeit von clear proportional zur Listenlänge 2.Laufzeit von is_elem proportional zur Listenlänge 3.Laufzeit von append unabhängig von Listenlänge 4.Laufzeit des Kopierkonstruktors proportional zur Listenlänge kann nicht verbessert werden, weil ja jedes Element gelöscht werden muss unproblematisch, weil nur selten aufgerufen kann bei dieser Datenstruktur nicht verbessert werden verbessern wir gleich durch ADT BinärBaum war proportional zur Listenlänge in 1. Version Verbesserung erzielt durch Veränderung der Implementierung Elementare Datenstrukturen war quadratisch proportional zur Listenlänge in 1. Version Verbesserung erzielt durch Verbesserung von append

54 Kapitel 9 ADT Binärer Suchbaum Vorbemerkungen: Zahlenfolge (z. B. 17, 4, 36, 2, 8, 19, 40, 6, 7, 37) soll gespeichert werden, um später darin suchen zu können Man könnte sich eine Menge A vorstellen mit Anfrage: Ist 40 A ? Mögliche Lösung: Zahlen in einer Liste speichern und nach 40 suchen … … aber: nicht effizient, weil im schlechtesten Fall alle Elemente überprüft werden müssen! Bessere Lösungen? Elementare Datenstrukturen

55 Kapitel 9 Beispiel: Zahlenfolge 17, 4, 36, 2, 8, 19, 40, 6, 7, kleiner: nach links größer: nach rechts z.B. Suche, ob 42 enthalten benötigt nur 3 Vergleiche bis zur Entscheidung false Elementare Datenstrukturen ADT Binärer Suchbaum

56 Kapitel 9 ADT Binärer Suchbaum: Terminologie LR linker Unterbaum rechter Unterbaum Wurzel Blätter keine Wurzel und kein Blatt innerer Knoten Elementare Datenstrukturen

57 Kapitel 9 ADT Binärer Suchbaum: Klassendefinition typedef int T; class BinTree { private: struct Node { T data; Node *left, *right; } *root; Node *insert(Node *node, T key); bool isElem(Node *node, T key); void clear(Node *node); public: BinTree() { root = 0; } void insert(T x) { root = insert(root, x); } bool isElem(T x) { return isElem(root, x); } void clear() { clear(root); root = 0; } ~BinTree() { clear(); } }; Elementare Datenstrukturen leerer Unterbaum Nullzeiger

58 Kapitel 9 ADT Binärer Suchbaum: Element suchen bool BinTree::isElem(Node *node, T key) { if (node == 0) return false; if (node->data == key) return true; if (node->data right, key); return isElem(node->left, key); } Elementare Datenstrukturen Rekursive Suche: Falls kein Erfolg im aktuellen Knoten, dann Frage an den Unterbaum weiterreichen, der das Element enthalten müsste. Falls Knoten Element enthält: Erfolg! Falls Unterbaum leer, dann Element nicht vorhanden. Rekursionsverankerung (Abbruchbedingung)

59 Kapitel 9 ADT Binärer Suchbaum: Aufräumen void BinTree::clear(Node *node) { if (node == 0) return;// Rekursionsabbruch clear(node->left);// linken Unterbaum löschen clear(node->right);// rechten Unterbaum löschen delete node;// Knoten löschen } u.s.w Elementare Datenstrukturen

60 Kapitel 9 ADT Binärer Suchbaum: Einfügen BinTree::Node *BinTree::insert(Node *node, T key) { if (node == 0) { node = new Node; node->data = key; node->left = node->right = 0; return node; } if (node->data < key) node->right = insert(node->right, key); else if (node->data > key) node->left = insert(node->left, key); return node; } Elementare Datenstrukturen Rekursives Einfügen

61 Kapitel 9 ADT Binärer Suchbaum Höhe := Länge des längsten Pfades von der Wurzel zu einem Blatt. Höhe(leerer Baum) = 0 Höhe(nicht leerer Baum) = 1 + max { Höhe(linker U-Baum), Höhe(rechter U-Baum) } (U-Baum = Unterbaum) Anmerkung: rekursive Definition! Elementare Datenstrukturen

62 Kapitel 9 ADT Binärer Suchbaum Auf Ebene k können jeweils zwischen 1 und 2 k-1 Elemente gespeichert werden. In einem Baum der Höhe h können also zwischen h und Elemente gespeichert werden! Elementare Datenstrukturen

63 Kapitel 9 ADT Binärer Suchbaum Ein vollständiger Baum der Höhe h besitzt 2 h – 1 Knoten. Man braucht maximal h Vergleiche, um Element (ggf. nicht) zu finden. Bei n = 2 h – 1 Elementen braucht man log 2 (n) < h Vergleiche! Ein degenerierter Baum der Höhe h besitzt h Knoten (= lineare Liste). Man braucht maximal h Vergleiche, um Element (ggf. nicht) zu finden. Bei n = h braucht man also n Vergleiche! Elementare Datenstrukturen

64 Kapitel 9 Exkurs: Einfache Dateibehandlung Datei := speichert Daten in linearer Anordnung Zwei Typen: ASCII-Dateien - sind mit Editor les- und schreibbar - Dateiendung (suffix oder extension) meist.txt oder.asc - betriebssystem-spezifische Übersetzung von Zeichen bei Datentransfer zwischen Programm und externem Speicher Binär-Dateien - werden byteweise beschrieben und gelesen - lesen / schreiben mit Editor ist keine gute Idee - schnellerer Datentransfer, da keine Zeichenübersetzung

65 Kapitel 9 Hier: einfache Dateibehandlung! Dateien können gelesen oder beschrieben werden. Vor dem ersten Lesen oder Schreiben muss Datei geöffnet werden. Man kann prüfen, ob das Öffnen funktioniert hat. Nach dem letzten Lesen oder Schreiben muss Datei geschlossen werden. Bei zu lesenden Dateien kann gefragt werden, ob Ende der Datei erreicht ist. Beim Öffnen einer zu schreibenden Datei wird vorheriger Inhalt gelöscht! Man kann noch viel mehr machen … wir benötigen: #include bzw. Exkurs: Einfache Dateibehandlung

66 Kapitel 9 Eingabe-Datei = input file ifstream Quelldatei; DatentypBezeichner Ausgabe-Datei = output file ofstream Zieldatei; DatentypBezeichner Öffnen der Datei: Quelldatei.open(dateiName); ist Kurzform von Quelldatei.open(dateiName, modus); wobei fehlender modus bedeutet: ASCII-Datei, Eingabedatei (weil ifstream) Öffnen der Datei: Zieldatei.open(dateiName); ist Kurzform von Quelldatei.open(dateiName, modus); wobei fehlender modus bedeutet: ASCII-Datei, Ausgabedatei (weil ofstream) Exkurs: Einfache Dateibehandlung

67 Kapitel 9 modus: ios::binary binäre Datei ios::in öffnet für Eingabe (implizit bei ifstream) ios::out öffnet für Ausgabe (implizit bei ofstream) ios::app hängt Daten am Dateiende an ios::nocreate wenn Datei existiert, dann nicht anlegen Warnung: teilweise Compiler-abhängig ( nocreate fehlt in MS VS 2003, dafür trunc ) Man kann diese Schalter / Flags miteinander kombinieren via: ios::binary | ios::app (öffnet als binäre Datei und hängt Daten an) Exkurs: Einfache Dateibehandlung

68 Kapitel 9 Datei öffnen file.open(fileName) bzw. file.open(fileName, modus) falls Öffnen fehlschlägt, wird Nullpointer zurückgegeben Datei schließen file.close() sorgt für definierten Zustand der Datei auf Dateisystem; bei nicht geschlossenen Dateien droht Datenverlust! Ende erreicht? ja falls file.eof() == true Lesen (von ifstream) file.get(c); liest ein Zeichen file >> x; liest verschiedene Typen Schreiben (von ofstream) file.put(c); schreibt ein Zeichen file << x; schreibt verschiedene Typen Exkurs: Einfache Dateibehandlung

69 Kapitel 9 Merke: 1.Auf eine geöffnete Datei darf immer nur einer zugreifen. 2.Eine geöffnete Datei belegt Ressourcen des Betriebssystems. Deshalb Datei nicht länger als nötig geöffnet halten. 3.Eine geöffnete Datei unbekannter Länge kann solange gelesen werden, bis das Ende-Bit (end of file, EOF) gesetzt wird. 4.Der Versuch, eine nicht vorhandene Datei zu öffnen (zum Lesen) oder eine schreibgeschützte Datei zu öffnen (zum Schreiben), führt zu einem Nullpointer. Das muss überprüft werden, sonst Absturz bei weiterer Verwendung! 5.Dateieingabe und -ausgabe (input/output, I/O) ist sehr langsam im Vergleich zu den Rechenoperationen. I/O Operationen minimieren. The fastest I/O is no I/O. Nils-Peter Nelson, Bell Labs Exkurs: Einfache Dateibehandlung

70 Kapitel 9 #include using namespace std; int main() {// zeichenweise kopieren ifstream Quelldatei; ofstream Zieldatei; Quelldatei.open("quelle.txt"); if (!Quelldatei.is_open()) { cerr << "konnte Datei nicht zum Lesen öffnen\n"; exit(1); } Zieldatei.open("ziel.txt"); if (!Zieldatei.is_open()) { cerr << "konnte Datei nicht zum Schreiben öffnen\n"; exit(1); } Exkurs: Einfache Dateibehandlung

71 Kapitel 9 while (!Quelldatei.eof()) { char c; Quelldatei.get(c); Zieldatei.put(c); } Quelldatei.close(); Zieldatei.close(); } offene Datei Startaktuelle Position eof() == true Exkurs: Einfache Dateibehandlung

72 Kapitel 9 Exkurs: C++ Strings Bisher: Zeichenketten wie char str[20]; Relikt aus C-Programmierung! bei größeren Programmen mühevoll, lästig, … … und insgesamt fehlerträchtig! Jetzt: Zeichenketten aus C++ sehr angenehm zu verwenden (keine 0 am Ende, variable Größe, …) eingebaute (umfangreiche) Funktionalität wie benötigen: #include und using namespace std;

73 Kapitel 9 string s1;// leerer String string s2 = "xyz";// initialisieren mit C-String string s3 = s2;// vollständige Kopie! string s4("abc");// initialisieren mit C-String string s5(s4);// initialisieren mit C++-String string s6(10, *);// ergibt String aus 10 mal * string s7(1x);// initialisieren mit einem char string sx(x);// FEHLER! string s8("");// leerer String Datendefinition / Initialisierung Exkurs: C++ Strings

74 Kapitel 9 const char *Cstr = s2.c_str(); Eingebaute Funktionen Konvertierung C++-String nach C-String via c_str() cout << s2.length(); Stringlänge length() substr(), replace(), erase(), … Index von Teilstring finden int pos = s2.find(yz); Strings addieren s1 = s2 + s3; s4 = s2 + hello; s5 += s4; Strings vergleichen if (s1 == s2) s3 += s2; if (s3 < s8) flag = true; Exkurs: C++ Strings

75 Kapitel 9 ADT Binäre Bäume: Anwendung Aufgabe: Gegeben sei eine Textdatei. Häufigkeiten der vorkommenden Worte feststellen. Alphabetisch sortiert ausgeben. Strategische Überlegungen: Lesen aus Textdatei ifstream benutzen! Sortierte Ausgabe Binärbaum: schnelles Einfügen, sortiert von selbst Worte vergleichen C++ Strings verwenden! Programmskizze: Jeweils ein Wort aus Datei lesen und in Binärbaum eintragen. Falls Wort schon vorhanden, dann Zähler erhöhen. Wenn alle Wörter eingetragen, Ausgabe (Wort, Anzahl) via Inorder-Durchlauf. Elementare Datenstrukturen

76 Kapitel 9 gelesenes Wort wie oft gelesen? Elementare Datenstrukturen struct Node { std::string data; unsigned int cnt; BinBaum *left, *right; }; BinTree::BinTree(string &filename) { ifstream source; source.open(filename.c_str()); if (!source.is_open()) return 0; string s; while (!source.eof()) { source >> s; insert(s); } source.close(); } Datei öffnen Worte einzeln auslesen und im Baum einfügen Datei schließen zusätzlicher Konstruktor (zum Einlesen der Datei) zusätzlicher Zähler im Knoten

77 Kapitel 9 BinTree::Node *BinTree::insert(Node *node, T key) { if (node == 0) { node = new Node; node->data = key; node->cnt = 1; node->left = node->right = 0; return node; } if (node->data < key) node->right = insert(node->right, key); else if (node->data > key) node->left = insert(node->left, key); else node->cnt++; return node; } Elementare Datenstrukturen Einfügen (Änderungen in rot)

78 Kapitel 9 void BinTree::print(Node *node) { if (node == 0) return; print(node->left); cout cnt data.c_str() << endl; print(node->right); } Dies ist die Inorder-Ausgabe. Präorder: cout …; Ausgabe(…); Ausgabe(…); Postorder: Ausgabe(…); Ausgabe(…); cout …; Elementare Datenstrukturen Ausgabe (rekursiv)

79 Kapitel 9 #include "BinTree.h" using namespace std; int main() { string s("quelle.txt"); BinBaum b(s); b.print(); return 0; } Hauptprogramm: Elementare Datenstrukturen

80 Kapitel 9 Durchlaufstrategien: Tiefensuche (depth-first search, DFS) - Präorder Vor (prä) Abstieg in Unterbäume die Knotenbehandlung durchführen - Postorder Nach (post) Abstieg in bzw. Rückkehr aus Unterbäumen die Knotenbehandlung durchführen - Inorder Zwischen zwei Abstiegen Knotenbehandlung durchführen z.B. Ausdruck des Knotenwertes Breitensuche (breadth-first search, BFS; auch: level search) auf jeder Ebene des Baumes werden Knoten abgearbeitet, bevor in die Tiefe gegangen wird Elementare Datenstrukturen

81 Kapitel 9 Breitensuche Beispiel: eingegebene Zahlenfolge 17, 4, 36, 2, 8, 40, 19, 6, 7, Ausgabe: 17, 4, 36, 2, 8, 19, 40, 6, 37, 7 Elementare Datenstrukturen

82 Kapitel 9 ADT Graph Verallgemeinerung von (binären) Bäumen Wichtige Struktur in der Informatik Zahlreiche Anwendungsmöglichkeiten - Modellierung von Telefonnetzen, Versorgungsnetzwerken, Straßenverkehr, … - Layout-Fragen bei elektrischen Schaltungen - Darstellung sozialer Beziehungen - etc. Viele Probleme lassen sich als Graphenprobleme verkleiden und dann mit Graphalgorithmen lösen! Elementare Datenstrukturen

83 Kapitel 9 Definition EIn Graph G = (V, E) besteht aus einer Menge von Knoten V (vertex, pl. vertices) und einer Menge von Kanten E (edge, pl. edges) mit E V x V Eine Kante (u, v) heißt Schlinge (loop), wenn u = v. Der Grad (degree) eines Knotens v V ist die Anzahl der zu ihm inzidenten Kanten: deg(v) = | { (a, b) E : a = v oder b = v } |. Maxgrad von G ist (G) = max { deg(v) : v V } Mingrad von G ist (G) = min { deg(v) : v V } (G) = 6 (G)= 3 Schlinge Elementare Datenstrukturen

84 Kapitel 9 Definition Für v i V heißt (v 0, v 1, v 2, …, v k ) ein Weg oder Pfad in G, wenn (v i,v i+1 ) E für alle i = 0, 1, …, k-1. Die Länge eines Pfades ist die Anzahl seiner Kanten. Ein Pfad (v 0, v 1, v 2, …, v k ) mit v 0 = v k wird Kreis genannt. Distanz dist(u, v) von zwei Knoten ist die Länge des kürzesten Pfades von u nach v. Durchmesser diam(G) eines Graphes G ist das Maximum über alle Distanzen: diam(G) = max { dist(u, v) : (u, v) V x V }. Graph ist zusammenhängend, wenn 8 u, v V mit u v einen Pfad gibt. G heißt Baum gdw. G zusammenhängend und kreisfrei. Elementare Datenstrukturen

85 Kapitel 9 Darstellung im Computer Adjazenzmatrix A mit a ij = 1 falls (v i,v j ) E 0 sonst Problem: Da | E | | V | 2 = n 2 ist Datenstruktur ineffizient (viele Nullen) wenn | E | verschwindend klein. Adjazenzlisten: Für jeden Knoten v eine (Nachbarschafts-) Liste L(v) mit L(v) = { u V : (v, u) E } Elementare Datenstrukturen

86 Kapitel 9 Beispiel e d a b c L(a) = ( b, e ) L(b) = ( a, c, d ) L(c) = ( b, d ) L(d) = ( b, c, e ) L(e) = ( a, d ) Adjazenzlisten abcde a01001 b10110 c01010 d01101 e10010 Adjazenzmatrix ADT Liste Array[][] Elementare Datenstrukturen

87 Kapitel 9 class Graph { public: Graph(uint NoOfNodes); void addEdge(uint Node1, uint Node2); bool hasEdge(uint Node1, uint Node2); uint noOfEdges(); uint noOfNodes(); void printGraph(); ~Graph(); private: uint mNoOfNodes; Liste *mAdjList; }; Mögliche Funktionalität typedef unsigned int uint;typedef Datentyp TypName; Elementare Datenstrukturen mAdjList : Array von Zeigern auf Liste Liste

88 Kapitel 9 #include #include "Graph.h" using namespace std; Graph::Graph(uint NoOfNodes) { mNoOfNodes = NoOfNodes; if (mNoOfNodes > 0) mAdjList = new Liste[mNoOfNodes]; } Graph::~Graph() { if (mNoOfNodes > 0) delete[] mAdjList; } void Graph::printGraph() { for (uint i = 0; i < mNoOfNodes; i++) { cout << i << " : "; mAdjList[i].print(); } mAdjList : Array von Zeigern auf Liste Liste Elementare Datenstrukturen

89 Kapitel 9 void Graph::addEdge(uint Node1, uint Node2) { if (!hasEdge(Node1, Node2)) { mAdjList[Node1].append(Node2); mAdjList[Node2].append(Node1); } bool Graph::hasEdge(uint Node1, uint Node2) { if (mNoOfNodes < 1) return false; return mAdjList[Node1].is_elem(Node2); } uint Graph::noOfEdges() { uint cnt = 0; for (uint i = 0; i < mNoOfNodes; i++) cnt += mAdjList[i].size(); return cnt / 2; } uint Graph::noOfNodes() { return mNoOfNodes; } Ineffizient! Falls häufig benutzt, dann besser Zähler mNoOfEdges in class Graph Elementare Datenstrukturen Ineffizient! Speicherung redundanter Information!

90 Kapitel 9 #include #include "Graph.h" using namespace std; int main() { Graph g(10); uint n = g.noOfNodes(); cout << "Knoten: " << n << endl; cout << "Kanten: " << g.noOfEdges() << endl; for (uint i = 0; i < n; i++) g.addEdge(i, (i+1) % n); for (uint i = 0; i < n; i++) g.addEdge(i, (i+2) % n); g.addEdge(5,0); if (g.hasEdge(0,5)) cout << "Kante (0,5) existiert" << endl; g.printGraph(); cout << "Kanten: " << g.noOfEdges() << endl; return 0; } Elementare Datenstrukturen Test

91 Kapitel 9 Elementare Datenstrukturen Verbesserungsmöglichkeiten Überprüfung, ob Knotennummer zulässig (< Anzahl Knoten) ! Zähler mNoOfEdges wird erhöht, wenn neue Kante eingefügt wird Kanten sind bidirektional nur einmal speichern! erfordert Anpassung in einigen Methoden! Funktionalität erweitern: Hinzufügen Knoten; Löschen Knoten / Kanten, etc. void Graph::addEdge(uint Node1, uint Node2) { if (Node1 > Node2) Swap(&Node1, &Node2); if (!hasEdge(Node1, Node2)) mAdjList[Node1].append(Node2); } bool Graph::hasEdge(uint Node1, uint Node2) { if (mNoOfNodes Node2) Swap(&Node1, &Node2); return mAdjList[Node1].is_elem(Node2); } Idee: Normierung, so dass kleinere Knotennummer zuerst


Herunterladen ppt "Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund."

Ähnliche Präsentationen


Google-Anzeigen