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 428/715 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 429/715 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 430/715 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 431/715 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 433/715 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 434/715 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 435/715 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 436/715 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 437/715 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 438/715 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 439/715 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 440/715 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 441/715 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 442/715 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 443/715 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 444/715 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 449/715 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 450/715 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 451/715 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 452/715 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 453/715 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 454/715 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 455/715 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 456/715 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 457/715 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 458/715 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 459/715 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 460/715 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 461/715 Wachstum /2 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 462/715 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 463/715 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 464/715 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 465/715 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 466/715 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 467/715 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 468/715 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 469/715 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 470/715 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 471/715 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 472/715 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 473/715 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 474/715 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 475/715 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 476/715 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 477/715 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 478/715 Rucksackproblem /1 Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 479/715 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 480/715 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 481/715 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 482/715 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 483/715 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 484/715 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 485/715 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 486/715 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 487/715 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 488/715 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 489/715 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 490/715 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 491/715 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 492/715 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 493/715 Weitere Algorithmenmuster Teile und Herrsche Dynamische Programmierung Genetische Algorithmen Eike Schallehn, FIN/ITI Grundlagen der Informatik für Ingenieure 494/715 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 495/715 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 496/715 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 497/715 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 498/715 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 500/715 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 501/715 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 502/715 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 503/715 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 504/715 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 505/715 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 506/715 Ü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 507/715 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 508/715 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 509/715 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 510/715 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 511/715 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 512/715 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 513/715 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 514/715 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 515/715 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 516/715 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 517/715 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 518/715 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 519/715 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 520/715 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 521/715 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 522/715 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 523/715 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 524/715 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 525/715 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 526/715 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 527/715 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 528/715 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 529/715 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 530/715 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 531/715 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 532/715 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 533/715 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 534/715 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 535/715 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 536/715 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 537/715 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 538/715 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 539/715 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 540/715 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 541/715 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 542/715 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 543/715 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 544/715 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 545/715 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 546/715 ... 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 547/715 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 548/715 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 549/715 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 550/715 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 551/715 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 552/715 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 553/715 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 554/715 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 555/715