Teil VIII Programmierung mit C++ (4) Objektorientierung Überblick 1 Einführung Objektorientierung 2 Objekte und Klassen in C++ 3 Vererbung in C++ 4 Strukturen in C und C++ Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 385/719 Einführung Objektorientierung Definition (Objektorientierung) Objektorientierung ist ein Ansatz aus der Softwareentwicklung, der die Zusammenfassung von Daten und den dazugehörigen Funktionen in Klassen von Realweltobjekten unterstützt. Grundlegender Gedanke: weiteres Mittel zu Strukturierung des Programmtextes für komplexe Anwendungen Bisher: Funktionen fassen zusammenhängende Befehlsfolgen für Lösung eines Teilproblems zusammen Jetzt: Strukturierung der Daten (Variablen etc.) und der dazugehörigen Funktionen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 386/719 Ziele der Objektorientierung Wiederverwendbarkeit → verminderter Programmieraufwand Nutzung vordefinierter Klassen aus Bibliotheken Definition eigener Klassen zur Verwendung in vielen Programmteilen Spezialisierung von Klassen: Erweiterung vordefinierter Klassen um neue Attribute und Funktionen Bessere Verständlichkeit und Wartbarkeit des Codes Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 387/719 OO Grundkonzepte: Klassen und Instanzen Klassen fassen Objekte mit gleichen Eigenschaften (Attributen) und gleichen auf sie anwendbaren Funktionen (Methoden) zusammen Gesamtheit von Attributen und Methoden entspricht dem Typ → Klassen beinhalten somit eine anwendungsspezifische und vom Programmierer entwickelte Typdefinition Objekte werden auch als Instanzen (Ausprägung, Einheit) der Klasse gespeichert Entstehen durch Erzeugung von Objekten des Klassentyps → Instantiierung Tragen die Werte für die in der Klasse definierten Attribute Grundlage für Aufruf der in der Klasse definierten Methoden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 388/719 OO Grundkonzepte: Instanzen und Klassen Instanzen der Klasse Mitarbeiter Schmidt Claudia 70000 Finanzen Klasse Mitarbeiter Eigenschaften Name Vorname Gehalt Abteilung Schulz Peter 50000 Entwicklung Funktionen Einstellen Gehalt Erhöhen Abteilung Wechseln Entlassen Eike Schallehn, FIN/ITI Meier Gustav 80000 Marketing Grundlagen der Informatik für Ingenieure 389/719 OO Grundkonzepte: Kapselung Kapselung als Programmierkonzept unterscheidet die nach außen sichtbaren Attribute und Methoden (Schnittstelle der Klasse) von internen Eigenschaften und Methoden, die vor dem Nutzer der Klasse verborgen bleiben sollen (Implementierung der Klasse) Beispiel: Dateizugriff über fstream-Klassen Öffentlich zugänglich: Dateiname, Zugriffsmodus sowie Methoden zum Öffnen, Schließen, Lesen, Schreiben etc. Gekapselt: Methoden und Daten zum Zugriff auf Dateipuffer etc. Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 390/719 OO Grundkonzepte: Vererbung Vererbung oder auch (allgemeiner) Spezialisierung ist eine Beziehung zwischen Klassen und Typen, welche dadurch in einer Vererbungshierarchie stehen Ist eine Klasse eine Unterklasse einer Oberklasse, dann „Erbt“ sie die Attribute und Methoden der Oberklasse, d.h. diese sind ohne weiteren Programmieraufwand auch hier definiert Kann jedes Objekt der Unterklasse auch als Objekt der Oberklasse behandelt werden (→ Polymorphie) Beispiel: Objekte der Unterklasse Studenten und der Oberklasse Personen Jeder Student ist eine Person Jeder Student hat auch die Eigenschaften und Methoden einer Person im Allgemeinen Alles, was eine Person machen kann, kann auch ein Student machen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 391/719 OO Grundkonzepte: Wiederverwendbarkeit Objektorientierung verringert bei geeignetem Einsatz den Entwicklungsaufwand durch Wiederverwendung Wiederverwendbarkeit durch Verwendung von Klassen als nutzerdefinierte Datentypen, die überall im Programm durch die Instantiierung von Objektvariablen verwendet werden können Ableitung eigener Klassen durch Spezialisierung von vorher implementierten Klassen oder Klassen einer vordefinierten Klassenbibliothek → Erweiterung um im neuen Anwendungskontext notwendige zusätliche Attribute oder Methoden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 392/719 Anwendung der Objektorientierung Anwendung in vielen Bereichen der Informatik Objektorientierter Entwurf von Software mit Hilfe formaler Modelle oder grafischer Notationen (zum Beispiel UML) Zahlreiche objektorientierte Programmiersprachen Objektorientierte Datenbanken Objektorientierte Nutzerschnittstellen ... Im Folgenden: Objektorientierung in der Programmiersprache C++ Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 393/719 Objekte und Klassen in C++ Arbeiten mit Objektorientierung in C++ erfordert Definition von Klassen Implementierung von Konstruktoren zum Erzeugen von Objekten der Klasse Ggf. Implementierung von Destruktoren zum „Bereinigen“ der Daten von Objekten, die beim expliziten oder impliziten Löschen der Objekte aufgerufen werden Implementierung der Klassenmethoden Ggf. Implementierung von klassenspezifischen Operatoren Instantiierung der Klassen, d.h. Erzeugung von Objekten, durch Aufruf von Konstruktoren Als lokale Objektvariablen, z.B. auf dem Programm-Stack Als Objektvariablen auf dem Heap über new Arbeit mit Objekten durch Zugriff auf deren Attribute und Methoden Umsetzung der Vererbung zwischen Klassen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 394/719 Objekte und Klassen in C++ Definition von Klassen durch Schlüsselwort class - im einfachsten Fall: class Klassenname { ... }; Bestandteil der Klasse können sein: Attributdeklarationen: Klassenvariablen vergleichbar Komponentenvariablen bei Strukturen Konstruktoren und Destruktoren Methodendeklarationen/-definitionen (Klassenfunktionen) Operatordeklarationen/-definition Zusätzlich: Kapselung durch Angabe ob Bestandteile public: überall, private: (Default, bei fehlender Angabe) nur in dieser Klasse oder protected: auch in abgeleiteten Klassen sichtbar sind, d.h. im Code darauf zugegriffen werden kann Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 395/719 Kapselung in C++ class Klassenname { private: // Alle nur in dieser Klasse sichtbaren Attribute, // Konstruktoren, Methoden, Operatoren kommen // hier hin. public: // Alle überall sichtbaren Attribute, // Konstruktoren, Methoden, Operatoren. // Destruktoren müssen hier stehen. protected: // Alle in dieser und abgeleiteten Klassen sichtbaren // Attribute, Konstruktoren, Methoden, Operatoren. }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 396/719 Beispiel: Vektor-Klasse Im Folgenden durchgehend verwendetes Beispiel: Klasse zur Arbeit mit mathematischen Vektoren beliebiger Dimensionalität Attribute: Dimensionalität des Vektor Array zur Speicherung der Komponenten Methoden: Norm (Betrag, Länge) des Vektors Ausgabe des Vektors als virtuelle Funktion (kann in abgeleiteten Klassen überschrieben werden → Vererbung) Operatoren: Addition zweier Vektoren mit + Multiplikation mit einem skalaren Wert mit * Außerdem zwei Konstruktoren und ein Destruktor Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 397/719 Beispiel: Klassendeklaration class Vektor { protected: int dimensionen; float* komponenten; public : Vektor(const Vektor& v); Vektor(int d, float k[]); ∼Vektor(); virtual void ausgabe(); float norm(); Vektor operator +(Vektor v); Vektor operator *(float skalar); }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 398/719 Anmerkung: Virtuelle Funktionen Virtuelle Funktionen (korrekter eigentlich: Virtuelle Methoden) erlauben ein späteres Überschreiben der Implementierung in abgeleiteten Klassen (→ Vererbung) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 399/719 Implementierung von Methoden in C++ Zwei Alternativen für Implementierung von Methoden, Operatoren, Konstruktoren und Destruktoren Direkt innerhalb der Klassendefinition float norm() { float sum=0; for (int i=0; i<dimensionen; i++) sum+=komponenten[i] * komponenten[i]; return sqrt(sum); }; Außerhalb der Klassendefinition (empfohlen wegen Übersichtlichkeit) durch Angabe des Klassennamens als Scope float Vektor::norm() { float sum=0; for (int i=0; i<dimensionen; i++) sum+=komponenten[i] * komponenten[i]; return sqrt(sum); } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 400/719 Konstruktoren in C++ Konstruktoren sind spezielle Methoden, die beim Erzeugen von Objekten aufgerufen werden Name der Methode entspricht Name der Klasse Haben in der Signatur keinen Rückgabewert Parameter können zur Initialisierung der Attribute verwendet werden Verschiedene Konstruktoren mit unterschiedlichen Parametern möglich Zwei spezielle Arten von Konstruktoren werden automatisch generiert, können aber vom Entwickler überschrieben werden Default-Konstruktor ohne Parameter, wenn nicht überschrieben zufällige Werte bzw. Aufruf Default-Konstruktoren der eingebetteten Objekte Copy-Konstruktor erzeugt eine Kopie eines existierenden Objektes diesen Typs Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 401/719 Beispiel: Konstruktoren Vektor::Vektor(int d, float k[]) { dimensionen = d; komponenten = new float[d](); for (int i=0; i<d; i++) komponenten[i] = k[i]; } Vektor::Vektor(const Vektor& v) { dimensionen = v.dimensionen; komponenten = new float[dimensionen](); for (int i=0; i<dimensionen; i++) komponenten[i] = v.komponenten[i]; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 402/719 Copy-Konstruktoren in C++ Falls nicht angegeben, existiert immer vordefinierter Copy-Konstruktor, der einfach alle Daten (inklusive Zeiger) eines Objektes direkt übernimmt Copy-Konstruktor wird an zahlreichen Stellen automatisch und für den Nutzer nicht sichtbar aufgerufen Beim Aufruf von Funktionen mit Objekt als Pass By Value Bei der Rückgabe eines Objektes als Ergebnis einer Funktion Bei der direkten Zuweisung einer Objektvariablen mit = Überschreiben des Copy-Konstruktors oft sinnvoll Wenn im Objekt Zeiger auf weitere Daten existieren, damit diese Daten, und nicht nur der Zeiger kopiert werden Signatur ist festgelegt Klassenname(const Klassenname& o) { ... }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 403/719 Instantiierung von Objekten Erzeugung von Objekten auf dem Stack Vektor v1(3,daten); Vektor v2; // Aufruf des Default-Konstruktors Vektor v3 = v1; // Aufruf des Copy-Konstruktors → werden beim Verlassen des aktuellen Scopes automatisch gelöscht (inklusive Aufruf des Destruktors) Erzeugung von Objekten auf dem Heap Vektor* v4 = new Vektor(3,daten); Vektor* v5 = new Vektor(); // Default-Konstruktor Vektor* v6 = new Vektor(*v4); // Copy-Konstruktor → müssen, wenn nicht mehr benötigt, explizit durch delete gelöscht werden, z.B. delete v4; (ruft ebenfalls automatisch Destruktor auf) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 404/719 Destruktoren in C++ Spezielle Methode, die automatisch beim Löschen des Objektes aufgerufen wird Beim expliziten Löschen von Objekten auf dem Heap durch delete Für Objekte auf dem Stack veim Verlassen der aktuellen Funktion Deklaration immer als Methode der Form ∼Klassenname(); ohne Parameter Automatisch generierter Default-Konstruktor löscht nur Speicher des Objektes selber Überschreiben sinnvoll, um weiteren, indirekt vom Objekt genutzten und durch Zeiger referenzierten Speicher freizugeben, bzw. um sonstige „Aufräumarbeiten zur erledigen (z.B. Schließen oder Löschen von Dateien, Schließen von Fenstern, etc.) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 405/719 Beispiel: Destruktoren Vektor::∼Vektor() { delete[] komponenten; } Löscht mit Vektor assoziiertes Array auf dem Heap → bliebe sonst als nicht mehr nutzbarer Speicherbereich (Memory Leak) belegt Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 406/719 Überladen von Operatoren in C++ C++ erlaubt für Klassen die Definition neuer Operatoren sowie das Überschreiben zahlreicher existierender Operatoren, d.h. die Zuweisung einer neuen Bedeutung dieser Operatoren durch eine anzugebende Implementierung Überschreiben zum Beispiel für arithmetische Operatoren (+, -, *, /, ++, etc.), Zuweisungsoperatoren (=, += etc.), Vergleichsoperatoren (==, >, <, etc.) und andere Deklaration in der Klasse mit operator und entsprechende Implementierung Im Beispiel der Klasse Vektor Vektoraddition + durch komponentenweise Addition Skalarmultiplikation * mit einem float-Wert Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 407/719 Beispiel: Überladen von Operatoren In der Klassendeklaration: Vektor operator +(Vektor v); Implementierung Vektor Vektor::operator+ (Vektor v) { Vektor result(this->dimensionen, this->komponenten); for (int i=0; i<dimensionen; i++) result.komponenten[i] += v.komponenten[i]; return result; } Anwendung, z.B. Vektor v2 = v1 + v1; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 408/719 Der Zeiger this Vordefinierte Konstante in jeder C++-Klasse Der this-Zeiger verweist für jede aufgerufene Methode auf das Objekt (Adresse des Objektes), für das die Methode aufgerufen wurde Zum Beispiel notwendig ... zur Vermeidung von Namenskonflikten, wenn Parameter dieselben Namen wie Attribute der Klasse haben oder das Objekt selber als Rückgabewert der Methode zurückgeliefert werden soll Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 409/719 Beispiel: main-Funktion ... int main() { float daten[]={1,2,3}; Vektor v1(3,daten); cout << ”Die Laenge des Vektors v1 ”; v1.ausgabe(); cout << ” ist ” << v1.norm() << ” und v1 + v1 ist ” ; Vektor v2 = v1 + v1; v2.ausgabe(); cout << endl; return 0; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 410/719 Zerlegung des Quelltextes in C++ Konvention für komplexere Programme mit vielen Klassen: Jede Klassendeklaration in eine eigene Header-Datei (klassenname.h) Implementierung von Methoden, Konstruktore etc. dieser Klasse ebenfalls in eine eigene Quelldatei (klassenname.cpp) Hauptprogramm ebenfalls in separate Quelldatei (.cpp) Vorteile: Können vom Compiler separat und auch nur wenn notwendig, d.h. wenn genau diese Klasse verändert wurde, übersetzt werden Linker verbindet am Ende einzelne Module mit Hauptprogramm Überall wo eine Klasse genutzt wird, muss nur deren Header-Datei inkludiert werden Kürzere und übersichtlichere Dateien Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 411/719 Beispiel: Zerlegung des Quelltextes in C++ // File vektor.h #ifndef VEKTOR_H #define VEKTOR_H 1 class Vektor { ... }; #endif Verwendung der Präprozessoranweisungen verhindert mehrfaches indirektes Einbetten derselben Deklaration durch den Präprozessor Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 412/719 Vererbung in C++ C++ erlaubt Klassenhierarchien durch Spezialisierung Wichtigste Effekte: Vererbung (Inheritance): alle Attribute und Methoden der Oberklasse werden übernommen Spezialisierung: in der Unterklasse müssen nur zusätzliche, „speziellere“ Attribute und Methoden neu definiert werden Polymorphie: überall, wo ein Objekt der Oberklasse verwendet werden kann, kann auch ein Objekt der Unterklasse verwendet werden - uneingeschränkt nur bei Public Inheritance (s.u.) Überschreiben von Methoden (Overriding): in der Unterklasse kann eine speziellere Implementierung für eine Bereits in der Oberklasse implementierte Methode angegeben werden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 413/719 Vererbung in C++ /2 class Unterklasse : [public|private|proted] Oberklasse {...}; Typ der Vererbung (public,private,proted) bestimmt, mit welcher Kapselung die public- und protected-Attribute und -Methoden der Oberklasse übernommen werden → meistens Public Inheritance sinnvoll → Default-Verhalten, d.h. wenn nicht angegeben, ist aber private! Auch Mehrfachvererbung durch kommaseparierte Liste von Oberklassen mit separatem Vererbungstyp möglich Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 414/719 Vererbung am Beispiel Einfaches Anwendungsszenario: für eine Firma sollen in einer Anwendung verschiedene Bauteile verwendet werden BauTeil: ist eine allgemeine Klasse von zu verwendenden Teilen, wobei jedes Bauteil eine Bezeichnung und eine Masse als Attribute hat. Neben einem Konstruktor zur Erzeugung von Bauteil-Objekten wird eine toString()-Methode für die Ausgabe benötigt. KaufTeil: ist eine spezielle Unterklasse Von Bauteilen, die von Zulieferern zu einem bestimmten Preis eingekauft werden. FertigungsTeil: ist eine spezielle Unterklasse Von Bauteilen, die innerhalb der Firma gefertigt werden und für die die Durchlaufzeit von Interesse ist. Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 415/719 Klassendefinition: Basisklasse Bauteil class BauTeil { private : string bezeichnung; float masse; public : BauTeil(string b, float m); virtual string toString(); }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 416/719 Klassendefinition: Unterklasse Kaufteil class KaufTeil : public BauTeil { private : string zulieferer; float preis; public : KaufTeil(string b, float m, string z, float p); virtual string toString(); }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 417/719 Klassendefinition: Unterklasse Fertigungsteil class FertigungsTeil : public BauTeil { private : int durchlaufzeit; public : FertigungsTeil(string b, float m, int d); virtual string toString(); }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 418/719 Konstruktor der Oberklasse Bauteil BauTeil::BauTeil(string b, float m) { bezeichnung = b; masse = m; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 419/719 Konstruktor der Unterklasse Kaufteil KaufTeil::KaufTeil(string b, float m, string z, float p) : BauTeil(b,m) { zulieferer = z; preis = p; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 420/719 Überschreiben der Methode toString() Die Methodendefinition string BauTeil::toString() { stringstream stst; stst << ”Teil : ” << bezeichnung << ”, ” << masse; return stst.str(); } aus der Oberklasse wird überschrieben durch string FertigungsTeil::toString() { stringstream stst; stst << ”Fertigungs” << BauTeil::toString() << ”, ” << durchlaufzeit; return stst.str(); } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 421/719 Erklärungen zum Beispiel Bei Konstruktoren können durch den Syntax Klassenname::Klassenname(...) : Konstruktor(...) { ... } explizit Konstruktoren der Oberklasse(n) oder anderer eingebetteter Komponenten aufgerufen werden (bei mehreren kommasepariert) Entsprechend erzeugt der Konstruktor durch KaufTeil::KaufTeil(string b, float m, string z, float p) : BauTeil(b,m) das Kaufteil erst als „normales“ Bauteil und setzt dann die Werte beiden Kaufteil-Attribute Die Methode toString() überschreibt die Methode aus der Oberklasse BauTeil welche dort als virtual definiert wurde, um die zusätzlichen eigenschaften auszugeben, verwendet aber auch Implementierung aus der Oberklasse explizit durch die Angabe der Herkunft der Methode bei dem Aufruf Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 422/719 Virtuelle Funktionen in C++ Definition einer Funktion als virtual erlaubt Angabe einer anderen Implementierung in einer abgeleiteten Klasse Wird auch als Overriding (Überschreiben) bezeichnet Durch Late Binding (spätes bzw. dynamisches Binden, wird die „passende“ Implementierung zur Laufzeit gewählt Statisches Binden: korrekte Implementierung einer (nicht-virtuellen) Funktion wird vom Compiler in Abhängigkeit vom Typ der Objekt- oder Zeigervariable ausgewählt Dynamisches Binden: korrekte Implementierung einer virtuellen Funktion wird zur Laufzeit ausgewählt, d.h. es wird getestet ob das Objekt unabhängig vom Variablentyp selber von einem noch spezielleren Typ ist (möglich wegen Polymorphie) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 423/719 Beispiel: Hauptprogramm Drei mal Aufruf der Methode toString() für Objekte verschiedenen Typs: int main() { BauTeil* b[3]; b[0] = new KaufTeil(”Getriebe”,122.5, ”Laios und Sohn”,99.99); b[1] = new FertigungsTeil(”Motor”,407.00,4); b[2] = new KaufTeil(”Vergaser”,72.00, ”Daidalos und Sohn”,49.99); for (int i=0 ; i < 3 ; i++) cout << b[i]->toString() << endl; return 0; } KaufTeil : Getriebe, 122.5, Laios und Sohn, 99.99 FertigungsTeil : Motor, 407, 4 KaufTeil : Vergaser, 72, Daidalos und Sohn, 49.99 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 424/719 Strukturen in C und C++ Strukturen in C Vorstufe der Objektorientierung: fassen vergleichbar mit Klassen von Objekten Daten zusammen Strukturen dienen in C lediglich der Zusammenfassung inhaltlich zusammengehöriger Variablen (keine Vererbung, Kapselung, Methoden, etc.) Erweiterung in C++: können dort ähnlich wie Klassen verwendet werden und bieten zusätzliche Funktionalität Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 425/719 Strukturen in C: Deklaration und Initialisierung Einfachste Form der Deklaration inklusive einer Benennung des Strukturtyps (für spätere Wiederverwendung) und der sofortigen Deklaration von Variablen dieses Typs struct typ_name { datentyp variable1; datentyp variable2; ... } struct_variable1, ... ; Initialisierung ähnlich Arrays über Werteliste (verschiedenen Typs) möglich struct typ_name struct_variable = {...}; zum Beispiel struct studenten_typ s = {”Meier”,”Karl”,”123456”,25}; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 426/719 Strukturen und C++ Strukturen wie oben dargestellt auch in C++ verwendbar Implizieren immer Typdefinition Verwendbarkeit in C++ erweitert: haben vergleichbare Möglichkeiten wie Klassen, d.h. Vererbung (→) Kapselung (→) Methoden (→) Unterschied zu Klassen: Default-Zugriff für Attribute und Methoden sowie Vererbung ist public Häufig jedoch „elegantere“ Lösung durch Verwendung objektorientierter Konzepte wie Klassen möglich Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 427/719 Zusammenfassung: Objektorientierung in C++ Grundkonzepte der Objektorientierung Klassen von Objekten mit gleichen Eigenschaften Methoden als Beschreibung der Funktionalität, welche zu diesen Objekten gehört Vererbung als Mittel zur Wiederverwendung von Code Erlaubt Entwicklung sehr gut strukturierter und damit leicht zu entwickelnder und zu pflegender Programme Konzepte sind in der Anwendung aber recht komplex → erfordert „objektorientierte Denkweise“ Objektorientierung erfolgreiches Konzept, das sich in vielen Programmiersprachen aber auch in anderen Gebieten durchsetzt Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 428/719 Zusammenfassung C und C++ C und C++ ... sind weit verbreitet eingesetzte Programmiersprachen in allen denkbaren Anwendungsbereichen stellen eine Hauptlinie bei der Entwicklung von Programmiersprachen dar, d.h. viele andere Programmiersprachen sind davon abgeleitet sind sehr umfangreiche Programmiersprachen mit zahlreichen umgesetzten, anspruchsvollen Konzepten C und C++ sind keine „einfachen“ Programmiersprachen ABER: „wer C++ programmieren kann, kann auch (fast) alles andere programmieren“ Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 429/719 Zusammenfassung C und C++ /2 In der Vorlesung: wichtigste Kernkonzepte vorgestellt C und C++ umfassen noch wesentlich mehr Konzepte: Bitmanipulaionsoperatoren - Union-Typkonstruktor - Typqualifikatoren: const und volatile - Zeiger auf Funktionen - Präprozessor pragmas bedingte Kompilierung - Unicode in C/C++ - virtuelle Vererbung zahlreiche Standardbibliotheken - Typumwandlung durch Casting Reflexion und Laufzeittypinformationen - Entwicklungsumgebungen Debugging - Entwicklungstools - Programmstacks - Templates für Klassen und Funktionen - Statische Methoden - virtuelle Destruktoren Namensräume - friend-Klassen - Entwicklung von Nutzerschnittstellen (GUIs) - Arbeit mit Hardwareschnittstellen - Arbeit mit Netzwerken Multithreadding - Coding Conventions - Eike Schallehn, FIN/ITI ... Grundlagen der Informatik für Ingenieure 430/719 Teil IX Eigenschaften und Entwurf von Algorithmen Überblick 1 Einführung 2 Grundlegende Eigenschaften von Algorithmen 3 Zeitkomplexität von Algorithmen 4 Zeitkomplexität am Beispiel von Sortieralgorithmen 5 Typische Algorithmenmuster Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 432/719 Algorithmen Zur Erinnerung: Definition (Algorithmus) Ein Algorithmus ist eine eindeutige Beschreibung eines in mehreren Schritten durchzuführenden Vorgangs zur Lösung einer bestimmten Klasse von Problemen. Bisher: Sehr einfache Algorithmen, z.B. Matrizenmultiplikation durch geschachtelte Schleifen Einfügen in eine Datenstrukur (einfach verkettete Liste) Steuerung der Nutzerinteraktion Möglichkeiten der Umsetzung in C/C++ Nur: Umsetzbarkeit von Algorithmen! Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 433/719 Eigenschaften von Algorithmen Jetzt: ist der Algorithmus ein „guter“ Algorithmus? → Eigenschaften von Algorithmen Effektivität: berechnet der Algorithmus, was er berechnen soll? Kommt er unter allen Umständen irgendwann zu einem Ergebnis? → Terminiertheit Berechnet er das richtige? → Korrektheit Berechnet er für identische Eingaben immer dasselbe Ergebnis? → Determiniertheit Effizienz: Liefert der Algorithmus (auch bei Eingabe beliebig großer Datenmengen) möglichst schnell das Ergebnis? → Zeitkomplexität Eigenschaften von Algorithmen werden oft durch die Eigenschaft des zu lösenden Problems bestimmt → Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 434/719 Eigenschaften von zu lösenden Problemen Kann für das Problem überhaupt ein Algorithmus angegeben werden, welcher das Problem (für alle Eingaben) löst? → Berechenbarkeit, Entscheidbarkeit Wie schnell könnte der bestmögliche Algorithmus für dieses Problem arbeiten? → Komplexitätsklassen Kann für das Problem ein Algorithmus angegeben werden, der (auch bei Eingabe beliebig großer Datenmengen) nach „vertretbarer“ Zeit zu einem Ergebnis kommt? → Praktische Berechenbarkeit, Komplexitätsklasse P (polynomiale Zeitkomplexität) Kann für das Problem kein solcher Algorithmus angegeben werden? → Komplexitätsklasse EXP (exponentielle Zeitkomplexität), NP-Vollständigkeit Fragen bzgl. Eigenschaften von Algorithmen und Problemen stehen im Mittelpunkt der Betrachtungen für Theoretische Informatik Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 435/719 Warum ... sind Eigenschaften von Algorithmen für Ingenieure von Bedeutung? In der Anwendung von Software und der Entwicklung von Produkten kann ein Ingenieur in verschiedenen Bereichen auf Probleme der Eigenschaften von Algorithmen treffen, z.B. 1 Bei der Entwicklung von Steuerungsalgorithmen von eingebetteten Systemen ist deren Korrektheit und Terminiertheit von extrem großer Bedeutung. Selbst wenn ein Ingenieur in deren Implementierung nicht direkt involviert ist, so entwickelt er durch die notwendige Spezifikation doch die Grundlagen für die Überprüfung. 2 Effizienzprobleme bei zu entwickelnder Software können grundlegenden Anforderungen für den Einsatz entgegenstehen. Beispiel Echtzeitfähigkeit: Automotive-Systeme wie ABS und EPS müssen so schnell zu einem Ergebnis kommen, dass der Steuerungsvorgang (Bremsen, Beschleunigen, etc.) nicht zu spät gestartet werden kann. Grundlegende Eigenschaften von Algorithmen Im Folgenden: Terminiertheit Determiniertheit Korrektheit Weitere Eigenschaften Berechnungsmodell/Paradigma: zum Beispiel C/C++ mit imperativen, funktionalen und rekursiven Berechnungskonzepten (Alternativen: logische oder nicht-deterministische Berechnungsmodelle) Umsetzung bestimmter Algorithmenmuster (→) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 437/719 Eigenschaften von Algorithmen: Terminiertheit Definition (Terminiertheit) Ein Algorithmus heißt terminierend, wenn er (bei jeder erlaubten Eingabe von Parameterwerten) nach endlich vielen Schritten abbricht. Beispiel einer nicht-terminierenden Berechnungsvorschrift aus der Mathematik ∞ X 1 1 1 1 e = 1 + + + + ··· = 1! 2! 3! n! n=0 Terminiertheit nicht immer erwünscht: Endlosschleifen in Betriebsystemen, GUI-Anwendungen (siehe GLUT Main Loop bei Grafikprogrammierung), Server-Programmen, etc. → theoretisch endlose Laufzeit möglich, Abbruch durch Ereignissteuerung bzw. externer Abbruch des Prozesses Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 438/719 Halteproblem Bedeutendes Problem in der theoretischen Informatik mit wichtigen Konsequenzen für die Praxis: Halteproblem: gibt es ein Verfahren (Algorithmus!), mit dem man für jeden Algorithmus entscheiden kann, ob dieser terminiert? Ein solches Verfahren wäre sehr nützlich: z.B. könnte ein Compiler oder Verifizierer für Programme Warnungen ausgeben, wenn diese (unter bestimmten Bedingungen) nicht terminieren Dieses Problem ist aber NICHT ENTSCHEIDBAR! → es kann kein Programm existieren, so dass ein Computer mit einem anderen Programm als Eingabe berechnen kann, ob dieses Programm unter allen Umständen terminiert Halteproblem ist ein klassisches Beispiel (von vielen) für ein nicht entscheidbares/berechenbares Problem Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 439/719 Terminiertheit in der Praxis Auch wenn kein allgemeines Verfahren existiert: Terminierheit kann nachgewiesen werden für Spezielle Algorithmen Klassen von Algorithmen Spezielle Programstrukturen (z.B. Schleifen) etc. Untersuchungen zur Terminiertheit von Programmen können im Rahmen der Verifizierung von Programmen durchgeführt werden Nicht-Entscheidbarkeit verbleibt als „Grauzone“ zwischen eindeutig terminierenden und eindeutig nicht-terminierenden Algorithmen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 440/719 Terminiertheit: Beispiel Ackermann-Funktion Terminiert folgender Algorithmus für alle (m,n)? int f1(int m, int n) if (m == 0) return n+1; else if (m > 0 && return f1(m-1, else if (m > 0 && return f1(m-1, else return -1; } { n == 0) 1); n > 0) f1(m, n-1)); Ackermann-Funktion: häufig untersuchtes Beispiel Sehr berechnungsaufwändig schon für kleine Parameter Problematisch für Computer: Speicherüberlauf wegen hoher Rekursionstiefe Aber: Terminiertheit wurde nachgewiesen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 441/719 Terminiertheit: Collatz-Problem Terminiert folgender Algorithmus für alle n? int f2(int n) { if (n < 1) return -1; while (n != 1) { if (n%2 == 0) n=n/2; else n=3*n+1; } return n; } Collatz-Folge 1 2 3 Starte mit einer beliebigen natürlichen Zahl n Ist diese gerade, halbiere sie, andernfalls berechne 3n + 1 Wiederhole ab Schritt 2 Vermutung: Folge endet immer mit Zyklus (4,2,1)* → konnte bisher weder bewiesen noch widerlegt werden Deshalb: Terminiertheit nicht bewiesen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 442/719 Terminiertheit: Fehlerhafte Abbruchbedingungen Terminiert folgender Algorithmus für alle n? int f3(int n) { while (n != 0) { n = n%7; n = n-1; } return n; } Algorithmus terminiert nicht für Vielfache von 7 Negative Eingaben → Endlosschleife Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 443/719 Eigenschaften von Algorithmen: Determiniertheit Definition (Determiniertheit) Ein Algorithmus ist determiniert, wenn dieser bei jeder Ausführung mit gleichen Startbedingungen und Eingaben gleiche Ergebnisse liefert. Entspricht mathematischem Konzept der Funktion als eindeutig Abbildung von Eingaben(-mengen) auf Ausgabe(-mengen) Alternative: zufallsbasierte (auch stochastische, randomisierte) Algorithmen → Einsatz ebenfalls unter vielen Bedingungen sinnvoll: Berechnungen (z.B. Monte-Carlo-Verfahren) Optimierungsprobleme (z.B. Genetische Algorithmen →) Nutzerinteraktion, Spiele, etc. Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 444/719 Beispiel: Zufallsbasierte Algorithmen Rπ Näherungsweise Berechnung von 0 sin(x) durch Monte-Carlo-Verfahren Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 445/719 Monte-Carlo-Verfahren für Integralberechnung /1 #include <cmath> #include <cstdlib> #include <ctime> #include <iostream> using namespace std; #define PI 3.14159265358979 #define VERSUCHE 10000000 int main() { float x,y; int treffer; srand(time(NULL)); ... Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 446/719 Monte-Carlo-Verfahren für Integralberechnung /2 ... for (int i=0; i < VERSUCHE; i++) { x = rand()%100000*PI/100000.0; y = rand()%100000/100000.0; if (y < sin(x)) treffer++; } float flaeche = PI * treffer / VERSUCHE; cout << ”Geschätzte Fläche: ” << flaeche << endl; return 0; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 447/719 Monte-Carlo-Verfahren für Integralberechnung /3 Erläuterungen zum Algorithmus Zufällige Erzeugung von Testpunkten (x, y) ∈ R2 im Bereich 0 ≤ x ≤ π (Integrationsbereich) und 0 ≤ y ≤ 1 (Wertebereich der Funktion in diesem Bereich) Ist der Testpunkt unterhalb der Kurve, ist er Teil der Fläche und somit ein Treffer Fläche unter Kurve kann damit über Verhältnis von Treffern zu Gesamtversuchen mal der Versuchsfläche 1 ∗ π berechnet werden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 448/719 MATLAB: Monte Carlo-Verfahren /1 versuche = 100; treffer = 0; xz = pi*rand(versuche,1); yz = rand(versuche,1); for i = 1:versuche if yz(i) < sin(xz(i)) treffer = treffer + 1; end end func = @(x)sin(x); korrekt = quad(func,0,pi); schaetzung = pi*treffer/versuche; fprintf(’Schaetzung: %f \n’,schaetzung); fprintf(’Korrekter Wert: %f \n’,korrekt); ... MATLAB: Erläuterungen zum Hauptprogramm Zufällige Punkte werden über rand()-Funktion als Felder erzeugt Da Matlab im Gegensatz zu C++ symbolische Mathematik unterstützt, kann der korrekte Wert durch Integration mittels der quad()-Funktion berechnet werden func = @(x)sin(x); korrekt = quad(func,0,pi); MATLAB: Monte Carlo-Verfahren /2 ... figure hold on scatter(xz,yz); x = 0:0.01:pi; plot(x,sin(x),’Color’,’red’,’LineWidth’,2); legend([’\fontsize{18} Schaetzung: ’ num2str(schaetzung)]); hold off Program beinhaltet einfache grafische Ausgabe Scatter Plot für Zufallspunkte Funktionsplot von sin(x) MATLAB: Ausgabe des Programms Eigenschaften von Algorithmen: Korrektheit Allgemein betrachtet: die Korrektheit eines Algorithmus besteht darin, dass „er berechnet, was er berechnen soll“ → entspricht Bedeutung (Semantik) des Verfahrens Test auf Korrektheit erfordert vollständige und korrekte Darstellung der Semantik Verfahren zum Nachweis Ist so allgemein (wie Terminiertheit) nicht entscheidbar! Deshalb eingeschränkte Sicht auf Definition (Korrektheit) Unter der Korrektheit eines Algorithmus versteht man die Eigenschaft, einer Spezifikation (formale Beschreibung der Semantik) zu genügen. Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 453/719 Eigenschaften von Algorithmen: Korrektheit /2 Verifikation: formaler Beweis der Korrektheit bezüglich einer formalen Spezifikation Validation: (nicht-formaler) Nachweis der Korrektheit bezüglich einer informellen oder formalen Spezifikation (etwa systematisches Testen) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 454/719 Vor- und Nachbedingungen Möglichkeit der Spezifikation durch Angabe von Vor- und Nachbedingungen für Programmtext (Algorithmus, Funktion, Abschnitt, ...) { VOR } Programmtext { NACH } VOR und NACH sind dabei Aussagen über den Zustand vor bzw. nach Ausführung der Anweisungen Aussage bedeutet: Gilt VOR unmittelbar vor der Ausführung und terminiert der Programmtext, so gilt NACH unmittelbar nach Ausführung. Kann oft formal verifiziert bzw. durch Tests evaluiert werden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 455/719 Effizienz von Algorithmen Effizienz von Algorithmen betrifft eigentlich verschiedene Kriterien Laufzeiteffizienz: das Ergebnis soll so schnell wie möglich geliefert werden Effizienz der Ressourcen-Nutzung: das Ergebnis soll unter möglichst geringer Nutzung von Ressourcen (vor allem Speicher: Haupt- und Festplattenspeicher) berechnet werden Meist Trade-Off (Kompromiss) zwischen beiden Kriterien möglich, zum Beispiel Vorberechnung und Speicherung von (Zwischen-)Ergebnissen Speicherung von Zugriffspfaden für Daten (Indexe → Datenstrukturen, Datenbanken) Ressourcen werden jedoch oft als gegeben betrachtet, deshalb: Fokus auf Laufzeiteffizienz! Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 456/719 Laufzeiteffizienz von Algorithmen Laufzeiteffizienz drückt ein optimales temporales Verhalten eines Algorithmus aus (möglichst hohe Geschwindigkeit, möglichst geringe Bearbeitungszeit) Tatsächliche Ausführungszeit kann für eine konkrete Programmausführung von zahlreichen Faktoren abhängen Wie gut/schnell ist die Hardware (CPU, Festplatten, ...)? Welche Programmiersprache wurde verwendet? Laufen parallel andere Prozesse, die die Rechenzeit beeinflussen? etc. und ist deshalb als Maß für die Effizienz des Algorithmus wenig geeignet Abstraktion: wie verändert sich die Anzahl der notwendigen Bearbeitungsschritte in Abhängigkeit von der „Größe“ des zu lösenden Problems → Zeitkomplexität! Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 457/719 Zeitkomplexität Definition (Zeitkomplexität) Die Zeitkomplexität eines Algorithmus ist eine Abschätzung der Anzahl der durchzuführenden Berechnungsschritte f (n, m, ...) in Abhängigkeit von der Größe der Eingabe(n) n, m, .... Weitere Unterteilung möglich: Best Case-Komplexität: nach wie vielen Schritten beendet der Algorithmus im günstigsten Fall seine Ausführung Average Case-Komplexität: nach wie vielen Schritten beendet der Algorithmus im durchschnittlichen Fall seine Ausführung Worst Case-Komplexität: nach wie vielen Schritten beendet der Algorithmus im ungünstigsten Fall seine Ausführung Normalerweise Average Case- (und zum Teil Worst Case-) Komplexität von Bedeutung Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 458/719 Zeitkomplexität: einfaches Beispiel Suche einer Zahl x in einem Feld der Größe n: int suche(int x, int feld[], int feld_groesse) { for (int i=0; i < feld_groesse; i++) if (x == feld[i]) return 1; return 0; } Gezählt werden jetzt nur die Vergleiche V in Abhängigkeit von der Feldgröße n: Best Case: gleich das erste Element im Array ist das gesuchte → Anzahl der Vergleiche ist V(n) = 1 Average Case: im Durchschnitt finden wir das gesuchte Element, nachdem wir das halbe Feld durchsucht haben (Voraussetzung: einmalige Feldwerte, Suchwerte im selben Wertebereich) → Anzahl der Vergleiche ist V(n) = n2 Worst Case: das gesuchte Element ist garnicht oder erst als letztes im Feld → Anzahl der Vergleiche ist V(n) = n Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 459/719 Zeitkomplexität: Analyse des Beispiels Gesamtaufwand f (n) ist bestimmt von der Anzahl der Vergleiche V(n) etwa über einen konstanten Faktor cop , welcher den Aufwand für einen Vergleich und abhängige Operationen (z.B. Iterationschritte) einschließt, d.h. f (n) ∼ cop ∗ V(n) wobei es Abweichungen vor allem für kleine n gibt, durch Mehraufwand für Programmstart etc. Für den mittleren und schlechtesten Fall gilt: wenn sich die Problemgröße „n“ um einen Faktor csize ändert, so verändert sich auch die Anzahl der Vergleiche und somit der Aufwand um diesen Faktor f (csize ∗ n) ∼ cop ∗ csize ∗ V(n) Entfernung (irrelevanter) konstanter Faktoren über asymptotische Abschätzung → Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 460/719 O-Notation: Asymptotische Abschätzung O-Notation (auch Landau-Notation) beschreibt Größenordnung bzw. Wachstumsgeschwindigkeit der Funktion Idee: Angabe einer einfachen und intuitiv verständlichen Vergleichsfunktion g : N → N für Aufwandsfunktion f mit f (n) = O(g(n)) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 461/719 O-Notation: Asymptotische Abschätzung /2 Beispiel Suchfunktion: der Aufwand wächst linear mit der Größe des Problems f (n) = O(n) d.h. Vergleichsfunktion g(n) = n Rechnen in Größenordnungen erlaubt Vereinfachungen: Weglassen von konstanten Faktoren: O(c ∗ n) = O(n) Basis des Logarithmus ist unerheblich: O(log2 (n)) = O(log(n)) Beschränkung auf höchsten Exponenten: O(n2 + n + 1) = O(n2 ) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 462/719 O-Notation (formal) Formale Definition: f (n) = O(g(n)) : ⇔ ∃c, n0 ∀n ≥ n0 : f (n) ≤ c · g(n) f (n) g(n) ist für genügend große n durch eine Konstante c beschränkt, d.h. f wächst nicht schneller als g c · g(n) 6 f (n) n0 Eike Schallehn, FIN/ITI - n Grundlagen der Informatik für Ingenieure 463/719 Komplexitätsklassen Komplexitätsklassen erlauben Zusammenfassen von Algorithmen mit typischen Aufwandsabschätzungen Auch Probleme können danach in Komplexitätsklassen eingeteilt werden: durch den besten (bekannten) Algorithmus zur Lösung des Problems O(1) O(log n) O(n) O(n · log n) O(n2 ) O(nk )für ein k ≥ 0 O(2n ) Eike Schallehn, FIN/ITI konstanter Aufwand logarithmischer Aufwand linearer Aufwand quadratischer Aufwand polynomialer Aufwand Problemklasse P exponentieller Aufwand Problemklasse EXP bzw. NP-vollständiges Problem Grundlagen der Informatik für Ingenieure 464/719 Wachstum f (n) log n n n · log n Eike Schallehn, FIN/ITI n=2 1 2 2 24 = 16 4 16 64 28 = 256 8 256 2048 210 = 1024 10 1024 10240 1048576 ≈ 1012 ≈ 109 ≈ 1018 ≈ 10308 ≈ 10315653 n2 4 256 65536 n3 8 4096 16777200 2n 4 65536 ≈ 1077 Grundlagen der Informatik für Ingenieure 220 = 1048576 20 1048576 20971520 465/719 Wachstum /2 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 466/719 Problemklassen und typische Probleme Aufwand O(1) O(log n) O(n) O(n · log n) O(n2 ) O(n3 ) O(2n ) Eike Schallehn, FIN/ITI Typische Probleme Einige Suchverfahren für Tabellen (Hashing) Allgemeine Suchverfahren für Tabellen (Baum-Suchverfahren) Sequenzielle Suche, Suche in Texten Sortieren Einige dynamische Optimierungsverfahren (z.B. optimale Suchbäume), Multiplikation Matrix-Vektor (einfach) Matrizen-Multiplikation (einfach) Zahlreiche Optimierungsprobleme Grundlagen der Informatik für Ingenieure 467/719 Zeitkomplexität am Beispiel von Sortieralgorithmen „Computer verbringen im Durchschnitt 25% ihrer Rechenzeit mit Sortieren.“ Oft zitiert, Quelle: ??? Grundlegende Aufgabe: Schaffung einer (Halb-)Ordnung von Daten, so dass sie auf- oder absteigend nach ausgewählten Eigenschaften angeordnet sind Sortierung von Daten von großer Bedeutung für Effizienz zahlreicher Algorithmen, z.B. Suche von Daten, Optimierung, etc. Nutzbarkeit der Daten durch Anwender (z.B. Sortierung nach Präferenzen, Relevanz, etc.) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 468/719 Sortieralgorithmen Klasse von Algorithmen mit gleichen Schnittstellen Eingabe: unsortiertes Feld (Liste, Menge), ggf. Sortierbzw. Ordnungskriterium (z.B. welches Attribut, auf- oder absteigend, etc.) Ausgabe: sortiertes Feld (Liste) Zahlreiche existierende Implementierungen mit sehr verschiedenen Eigenschaften BubbleSort (hier vorgestellt) MergeSort (hier vorgestellt) QuickSort HeapSort ... Typischer Algorithmentyp, der für grundlegende Betrachtungen zu Algorithmeneigenschaften herangezogen wird (Komplexität, Speicherverbrauch, Berechnungsmodell, etc.) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 469/719 BubbleSort Sehr einfacher, aber auch wenig effizienter Sortieralgorithmus Idee: Verschieden große aufsteigende Blasen („Bubbles“) in einer Flüssigkeit sortieren sich quasi von allein, da größere Blasen die kleineren „überholen“. Umsetzung als Algorithmus: 1 2 Eike Schallehn, FIN/ITI Durchlaufe das Feld und tausche dabei das aktuelle Element mit dem folgenden, wenn diese nicht in Sortierreihenfolge sind Wiederhole das komplette Durchlaufen des Feldes so lange, bis bei einem Durchlauf keine Vertauschungen mehr durchgeführt wurden Grundlagen der Informatik für Ingenieure 470/719 BubbleSort: Beispiel 5 1 8 3 9 2 1 5 8 3 9 2 1 5 3 8 9 2 1 5 3 8 2 9 1 3 5 8 2 9 3. Durchlauf 1 3 5 2 8 9 4. Durchlauf 1 3 2 5 8 9 5. Durchlauf 1 2 3 5 8 9 1. Durchlauf 2. Durchlauf Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 471/719 BubbleSort: Optimierung Größte Zahl rutscht in jedem Durchlauf automatisch an das Ende der Liste im Durchlauf k reicht die Untersuchtung bis Position n − k Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 472/719 BubbleSort in C++ void bubblesort (int feld[], int feld_groesse) { bool swapped; int max = feld_groesse - 1; do { swapped = false; for (int i = 0; i < max; i++) { if (feld[i] > feld[i + 1]) { swap (feld, i, i + 1); swapped = true; } } max--; } while (swapped); } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 473/719 BubbleSort in C++ - Erläuterungen Erläuterungen zum Code Variable swapped beendet Algorithmus, wenn bei einem Durchlauf keine Vertauschung mehr durchgeführt wurde Variable max setzt Optimierung um, dass bei Durchlauf k nur bis n − k verglichen werden muss Hilfsfunktion swap() tauscht zwei Elemente in einem Feld Vollständiger Quelltext auf der Web-Seite zur Vorlesung Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 474/719 Analyse von BubbleSort Gezählt werden wieder Vergleiche Bester Fall: Die Liste ist sortiert, was nach einem Durchlauf mit n − 1 Vergleichen festgestellt werden kann: O(n) Mittler und schlechtester Fall: Mit der Optimierung müssen wir in den einzelnen Durchläufen n − 1, n − 2, n − 3 ... 1 Vergleiche Durchführen Laut Summenformel: n−1 X i= i=1 (n − 1)(n − 2) n2 − 3n + 2 = 2 2 und damit O(n2 ) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 475/719 MergeSort: Prinzip Relativ effizienter und oft verwendeter Sortieralgorithmus Beruht auf grundlegendem Algorithmenmuster (→): Teile und Herrsche Idee: 1 2 3 Eike Schallehn, FIN/ITI Teile die zu sortierende Liste in zwei gleich große Teillisten Sortiere diese durch rekursive Anwendung desselben Verfahrens (wird zurückgeführt auf trivialen Fall der Liste mit einem Element, welche immer sortiert ist) Mische die sortierten Teilergebnisse und setze so das Gesamtergebnis zusammen Grundlagen der Informatik für Ingenieure 476/719 MergeSort: Beispiel Split 5 5 5 Merge Eike Schallehn, FIN/ITI 1 1 5 8 1 9 3 2 8 3 9 8 3 9 1 9 1 5 3 9 1 5 8 2 3 1 2 3 5 2 2 3 8 9 9 Grundlagen der Informatik für Ingenieure 477/719 MergeSort in C++ /1 Zerlegung des Problems durch rekursive Teilung des zu sortierenden Feldes void msort (int feld[], int feld_groesse, int l, int r) { int i, j, k; int* b = new int[feld_groesse](); if (r > l) { int mid = (r + l) / 2; msort (feld, feld_groesse, l, mid); msort (feld, feld_groesse, mid + 1, r); ... Rekursiver Aufruf mit oberer und unter Grenze der Array-Elemente, die betrachtet werden sollen Rekursion bricht ab, wenn Grenzen gleich, d.h. nur ein Element im betrachteten Bereich Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 478/719 MergeSort in C++ /2 Merge-Schritt: sortierte Teillisten werden zusammengesetzt ... for (i = mid + 1; i > l; i--) b[i - 1] = feld[i - 1]; for (j = mid; j < r; j++) b[r + mid - j] = feld[j + 1]; for (k = l; k <= r; k++) if (b[i] < b[j]) feld[k] = b[i++]; else feld[k] = b[j--]; } } Benötigt Hilfsfeld b, in das Zwischenergebnisse kopiert werden Dann gemischtes Zurückkopieren der 2 sortierten Teilfolgen in Original-Array Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 479/719 Analyse von MergeSort Anzahl der Aufrufe der rekursiven Funktionen für jeden Pfad (Aufruftiefe): ca. log2 n, zum Beispiel Feldlänge n = 8 = 23 : 3 Aufrufebenen für Teilfelder mit Längen 4, 2 und 1 Feldlänge n = 16 = 24 : 4 Aufrufebenen für Teilfelder mit Längen 8, 4, 2 und 1 ... Auf jeder Ebene müssen für alle n Elemente alle 3 Schleifen durchlaufen werden, d.h. Faktor 3 · n Gesamtaufwand ca. 3 · n · log2 n Asymptotische Abschätzung: O(n · log n) Gleich für besten, mittleren und schlechtesten Fall D.h. im besten Fall (vorsortierte Liste) nicht so gut wie BubbleSort Aber: es sind keine Sortieralgorithmen bekannt, die für den durchschnittlichen und schlechtesten Fall eine bessere Komplexitätsabschätzung als O(n · log n) haben!!! Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 480/719 Zusammenfassung: Zeitkomplexität Laufzeit von Algorithmen/Programmen kann von zahlreichen Faktoren (Hardware, Programmiersprache, Laufzeitumgebung) abhängen Deshalb: Zeitkomplexität wichtigstes Kriterium für die Effizienz eines Algorithmus Abschätzung des Rechenaufwands in Abhängigkeit von der Problemgröße über eine Vergleichsfunktion, welches das Wachstum beschreibt → O-Notation Typische Probleme können durch schnellste bekannte Algorithmen in Komplexitätsklassen eingeordnet werden Beispiel Sortieralgorithmen: beste Algorithmen = Komplexitätsklasse = O(n · log n) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 481/719 Typische Algorithmenmuster Allgemeine Beschreibung der Aufgaben von Algorithmen: aus Eingabedaten bzw. einer großen Anzahl daraus abgeleiteter möglicher Lösungen sollen alle oder eine korrekte, optimale oder hinreichende Lösung(en) abgeleitet werden Effiziente Algorithmen folgen dabei oft gleichartigen Mustern oder Strategien für die Lösung des Problems Systematische Anwendung dieser Muster beim Entwurf → deshalb auch als Entwurfsparadigmen oder Entwurfsprinzipien von Algorithmen bezeichnet Kenntnis erleichtern Umsetzung effizienter Algorithmen Einführung am Beispiel: Rucksackproblem: optimales Packen eines Rucksacks 2 Lösungen: Greedy-Algorithmus, Backtracking Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 482/719 Rucksackproblem /1 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 483/719 Rucksackproblem /2 Eingabe: Ein leerer Rucksack mit einer maximalen Kapazität (Maximalgewicht) Eine Auswahl an möglichen Gegenständen, wobei jeder Gegenstand eine Gewicht und einen Nutzen hat Zu lösendes Problem: packe den Rucksack so, dass das Gesamtgewicht der eingepackten Gegenstände die Kapazität nicht übersteigt der Nutzen der eingepackten Gegenstände optimal, d.h. maximal, ist Klassisches Optimierungsproblem, auf welches zahlreiche andere Auswahlprobleme abgebildet werden können In der Informatik häufig untersucht Komplexitätsklasse O(2n ) (n ist Anzahl der wählbaren Gegenstände), d.h. NP-vollständiges Problem mit exponentiellem Berechnungsaufwand Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 484/719 Rucksackproblem: Lösung mit Greedy-Algorithmus Idee: man packe den Rucksack, indem man jeweils den aktuell besten Gegenstand auswählt, z.B. den mit dem höchsten Nutzen, den mit dem geringsten Gewicht oder den mit dem besten Verältnis von Nutzen und Gewicht. Wiederhole diese Auswahl, bis der Rucksack voll ist bzw. nur noch Gegenstände übrig sind, die nicht mehr in den Rucksack passen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 485/719 Algorithmenmuster: Greedy-Algorithmus Greedy=Gierig Grundprinzip: Finden einer Lösung, indem bei jedem Entscheidungsschritt die lokal optimale Entscheidung getroffen wird Dadurch findet man für viele Probleme (inklusive dem Rucksackproblem) aber nicht die global optimale Lösung Trotzdem oft verwendet, da bei günstigem Sortierkriterium meist eine hinreichend gute Lösung mit sehr geringem Aufwand O(n) gefunden wird Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 486/719 Greedy-Algorithmus in C++ Hinweise zur Implementierung Gegenstände und Rucksäcke sind als einfache Klassen umgesetzt Hier nur Auszüge aus dem Quelltext → der vollständiger Quelltext ist auf der Web-Seite zur Vorlesung zu finden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 487/719 Beispiel: Gegenstand als C++-Klasse class Gegenstand { public : Gegenstand() { gewicht = 1+rand()%MAX_GEWICHT; nutzen = 1+rand()%MAX_NUTZEN; }; int gewicht; int nutzen; }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 488/719 Beispiel: Rucksack als C++-Klasse class Rucksack { public : int gesamtnutzen(); int gesamtgewicht(); bool einpacken(Gegenstand*); void ausgabe(); private : set<Gegenstand*> inhalt; }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 489/719 Anmerkung: Templates und die STL in C++ Anmerkung zum Quelltext Programm verwendet Template-Klassen aus der STL (Standard Template Library) Templates ... erlauben (u.a.) „Klassenschablonen“ mit Typparametern zur Implementierung generischer Klassen, welche mit konkreten Typen wiederverwendet werden können Die STL ... bietet nützliche Klassenschablonen für häufig verwendete Datenstrukturen sowie Funktionen dazu Programm verwendet: set - Menge (ungeordnet) list - geordnete Liste (mit Methode zur Sortierung) vector - vergleichbar Array, kann aber dynamisch wachsen Iteratoren zum Durchlaufen aller Elemente in den zuerst genannten Strukturen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 490/719 Beispiel: Vergleichsfunktionen in C++ Sortierung der Gegenstände über Vergleichsfunktion 1. Alternative: Sortierung absteigend nach Nutzen bool vergleichsfunktion(Gegenstand* g1, Gegenstand* g2) { if (g1->nutzen > g2->nutzen) return true; if (g1->nutzen == g2->nutzen && g1->gewicht < g2->gewicht) return true; return false; } 2. Alternative: Sortierung nach Verhältnis Nutzen/Gewicht liefert bessere Ergebnisse bool vergleichsfunktion2(Gegenstand* g1, Gegenstand* g2) { if ((float)g1->nutzen/g1->gewicht > (float)g2->nutzen/g2->gewicht) return true; return false; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 491/719 Beispiel: Greedy Algorithmus zur Auswahl in C++ Rucksack packenGreedy(list<Gegenstand*> auswahl) { Rucksack rs; auswahl.sort(vergleichsfunktion); list<Gegenstand*>::const_iterator pos; for (pos = auswahl.begin(); rs.gesamtgewicht() < KAPAZITAET && pos != auswahl.end(); pos++) { Gegenstand* g = *pos; rs.einpacken(g); } return rs; } Sortieren der Liste nach Greedy-Präferenz (Vergleichsfunktion) Dann entsprechend dieser Reihenfolge „in den Rucksack stopfen“ Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 492/719 Rucksackproblem mit Backtracking Idee: man versuche, systematisch alle möglichen Packungen des Rucksacks zu testen: 1 2 3 Man teste für den ersten Gegenstand Rucksäcke in denen dieser enthalten bzw. nicht enthalten ist Für diese beiden Möglichkeiten, teste für den 2. Gegenstand alle Rucksäcke ... usw. Gib von allen untersuchten Rucksäcken den mit dem besten Nutzen zurück Weitere Möglichkeiten müssen ggf. nicht untersucht werden, wenn der Rucksack aus dem vorhergehenden Schritt schon gefüllt ist Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 493/719 Entwurfsprinzip: Backtracking Backtracking: Zurückverfolgen, Rücksetzverfahren Verfahren das systematisch alle Lösungen testet, und schlechte Lösungen verwirft und mit guten Lösungen weiterarbeitet Garantiert optimale Lösung Meist durch Rekursion umgesetzt: erzeugt baumartige Aufrufstruktur Durch vollständige Untersuchung sehr berechnungsaufwändig mit O(2n ) Optimierung durch „Abschneiden von Suchpfaden“ möglich → spezifische Abbruchkriterien für konkretes Problem (z.B. Rucksack voll) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 494/719 Backtracking in C++ /1 Umsetzung als rekursive Funktion Eine weitere Funktion setzt Einstieg in Rekursion um, da rekursive Funktion spezielle Parameter benötigt Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 495/719 Backtracking in C++ /2 Rucksack packenBacktracking(list<Gegenstand*> auswahl) { vector<Gegenstand*>* v = new vector<Gegenstand*>(auswahl.begin(),auswahl.end()); Rucksack rs; rs = packenRekursiv(rs,v,0); return rs; } Rekursionseinstieg Kopieren der Liste in einen Vektor erlaubt Zugriff über Position wie bei einem Array Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 496/719 Backtracking in C++ /3 Rucksack packenRekursiv(Rucksack rs, vector<Gegenstand*>* auswahl, int pos) { if (pos < auswahl->size() && rs.gesamtgewicht() < KAPAZITAET) { Rucksack rs1 = packenRekursiv(rs,auswahl,pos+1); rs.einpacken((*auswahl)[pos]); Rucksack rs2 = packenRekursiv(rs,auswahl,pos+1); if (rs1.gesamtnutzen() > rs2.gesamtnutzen()) return rs1; return rs2; } return rs; } Rekursiv werden jeweils alle Rucksäcke mit UND ohne den Gegenstand an der aktuellen Position in der Auswahl berechnet Nur die beste wird jeweils zurückgegeben So werden systematisch alle Lösungen getestet Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 497/719 Weitere Algorithmenmuster Teile und Herrsche Dynamische Programmierung Genetische Algorithmen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 498/719 Algorithmenmuster: Teile und Herrsche Teile und Herrsche=Divide et Impera Idee: Man zerlege ein komplexes Problem in kleinere Teilprobleme Man zerlege so lange, bis man bei einer Problemgröße angekommen ist, bei der die Lösung trivial ist Man setze die Lösungen der Teilprobleme umgekehrt schrittweise zur Gesamtlösung zusammen Ebenfalls häufig rekursiv umgesetzt Z.B. MergeSort (→) und viele andere Sortieralgorithmen (QuickSort, HeapSort) Typische Komplexitätsklassen: O(n · log n) und O(log n) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 499/719 Algorithmenmuster: Dynamische Programmierung Idee: systematisches Zusammensetzen einer Gesamtlösung aus häufig auftretenden Teillösungen Funktioniert bottom up (von unten nach oben): kleinste Teillösungen werden zuerst berechnet und aufgehoben, um daraus dann schrittweise komplexere zusammenzusetzen Vermeidet durch Abspeichern der Teillösungen Mehrfachberechnungen Beispiel: für Rucksackproblem existiert unter der Annahme ganzzahliger Gewichte und Nutzen ein sehr effizienter Algorithmus mit Dynamischer Programmierung, indem optimale „Teilrucksäcke“ für verschiedene Kapazitäten kleiner der Maximalkapazität berechnet werden, um dann schrittweise „zusammengesetzt“ zu werden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 500/719 Algorithmenmuster: Genetische Algorithmen Beispiel für Muster zufallsbasierter Algorithmen Idee: Nachbildung der natürliche Auslese nach Evolutionstheorie von Darwin Erzeuge initiale Lösungen mit einfachem Verfahren (Greedy, Zufall) → Gen-Pool Bewerte Lösungskandidaten mit einer Überlebensfunktion Erzeuge neue Lösungen aus den besten Kandidaten durch Kreuzung (Kombination von Lösungen) oder Mutation (zufällige Veränderung) Wiederhole dies über eine feste Anzahl von Schritten (Generationen) oder bis Lösung bestimmte Qualität hat (z.B. verbessert sich kaum noch im Vergleich zur Vorgängergeneration) Aufwand (meist O(n) oder O(1)) kann sehr präzise gesteuert werden Auch hier: garantiert nicht das Finden der optimalen Lösung Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 501/719 Zusammenfassung: Algorithmen Grundlegende Eigenschaften von Algorithmen Terminiertheit Determiniertheit Korrektheit Zeitkomplexität als wichtiges Maß für Effizienz von Algorithmen Effiziente Algorithmen verwenden meist typischen Algorithmenmuster Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 502/719 Teil X Grundlegende Datenstrukturen Überblick 1 Einführung 2 Datenstrukturen für Kollektionen 3 Queues und Stacks 4 Bäume, Suchbäume und Hash-Tabellen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 504/719 Datenstrukturen Immer wiederkehrende Anforderungen an Verwaltung von Daten im Haupt- und Sekundärspeicher: typische Anordnungen und Zusammenhänge, typische Operationen und immer möglichst effizient! Vergleichbar Algorithmenmustern für die Verarbeitung von Daten: „klassische“ Datenstrukturen als Muster für effiziente Verwaltung von Daten Darüber hinaus: viele klassische Datenstrukturen oft als direkt wiederverwendbare Implementierungen in Programmiersprachenbibliotheken vorhanden Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 505/719 Beispiele für Datenstrukturen und deren Verwendung Prüfungslisten mit geordneten Studentendaten Knoten- und Kantenlisten in BREP-Modellen Das Inventory einer Computerspielfigur als Menge von Gegenständen Verzeichnisbäume zur Verwaltung von Dateien Straßennetzwerke eines Routenplaners als Graphen Warteschlangen mit Prozessen für die Prozessverwaltung des Betriebssystems Der Programmstack zur Verwaltung lokaler Daten von Funktionen während der Programmausführung B-Bäume als Indexe für schnelle Zugriffe in Datenbanksystemen (→) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 506/719 Definition: Datenstrukturen Definition (Datenstruktur) Eine Datenstruktur ist eine Anordnungsvorschrift zur Organisation und Speicherung von Daten, die für bestimmte Klassen von Anwendungen einen effizienten Zugriff ermöglicht. Umfasst zwei wesentliche Aspekte: Schnittstelle: Festlegung der möglichen Operationen und des Verhaltens als abstrakte Spezifikation (Abstrakte Datentypen →) oder konkrete Programmierschnittstelle (z.B. Bibliotheken wie C++ Standard Template Library →) Implementierung: konkrete Umsetzung in einer Programmiersprache durch möglichst effiziente Speicherstrukturen und Algorithmen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 507/719 Abstrakte Datentypen Abstrakte Datentypen (ADTs) als implementierungsunabhängige Spezifikationmethode der Schnittstelle und der Semantik Beispiel: Menge type Set[Item] operators create: → Set is_empty: Set → Bool insert: Set × Item → Set is_in: Set × Item → Bool axioms ∀s : Set, ∀i,j : Item is_empty (create) = true is_empty (insert (s, i)) = false is_in (create, i) = false is_in (insert (s, i), j) = if i=j then true else is_in (s, j) insert(insert(s,i),j) = insert(insert(s,j),i) insert(insert(s,i),i) = insert(s,i) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 508/719 Eigenschaften von Datenstrukturen Datenstrukturen sind ... ... komplex: werden durch Typkonstruktoren (mit Zeigern, Feldern, Strukturen, Klassen, etc.) aus einfacheren Strukturen zusammengesetzt und letztendlich auf Basisdatentypen (numerische, alphanumerische) zurückgeführt ... dynamisch: können konkrete Ausprägung zur Laufzeit ändern, um zum Beispiel beliebige Anzahl neuer Fakten aufzunehmen oder diese aus der Struktur zu entfernen ... wiederverwendbar: erlauben, wenn einmal definiert, den Einsatz für zahlreiche verschiedene Anwendungen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 509/719 Datenstrukturen für Kollektionen Kollektionen: Oberbegriff für Datenstrukturen, die eine Sammlung/Anzahl von gleichartigen Objekten verwalten sollen Wichtigste: Mengen, Multimengen, Listen Je nach Anwendung sehr unterschiedliche Anforderungen Duplikate: können (werte-)gleiche Objekte in der Struktur auftreten Ordnung: spielt die Reihenfolge der Elemente in der Struktur eine Rolle Positionaler Zugriff: kann der Zugriff über die Position innerhalb der Struktur erfolgen (vergleichbar Array) Assoziativer Zugriff: kann der Zugriff über einen anderen Wert (Schlüssel) erfolgen Iterativer Zugriff: Durchlaufen aller Elemente in der Kollektion (z.B. mittels Schleife) für alle Strukturen möglich Abgrenzung zum Feld (Array) in Programmiersprachen: Feld ist nicht dynamisch, da feste Anzahl von Elementen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 510/719 Überblick: Kollektionsdatentypen Kollektionstyp Array / Feld Set / Menge Bag / Multimenge List / Liste Map, Hash Table Eike Schallehn, FIN/ITI Dynamisch nein ja ja ja ja Duplikate ja nein ja ja ja Grundlagen der Informatik für Ingenieure Ordnung ja nein nein ja nein Zugriff Position Position Assoziativ 511/719 Schnittstellen von Kollektionsdatentypen Zum Teil sehr unterschiedlich nach Implementierung Grundlegende Funktionen für alle Kollektionstypen Erzeugen einer (leeren) Kollektion Suchen eines Elementes Einfügen eines Elementes Löschen eines Elementes Spezielle Funktionen für Listen Element an einer bestimmten Position zurückgeben Einfügen eines Elementes am Anfang, am Ende, an einer bestimmten Position Löschen eines Elementes am Anfang, am Ende, an einer bestimmten Position Sortierung der Liste nach einem bestimmten Kriterium Spezielle Funktionen für Maps/Hash-Tabellen Einfügen eines Elementes mit Zugriffsschlüssel Suchen eines Elementes anhand des Schlüssels Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 512/719 Implementierung von Kollektionen /1 Liste Knoten1 4 Knoten2 17 Knoten3 21 KnotenN 37 NULL Grundprinzipien für Mengen, Multimengen, Listen etc. ähnlich 1 2 Verwendung von Klassen oder Strukturen für Kollektions-Schnittstelle sowie innere Knoten Verwendung von Zeigern zum Aufbau der dynamischen Struktur aus einzelnen Knoten Einfachste Lösung: Kollektionsobjekt mit Zeiger auf ersten Knoten Knoten trägt Wert und Zeiger auf nächsten Knoten Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 513/719 Implementierung von Kollektionen /2 Liste Knoten1 4 Knoten2 17 Knoten3 21 KnotenN 37 NULL Zusätzlicher Zeiger auf letztes Element im Listenkopf Erlaubt Einfügen bzw. Löschen des letzten Elementes mit O(1) statt O(n), da Liste nicht erst komplett durchlaufen werden muss Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 514/719 Implementierung von Kollektionen /3 Liste NULL Knoten1 4 Knoten2 17 Knoten3 21 KnotenN 37 NULL Häufig verwendete Implementierung: doppelt verkettete Liste (Double Linked List) mit „Rückzeigern“ von jedem Knoten auf seinen Vorgänger Erlaubt Durchlaufen und Navigieren in beliebige Richtung Höherer Speicheraufwand Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 515/719 Implementierung von Kollektionen /4 Zahlreiche Alternativen bei tatsächlichen Implementierung Große Anzahl von Zeigern oft wenig speichereffizient → intern Verwendung von verketteten Arrays Zahlreiche Optimierungen, insbesondere für Suche in Liste (Skip-Listen) Interne Implementierung als Baum (→) oder Hash-Tabelle (→) zur Beschleunigung bestimmter Operationen (zum Beispiel Einfügen in Mengen mit gleichzeitigem Test, ob Element schon in der Menge) ... Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 516/719 Wiederverwendbarkeit für verschiedene Elementtypen Kollektionen werden mit immer wieder gleicher Funktionalität für viele verschiedene Anwendungen benötigt, zum Beispiel Liste von ganzen Zahlen Liste von Vertexes in OpenGL Liste von Studenten (Objekte einer Klasse) ... Bisher: Elementtyp in Knoten-Struktur/Klasse festgelegt Keine Wiederverwendbarkeit: muss für jede Anwendung neu programmiert oder angepasst werden Mögliche Lösungen: void-Pointer (in C) bzw. Templates mit Typparametern in C++ (Java, uva.) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 517/719 Elemente mittels void* class Node { private: void* element; ... } Erzeugung einzelner Elemente als separate Objekte auf dem Heap und Referenzierung über untypisierten (void) Zeiger Nachteile: Nicht typsicher: Kollektion kann Elemente beliebigen Typs enthalten → fehleranfällig Erfordert prinzipiell Arbeit mit Zeigern Einzige Option in vielen älteren Programmiersprachen, zum Beispiel C Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 518/719 Elemente mittels Templates /1 template <class T> class Node { private : T element; ... }; template <class T> class List { ... }; Typparameter in aktuellen Programmiersprachen (Templates in C++, Generics in Java, Delphi und C#) Erlauben Implementierung generischer „Klassenschablonen“ Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 519/719 Elemente mittels Templates /2 Typparameter werden bei der Erzeugung einer konkreten Variablen durch einen konkreten Typ ersetzt, zum Beispiel List<int> meineListe; Setzt zur Übersetzungszeit T auf int Meist Übersetzung einer separaten Klasse für alle Typparameter Beispiel im folgenden Abschnitt: Warteschlangen (Queues →) mittels Templates In C++: Standard Template Library (STL) setzt wiederverwendbare Kollektionstypen als Templates um → Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 520/719 C++ Standard Template Library (STL) Bietet (vor allem) Kollektionsklassen und einige Standaralgorithmen Kollektionsklassen (Auswahl) list<...>: Liste (geordnet, Duplikate) vector<...>: dynamisches Array, ähnlich Liste set<...>: Menge (ungeordnet, kein Duplikate) multiset<...>: Multimenge (ungeordnet, Duplikate) map<...>: Kollektion mit assoziativem Zugriff (Schlüssel) Iterativer Zugriff (Durchlaufen) von Kollektionen über Iterator-Klassen (→) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 521/719 C++ STL: Einfaches Beispiel #include <iostream> #include <list> using namespace std; int main() { list<int> zahlenliste; zahlenliste.push_back(7); zahlenliste.push_back(1); zahlenliste.push_back(13); zahlenliste.sort(); list<int>::const_iterator position; for (position = zahlenliste.begin(); position != zahlenliste.end(); position++) cout << *position << ” ”; cout << endl; return 0; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 522/719 Iteratoren für Kollektionen Ebenfalls grundlegende Datenstruktur: Hilfsstruktur zum Durchlaufen einer Kollektion Daten bestehen nur aus Verweis (Zeiger, Referenz) auf aktuelle Position (z.B. Knoten) in der Kollektion Methoden und Operatoren zum Steuern des Durchlaufs (Anfang, Ende, Weitersetzen, Zurücksetzen, ...) In C++ STL ebenfalls als Template-Klassen umgesetzt Verschiedene Iteratoren möglich Navigationsrichtungen (vor- und rückwärts, nur vorwärts) Modifikation der Kollektion (z.B. Einfügen, Löschen an Position) erlaubt Wahlfreie Positionierung: beliebiges Setzen der Position, Überspringen von Einträgen, etc. Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 523/719 Zusammenfassung: Kollektionen Verwaltung von Anzahl von Objekten immer wiederkehrendes Problem Unterschiedliche Anforderungen: Duplikate, Ordnung, Zugriffsmöglichkeiten → unterschiedliche Strukturen: Listen, Mengen, Multimengen, Maps → unterschiedliche Implementierungsmöglichkeiten nach Möglichkeiten der Programmiersprache und Anforderungen bzgl. Laufzeit und Speicheraufwand In C++ umgesetzt als Template-Klassen in der STL Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 524/719 Queues und Stacks Beide Datenstrukturen sind Listen (mit eingeschränkter Funktionalität) ähnlich und auch oft vergleichbar implementiert Aber: haben besondere Bedeutung als Zwischenspeicher für die Steuerung der Programmlogik Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 525/719 Queues: Warteschlangen 34 17 45 7 22 13 Enqueue 5 Dequeue FIFO-Prinzip: First In, First Out Entspricht Liste, bei der nur am Anfang geschrieben/eingefügt und am Ende gelesen/entfernt werden kann Zwischenspeicherlösung, welche Daten aufsteigend nach Dauer seit der letzten Bearbeitung bereitstellt: zuerst älteste Daten bearbeiten Zwei wichtige Zugriffsoperationen: enqueue: Einreihen eines Elementes in die Warteschlange dequeue: Auslesen eines Elementes aus der Warteschlange Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 526/719 Verwendung von Queues Meist auf Ebene des Betriebsystems oder von Protokollen Synchronisation (Herstellung einer zeitlichen Reihenfolge) von parallelen Zugriffen auf beschränkte Ressourcen Prozesse auf Prozessoren Lese-/Schreibanforderungen auf Festplatten Transfer von Daten in Netzwerken Druckaufträge an einen Drucker Transaktionen in Datenbanksystemen (→) ... Asynchrone Kommunikation: Zwischenspeicherung eingehender Nachrichten/Daten, z.B. Pipe bei Prozeßkommunikation Simulation von Produktions- und Transportprozessen Lastverteilung auf parallel arbeitende Ressourcen über Kontrolle von Warteschlangen, z.B. Prozessoren in Multiprozessormaschinen oder einzelnen Servern in Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 527/719 Stacks: Stapelspeicher LIFO-Prinzip: Last In, First Out Entspricht Liste, bei der nur am Anfang geschrieben/eingefügt und ebenda gelesen/entfernt werden kann Pop Push Zwischenspeicherlösung, welche Daten absteigend nach Dauer seit der letzten Bearbeitung bereitstellt: zuerst aktuellste Daten bearbeiten Zwei wichtige Zugriffsoperationen: push: Ablegen eines Elementes auf dem Stapel pop: Entnehmen eines Elementes vom Stapel Eike Schallehn, FIN/ITI 17 34 Grundlagen der Informatik für Ingenieure 47 3 22 5 528/719 Verwendung von Stacks Meist auf Ebene der Speicherverwaltung für Programme Mikroprozessoren unterstützen Stapelspeicher direkt: haben Stack Pointer-Register (Zeiger auf oberstes Stack-Element) und Maschinensprache umfaßt Befehle PUSH und POP Programm-Stack: bei Aufruf von Funktionen oder Sub-Routinen werden aktuelle Daten (Variablen, Programmzustand) auf einem Stack verwaltet Rahmen für Daten eines Funktionsaufrufs: Stack Frame Sequentielle Folge aller Funktionsaufrufe (Stack Frames): Stack Trace Syntaktische Analyse von Ausdrücken oder Sätzen (mit implizit aus Regeln gebildeter hierarchischer Struktur) Parser als Teil von Compilern und Interpretern zur Übersetzung von Programmtext Auswertung algebraischer Terme ... Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 529/719 Queue Implementierung Implementierung einer einfachen Queue mit Basisfunktionalität in C++ Verwendet Templates: ermöglicht Wiederverwendung der Queue für verschiedene Elementtypen Queue<int> wi; Queue<char*> wc; Queue<Student> ws; Queue<Student*> wsp; ... Implementierung illustriert auch Grundprinzipien für Kollektions-Datenstrukturen in C++ (einfach verkettete Liste) Vollständiger Quelltext auf der Web-Seite zur Vorlesung Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 530/719 Queue Implementierung: Knoten-Klasse (C++) template <class T> class Node { public : Node(T e, Node<T>* n) { element = e; next = n; } void set_next(Node<T>* n) {next = n;} Node<T>* get_next() { return next;} T get_element() { return element;} private : T element; Node<T>* next; }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 531/719 Queue Implementierung: Queue-Klasse (C++) template <class T> class Queue { public : Queue() { first = NULL; last = NULL; } void enqueue(T element); T dequeue(); bool is_empty() { return (first == NULL); } private : Node<T>* first; Node<T>* last; }; Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 532/719 Queue Implementierung: enqueue() (C++) template <class T> void Queue<T>::enqueue(T element) { Node<T>* old_last = last; last = new Node<T>(element, NULL); if (old_last == NULL) first = last; else old_last->set_next(last); } Einfügen eines Elementes durch Erzeugen eines neuen Knotens am Ende der Warteschlange Spezialfall: Warteschlange war vorher leer Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 533/719 Queue Implementierung: dequeue() (C++) template <class T> T Queue<T>::dequeue() { if (first==NULL) throw ”Dequeue from empty queue.”; T e = first->get_element(); Node<T>* old_first = first; first = first->get_next(); if (first == NULL) last=NULL; delete old_first; return e; } Rückgabe des Elementes im Knoten am Listenanfang und dann Knoten löschen Spezialfall: Warteschlange ist danach leer Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 534/719 Queue Implementierung: main() (C++) int main() { Queue<int> w; w.enqueue(19); w.enqueue(1); w.enqueue(42); w.enqueue(13); while (! w.is_empty()) cout << w.dequeue() << ” ”; cout << endl; return 0; } Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 535/719 Zusammenfassung: Queues und Stacks Queue (Warteschlange) und Stack (Stapel) sind listenähnliche Datenstrukturen Besondere Bedeutung für Steuerung von Programmabläufen Grundprinzipien: Queue: „Daten, die ich jetzt nicht bearbeiten kann, packe ich in eine Warteschlange und arbeite diese dann später systematisch ab“ Stack: „Ich bearbeite erstmal die aktuellsten Daten, und packe diese bei noch dringenderen Aufgaben auf den Stapel, von wo ich sie hole, sobald ich mit der aktuellen Aufgabe fertig bin“ Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 536/719 Bäume, Suchbäume und Hash-Tabellen Im folgenden Fokus auf Datenstrukturen, welche den assoziativen Zugriff (über einen bestimmten Wert als Suchkriterium) optimieren Bäume: Abbildung bzw. Vorberechnung von Entscheidungen während der Suche in einer geordneten Menge als hierarchische Datenstruktur (Entscheidungsbaum, Suchbaum) Hash-Tabellen: Abbildung von Objekten auf den Speicher (deren Position darin) wird direkt aus dem Suchkriterium als Eigenschaft der Objekte abgeleitet Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 537/719 Bäume Grundlegende Datenstruktur zur Abbildung einer Hierarchie Setzt Grundprinzip „Teile und Herrsche“ (siehe Algorithmen (→) als Datenstruktur um: Zerlegung von großen Datenmengen in kleinere, besser handhabbare Grundstruktur: ausgehend von einer Wurzel (Gesamtheit) kommt man über verschiedene Verzweigungen (Unterteilungen) zu den Blättern (kleinste Einheiten) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 538/719 Allgemeine Struktur von Bäumen Höhe des Baumes Wurzel Innere Knoten 1 2 3 4 Blätter Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 539/719 Beispiele: Baumstrukturen in der Informatik Dateisysteme mit Festplatten, Verzeichnissen, wiederum darin enthaltenen Verzeichnissen und letztendlich Dateien Dokumentenstrukturen, z.B. Mit Kapiteln, Abschnitten, Absätzen HTML und XML als hierarchische Strukturen Syntaktische Analyse und Auswertung von Programmen/Termen: Zerlegung eines Satzes einer Sprache (Grammatik) enstprechend Regeln in Teilausdrücke/Wortgruppen bis hin zu kleinsten Einheiten (Atome, Terminale) Suchbäume als Indexe zum schnellen assoziativen Zugriff über Schlüsselwerte Datenbanksysteme Allgemein: Suche nach Worten in Texten Speziell: Suchmaschinen im World Wide Web Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 540/719 Binäre Suchbäume 12 5 2 17 15 11 14 19 18 21 Binär = Verzweigungsgrad 2: jeder Knoten hat maximal 2 Kindknoten Jeder Knoten speichert einen Suchschlüssel und repräsentiert damit folgende Entscheidung: Ist der gesuchte Wert gleich dem Schlüssel → GEFUNDEN Ist der Wert kleiner, gehe zum linken Kindknoten Ist der Wert größer, gehe zum rechten Kindknoten Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 541/719 Binäre Suchbäume: Suchen und Einfügen Suchen und Einfügen prinzipiell ähnlich: Algorithmus startet an der Wurzel In jedem Knoten: wenn Schlüssel nicht gefunden, verzweige zu einem Kindknoten Auf Blattebene: Einfügen: neuen Kindknoten erzeugen Suchen: Worst Case - Schlüssel nicht gefunden Aufwand für beide Operationen dominiert vom Durchlaufen des Weges von der Wurzel bis zum Blatt, d.h. Höhe des Baumes an dieser Stelle Balancierter Baum (→): Baum ist so gleichmäßig gefüllt, dass Weg von der Wurzel zu Blättern überall möglichst gleich Bei balanciertem Baum mit n = 2k Elementen ist die Höhe des Baumes ca. h = k = log2 n Durchschnittlicher Aufwand für beide Operationen damit: O(log n) Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 542/719 Balancierte Binäre Suchbäume: Aufwand Eike Schallehn, FIN/ITI Maximale #Knoten Höhe = Aufwand 1= 2^1-1 3=2^2-1 7=2^3-1 15=2^4-1 31=2^5-1 63=2^6-1 127=2^7-1 255=2^8-1 511 1023 ... n 1 2 3 4 5 6 7 8 9 10 ... O(log n) Grundlagen der Informatik für Ingenieure 543/719 Binärbaum: Beispielimplementierung (C++) Einfach Implementierung bestehend aus Klassen für Knoten mit Schlüssel und Verweisen auf Kindknoten Binärbaum mit Verweis auf Wurzeln Implementiert nur Suchen und Einfügen Eigentliche Daten werden nicht eingetragen, nur Schlüssel vom Typ int Hinweise Verwendet friend-Klassen: umgehen Kapselung, indem befreundete Klassen auf privat-Daten zugreifen können Vollständiger Quelltext auf der Web-Seit zur Vorlesung Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 544/719 Binärbaum: Knotenklasse class Node { friend class BinaryTree; private : int key; Node* left; Node* right; Node(int k) { ... } bool search(int k); void insert(int k); void print(int level); }; Definiert rekursive Methoden zum Einfügen und Suchen → Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 545/719 Binärbaum: Baumklasse class BinaryTree { public : BinaryTree() { root = NULL; } bool search(int key); void insert(int key); void print(); private : Node* root; }; Methoden zum Einfügen und Suchen als Einstiespunkt für Rekursion ausgehend von der Wurzel Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 546/719 Binärbaum: Einfügen void Node::insert(int k) { if (k==key) return; if (k<key) if (left != NULL) left->insert(k); else left = new Node(k); if (k>key) if (right != NULL) right->insert(k); else right = new Node(k); } Schlüssel vorhanden → Einfügen beenden Andernfalls, falls möglich im linken (neuer Schlüssel kleiner) oder rechten Teilbaum einfügen (neuer Schlüssel größer) Falls kein Kindknoten links oder rechts existiert: neuen Kindknoten mit neuem Schlüssel erzeugen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 547/719 Entartung von Bäumen Balanciertheit wichtige Eigenschaft von Bäumen: garantiert effiziente Ausführung der Operationen mit O(log n) Ohne weiteres aber keine garantierte Eigenschaft Abhängig zum Beispiel von Einfügereihenfolge Schlechte Einfügereihenfolge kann zu Entartung des Baumes führen Im schlimmsten Fall wird Baum zu Liste Operationen dann mit wesentlich schlechterer Laufzeitkomplexität O(n): sequentielle Suche Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 548/719 Beispiel: Entartung von Bäumen 1 4 2 2 1 6 3 5 3 4 7 5 6 Balancierter Baum bei Einfügereihenfolge 4, 2, 6, 3, 1, 7, 5 Eike Schallehn, FIN/ITI Entarteter Baum bei Einfügereihenfolge 1, 2, 3, 4, 5, 6, 7 Grundlagen der Informatik für Ingenieure 7 549/719 Balancierte Bäume Sicherstellung einer relativen Ausgeglichenheit bei binären Bäumen durch spezielle Modifikationsoperationen (Einfügen, Löschen) Angabe eines speziellen Balancekriteriums, z.B. AVL-Baum: in jedem Knoten darf der Höhenunterschied zwischen linkem und rechten Teilbaum maximal 1 sein! Wird Balancekriterium verletzt, werden Verfahren zur lokalen Reorganisation des Baumes angewandt → AVL-Bäume, Rot-Schwarz-Bäume Vollständige Ausgeglichenheit möglich durch Knoten mit variablem Verzweigungsgrad Mehr als 1 Schlüssel pro Knoten Verweis auf Kindknoten mit Werten zwischen 2 Schlüsseln (Bereich) Knotengröße kann an Speicherstrukturen angepasst werden (z.B. Blöcke der Festplatte) → B-Bäume Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 550/719 ... geht es besser als O(log n)? Assoziative Zugriffe (Suche über einen Schlüsselwert) mit Bäumen mit logarithmischem Aufwand O(log n) D.h. nur ein zusätzlicher Suchschritt notwendige für jede Verdopplung der Größe der Datenmenge, in der gesucht wird Geht es noch besser? Ja, Hash-Tabellen können Schlüsselzugriff (unter bestimmten Bedingungen) mit konstantem Aufwand O(1) umsetzen D.h. egal wie groß die Datenmenge, das Finden der richtigen Daten geht immer gleich schnell! Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 551/719 Hash-Tabellen Auch Streuwerttabelle oder Hash Map Grundprinzip: Berechnung der Position der Daten im Speicher (strukturiert als Tabelle) aus dem Schlüsselwert key Berechnung der Position beim Einfügen Berechnung der Position beim Suchen Erfordert Vorreservierung eines Speicherbereichs der Größe M → M meist sehr groß, ab mehreren Tausend Einträgen Positionen 0 . . . M − 1 in Speicherbereich werden auch Hash Buckets genannte Berechnung der Position über spezielle Hash-Funktion h : dom(key) → {0, 1, . . . , M − 1} Wahlfreier Zugriff im RAM und auf Festplatte ermöglicht direkten Zugriff auf an dieser Stelle gespeicherte Daten Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 552/719 Einfügen in Hash-Tabellen Zu speichernde Objekte Hash-Tabelle Student Udo Urban MatrNr 170480 0 1 2 Student Eva Lange MatrNr 175783 156324, Max Müller 170480, Udo Urban 3 4 Student Max Müller MatrNr 156324 5 6 175783, Eva Lange Hash-Funktion h(MatrNr)=MatrNr % 7 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 553/719 Suchen in Hash-Tabellen Hash-Tabelle 0 Suche nach Matrikelnummer: 1 170480 3 2 156324, Max Müller 170480, Udo Urban 4 5 6 175783, Eva Lange Hash-Funktion h(MatrNr)=MatrNr % 7 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 554/719 Hash-Funktionen Wertebereich ist (beim hier betrachteten statischen Hashen) durch Speichergröße M bestimmt Problem: Hash-Funktion ist nicht injektiv, d.h. verschiedene Schlüssel können auf eine Adresse abgebildet werden → Kollisionen! Gute Hash-Funktionen erzeugen möglichst zufällig gestreute Speicherzuordnung und machen dadurch Kollisionen unwahrscheinlich Meist umgesetzt durch Kombination von verschiedenen Operationen mit möglichst zufälligem Ergebnis, z.B. Bit-Verschiebeoperationen Am Ende Modulodivision durch M → Rest ist Hash-Wert Primzahlen als Parameter der Hash-Funktion sorgen für gute, zufällige Verteilung Kollisionen lassen sich aber meist nicht völlig vermeiden → erfordern Kollisionsbehandlung Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 555/719 Hash-Tabellen: Kollisionsbehandlung Verkettete Liste: der Eintrag in einer Hash-Tabelle verweist auf eine Liste der dorthin gehashten Daten Kann bei schlechter Hash-Funktion mit vielen Kollisionen zu Entartung führen Mehraufwand für Speicherung Sondieren: (engl. Probing) ist der Hash Bucket bereits belegt, wird nach einem einfachen Muster ein anderer Platz gesucht Z.B. lineares Sondieren: testen ob folgende Hash Bucket frei ist, erster freier wird genutzt Doppeltes Hashen: ist der Hash Bucket belegt, wird (ggf. wiederholt) ein weiterer Hash-Wert berechnet und diese Position getestet Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 556/719 Implementierung von Hash-Tabellen (Bisher besprochene) statische Hash-Verfahren: vordefinierte Speichergröße kann effizient über Array umgesetzt werden Dynamische Hash-Verfahren können den benutzten Speicherbereich zur Laufzeit Vergrößern → z.B. durch verkettete Arrays Kollisionsbehandlung durch verkettet Liste erfordert zusätzliche Datenstruktur Sondieren und Doppeltes Hashen Erfordern aufwändigere Operationsimplementierungen Beispielimplementierung auf der Web-Seite zur Vorlesung Einfaches statisches Hashverfahren mit linearem Sondieren In der Vorlesung nicht vorgestellt Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 557/719 Nachteile von Hashtabellen Keine Ordnung der Elemente: in Bäumen sind Elemente stets geordnet gespeichert – geordnete Ausgabe aus einer Hash-Tabelle erfordert zusätzliche Sortierung Vorreservierung des Speichers notwendig: z.B. über Arrays, die zur Vermeidung von Überlauf und Kollisionen ggf. weit überdimensioniert sind (Trade-off: Speichereffizienz vs. Laufzeiteffizienz) Überlauf möglich: bei einigen statischen Verfahren (z.B. bei Überlaufbehandlung durch Sondieren, nicht bei verketteter Liste) kann die Hash-Tabelle tatsächlich vollständig gefüllt werden, so dass keine weiteren Daten eingetragen werden können Aufwand für Dynamik: Verfahren, welche zur Vermeidung von Überläufen und Kollisionen, die Hash-Tabelle dynamisch wachsen lassen, nähern sich mit ihrem Laufzeitverhalten Bäumen an Aufwand für Überlaufbehandlung: auch bei vielen Kollisionen, z.B. durch schlechte Hash-Funktion, verschlechtert sich die Laufzeitkomplexität Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 558/719 Zusammenfassung: Datenstrukturen Klassische Datenstrukturen bieten Standardlösungen für effiziente Bearbeitung von Daten Wichtigste hier vorgestellt: Kollektionsdatentypen wie Listen, Mengen und Multimengen zur Verwaltung einer Sammlung zusammengehörender Objekte Queues und Stacks zur Steuerung der Berabeitungsreihenfolge von Datenobjekten Bäume und Hash-Tabellen für schnelles Suchen von Daten über einen Schlüsselwert Oft in Form von generischen Klassenbibliotheken umgesetzt, z.B. STL in C++ Eigene Implementierung durch Verwendung von Typkonstruktoren (Arrays, Structs, Klassen) und Zeiger sowie Klassenschablonen (Templates, Generics) möglich Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 559/719