Teil IX Eigenschaften und Entwurf von Algorithmen

Werbung
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
Herunterladen