1. Objektorientierte Programmierung mit Daten

Werbung
Algorithmen und Datenstrukturen
1. Objektorientierte Programmierung mit Datenstrukturen und Algorithmen
In den 50er Jahren bedeutete „Rechnen“ auf einem Computer weitgehend
„numerisches Lösen“ wissenschaftlich-technischer Probleme. Kontroll- und
Datenstrukturen waren sehr einfach und brauchten daher nicht weiter untersucht
werden. Ein bedeutender Anstoß kam hier aus der kommerziellen Datenverarbeitung
(DV). So führte hier bspw. die Frage des Zugriffs auf ein Element einer endlichen
Menge zu einer großen Sammlung von Algorithmen1, die grundlegende Aufgaben
der DV lösen. Dabei ergab sich: Die Leistungsfähigkeit dieser Lösungen
(Programme) ist wesentlich bestimmt durch geeignete Organisationsformen für die
zu bearbeitenden Daten.
Die Datenorganisation oder Datenstruktur und die zugehörigen Algorithmen sind
demnach ein entscheidender Bestandteil eines leistungsfähigen Programms. Ein
einführendes Beispiel soll diesen Sachverhalt vertiefen.
1.1 Ein einführendes Beispiel: Das Durchlaufen eines Binärbaums
Das ist eine Grundaufgabe zur Behandlung von Datenstrukturen. Ein binärer Baum
B ist entweder leer, oder er besteht aus einem linken Baum BL, einem Knoten W und
einem rechten Teilbaum BR. Diese Definition ist rekursiv. Den Knoten W eines
nichtleeren Baumes nennt man seine Wurzel. Beim Durchlaufen des binären
Baumes sind alle Knoten aufzusuchen (, z. B. in einer vorgegebenen „von links nach
rechts"-Reihenfolge,) mit Hilfe eines systematischen Weges, der aus Kanten
aufgebaut ist.
Die Darstellung bzw. die Implementierung eines binären Baums benötigt einen
Binärbaum-Knoten:
Dateninformation
Knotenzeiger
Links
Rechts
Zeiger
Zeiger
zum linken
zum rechten
Nachfolgeknoten
Abb. 1.1-0: Knoten eines binären Suchbaums
Eine derartige Struktur stellt die Klassenschablone baumKnoten bereit2:
#ifndef BAUMKNOTEN
#define BAUMKNOTEN
#ifndef NULL
const int NULL = 0;
#endif // NULL
1
2
D. E. Knuth hat einen großen Teil dieses Wissens in "The Art of Computer Programming" zusammengefaßt
vgl. pr11_1, baumkno.h
1
Algorithmen und Datenstrukturen
// Deklaration eines Binaerbaumknotens fuer einen binaeren Baum
template <class T> class baumKnoten
{
protected:
// zeigt auf die linken und rechten Nachfolger des Knoten
baumKnoten<T> *links;
baumKnoten<T> *rechts;
public:
// Das oeffentlich zugaenglich Datenelement "daten"
T daten;
// Konstruktor
baumKnoten (const T& merkmal, baumKnoten<T> *lzgr = NULL,
baumKnoten<T> *rzgr = NULL);
// virtueller Destruktor
virtual ~baumKnoten(void);
// Zugriffsmethoden auf Zeigerfelder
baumKnoten<T>* holeLinks(void) const;
baumKnoten<T>* holeRechts(void) const;
// Die Klasse binSBaum benoetigt den Zugriff auf
// "links" und "rechts"
};
// Schnittstellenfunktionen
// Konstruktor: Initialisiert "daten" und die Zeigerfelder
// Der Zeiger NULL verweist auf einen leeren Baum
template <class T>
baumKnoten<T>::baumKnoten (const T& merkmal, baumKnoten<T> *lzgr,
baumKnoten<T> *rzgr): daten(merkmal), links(lzgr), rechts(rzgr)
{}
// Die Methode holeLinks ermoeglicht den Zugriff auf den linken
// Nachfolger
template <class T>
baumKnoten<T>* baumKnoten<T>::holeLinks(void) const
{
// Rueckgabe des Werts vom privaten Datenelement links
return links;
}
// Die Methode "holeRechts" erlaubt dem Benutzer den Zugriff auf den
// rechten Nachfoger
template <class T>
baumKnoten<T>* baumKnoten<T>::holeRechts(void) const
{
// Rueckgabe des Werts vom privaten Datenelement rechts
return rechts;
}
// Destruktor: tut eigentlich nichts
template <class T>
baumKnoten<T>::~baumKnoten(void)
{}
#endif // BAUMKNOTEN
Mit der vorliegenden Implementierung zu einem Binärbaum-Knoten kann bspw. die
folgende Gestalt eines binären Baums erzeugt werden:
2
Algorithmen und Datenstrukturen
1
2
3
5
4
Abb1.1-1: Eine binäre Baumstruktur
Benötigt wird dazu die folgenden Anweisungen im Hauptprogrammabschnitt:
// Hauptprogramm
int main()
{
int zahl;
baumKnoten<int> *wurzel;
baumKnoten<int> *lKind, *rKind, *z;
lKind = new baumKnoten<int>(3);
rKind = new baumKnoten<int>(4);
z
= new baumKnoten<int>(2,lKind,rKind);
lKind = z;
rKind = new baumKnoten<int>(5);
z
= new baumKnoten<int>(1,lKind,rKind);
wurzel = z;
}
1.1.1 Rekursive Problemlösung
Rekursive Datenstrukturen (z.B. Bäume) werden zweckmäßigerweise mit Hilfe
rekursiv formulierter Zugriffsalgorithmen bearbeitet. Das zeigt die folgende Lösung
in C++:
#include<iostream.h>
#include<stdlib.h>
#include "baumkno.h"
// Funktionsschablone fuer Baumdurchlauf
template <class T> void wlr(baumKnoten<T>* b)
{
if (b != NULL)
{
cout << b->daten << ' ';
wlr(b->holeLinks());
// linker Abstieg
wlr(b->holeRechts());
// rechter Abstieg
}
}
// Hauptprogramm
3
Algorithmen und Datenstrukturen
int main()
{
int zahl;
baumKnoten<int> *wurzel;
baumKnoten<int> *lKind, *rKind, *z;
lKind = new baumKnoten<int>(3);
rKind = new baumKnoten<int>(4);
z
= new baumKnoten<int>(2,lKind,rKind);
lKind = z;
rKind = new baumKnoten<int>(5);
z
= new baumKnoten<int>(1,lKind,rKind);
wurzel = z;
cout << "Inorder: " << endl;
ausgBaum(wurzel,0);
wlr(wurzel);
// Rekursive Problemlösung
cout << endl;
// Nichtrekursive Problemlösung
wlrnr(wurzel);
cout << endl;
}
Das Durchlaufen geht offensichtlich von der Wurzel aus, ignoriert zuerst die rechten
Teilbäume, bis man auf leere Bäume stößt. Dann werden die Teiluntersuchungen
abgeschlossen und beim Rückweg die rechten Bäume durchlaufen.
Jeder Baumknoten enthält 2 Zeiger (Adressen von BL und BR). Die Zeiger, die auf
leere Bäume hinweisen, werden auf „NULL“ gestellt.
1.1.2 Nichtrekursive Problemlösung
Das vorliegende Beispiel ist in C++ notiert. C++ läßt rekursiv formulierte
Prozeduren zu. Was ist zu tun, wenn eine Programmiersprache rekursive
Prozeduren nicht zuläßt? Rekursive Lösungsangaben sind außerdem schwer
verständlich, da ein wesentlicher Teil des Lösungswegs dem Benutzer verborgen
bleibt.
Die Ausführung rekursiver Prozeduren verlangt bekanntlich einen Stapel (stack). Ein
Stapel ist eine Datenstruktur, die auf eine Folge von Elementen 2 wesentliche
Operationen ermöglicht:
Die beiden wesentlichen Stackprozeduren sind PUSH und POP. PUSH fügt dem
Stapel ein neues Element an der Spitze (top of stack) hinzu. POP entfernt das
Spitzenelement. Die beiden Prozeduren sind mit der Typdefinition des Stapel
beschrieben. Der Stapel nimmt Zeiger auf die Baumknoten auf. Jedes Stapelelement
ist mit seinen Nachfolgern verkettet:
4
Algorithmen und Datenstrukturen
Zeiger auf Baumknoten
Top-Element
Zeiger auf Baumknoten
Zeiger auf Baumknoten
nil
nil
Abb. 1.1-2: Aufbau eines Stapels
Der nicht rekursive Baumdurchlauf-Algorithmus läßt sich mit Hilfe der
Stapelprozeduren der Containerklasse Stack der Standard Template Library (STL)
so formulieren:
template <class T> void wlrnr(baumKnoten<T>* z)
{
stack<baumKnoten<T>*, vector<baumKnoten<T>*> > s;
s.push(NULL);
while (z != NULL)
{
cout << z->daten << ' ';
if (z->holeRechts() != NULL)
s.push(z->holeRechts());
if (z->holeLinks() != NULL)
z = z->holeLinks();
else {
z = s.top();
s.pop();
}
}
}
Dieser Algorithmus ist zu überprüfen mit Hilfe des folgenden binären Baumes
5
Algorithmen und Datenstrukturen
Z1
1
Z2
Z3
2
5
Z4
Z5
3
4
Abb. 1.1-3: Zeiger im Binärbaum
Welche Baumknoten (bzw. die Zeiger auf die Baumknoten) werden beim Durchlaufen des vorliegenden Baumes (vgl. Abb. 1.1-3) über die Funktionsschablone
wlrnr aufgesucht? Welche Werte (Zeiger auf Baumknoten) nimmt der Stapel an?
Besuchte Knoten ¦
Stapel
-----------------+------------¦
Null
Z1
¦
Z5 Null
Z2
¦ Z4 Z5 Null
Z3
¦ Z4 Z5 Null
Z4
¦
Z5 Null
Z5
¦
Null
1.1.3 Verallgemeinerung
Bäume treten in vielen Situationen auf. Beispiele dafür sind:
- die Struktur einer Familie, z.B.:
Christian
Ludwig
Jürgen
Martin
Karl
Ernst
Abb. 1.1-4: Ein Familienstammbaum
- Bäume sind auch Verallgemeinerungen von Feldern (arrays), z.B.:
-- das 1-dimensionale Feld F
6
Fritz
Algorithmen und Datenstrukturen
F
F[1]
F[2]
F[3]
Abb. 1.1-5: Baumdarstellung eines eindimensionalen Felds
-- der 2-dimensionale Bereich
B
B[1,_]
B[2,_]
B[1,1]
B[1,2]
B[2,1]
B[2,2]
Abb. 1.1-6: Baumdarstellung eines zweidimensionalen Felds
-- Auch arithmetische Ausdrücke lassen sich als Bäume darstellen. So läßt sich
bspw. der Ausdruck ((3 * (5 - 2)) + 7) so darstellen:
+
*
7
3
-
5
2
Abb. 1.1-6: Baumdarstellung des arithmetischen Ausdrucks ((3 * (5 - 2)) + 7)
Durch das Aufnehmen des arithmetischen Ausdrucks in die Baumdarstellung können
Klammern zur Regelung der Abarbeitungsreihenfolge eingespart werden. Die
korrekte Auswertung des arithmetischen Ausdrucks ist auch ohne Klammern bei
geeignetem Durchlaufen und Verarbeiten der in den Baumknoten gespeicherten
Informationen gewährleistet.
7
Algorithmen und Datenstrukturen
Die Datenstruktur „Baum“ ist offensichtlich in vielen Anwendungsfällen die geeignete
Abbildung für Probleme, die mit Rechnerunterstützung gelöst werden sollen. Der zur
Lösung notwendige Verfahrensablauf ist durch das Aufsuchen der Baumknoten
festgelegt. Das einführende Beispiel zeigt das Zusammenwirken von Datenstruktur
und
Programmierverfahren
für
Problemlösungen
mit
Hilfe
von
Datenverarbeitungsanlagen.
Bäume sind deshalb auch Bestandteil von Container-Klassen aktueller Compiler
(C++, Java). Die Java Foundation Classes (JFC) enthalten eine Klasse JTree aus
dem Paket javax.swing, die eine Baumstruktur darstellt3.
Die Klasse JTree. Die Baumdarstellung basiert auf einem hierarchischen
Datenmodell. Die Modellklasse für JTree muß das Interface TreeModel
implementieren.
In
Swing
enthalten
ist
die
Implementierungsklasse
DefaultTreeModel. Die einzelnen Baumknoten müssen die Interfaces TreeNode
oder MutableTreeNode implemen-tieren. Die Klasse DefaultTreeModel enthält
eine universelle Implementierung (inkl. Navigationsmethoden) für Baumstrukturen.
3
vgl. pr13229
8
Algorithmen und Datenstrukturen
1.2 Begriffe und Erläuterungen zu Datenstrukturen und Programmierverfahren
1.2.1 Algorithmus (Verarbeitung von Daten)
1.2.1.1 Definition
Datenorganisation heißt: Daten zweckmäßig so einrichten, daß eine möglichst
effektive Verarbeitung erreicht werden kann. So wurde im einführenden Beispiel die
Bearbeitung rekursiver Strukturen (Bäume) mit Hilfe der Datenstruktur Stapel und
den dazugehörigen Programmierverfahren (PUSH, POP) ermöglicht. Das braucht
aber immer nicht „von Anfang an“ untersucht bzw. implementiert zu werden. Bereits
vorhandenes
Wissen,
z.B.
über
Datenstrukturen
und
dazugehörige
Programmierverfahren, ist zu nutzen. Das Wissen über den Stapel und seine
Programmierung kann allgemein zur Bearbeitung der Rekursion auf einem
Digitalrechner benutzt werden und ist nicht nur auf die Bearbeitung von
Baumstrukturen beschänkt.
Die Programmierverfahren sind durch Algorithmen festgelegt. In der Regel ist das
eine Folge von Anweisungen, die den Lösungsweg einer Aufgabe in endlich vielen
Schritten exakt und vollständig beschreiben.
Datenstruktur und Programmierverfahren bilden (, wie das einführende Beispiel
zeigt,) eine Einheit. Bei der Formulierung des Lösungswegs ist man auf eine
bestimmte Darstellung der Daten festgelegt. Rein gefühlsmäßig könnte man sogar
sagen: Daten gehen den Algorithmen voran. Programmieren führt direkt zum
Denken in Datenstrukturen, um Datenelemente, die zueinander in bestimmten
Beziehungen stehen, zusammenzufassen. Mit Hilfe solcher Datenstrukturen ist es
möglich, sich auf die relevanten Eigenschaften der Umwelt zu konzentrieren und
eigene Modelle zu bilden. Die Leistung des Rechners wird dabei vom reinen
Zahlenrechnen auf das weitaus höhere Niveau der „Verarbeitung von Daten“
angehoben.
1.2.1.2 Effizienz
System-Effizienz und rechnerische Effizienz
Effiziente Algorithmen zeichnen sich aus durch
- schnelle Bearbeitungsfolgen (Systemeffizienz) auf unterschiedliche Rechnersystemen. Hier wird die
Laufzeit der diversen Suchalgorithmen auf dem Rechner (bzw. verschiedene Rechnersysteme)
ermittelt und miteinander verglichen. Die zeitliche Beanspruchung wird über die interne Systemuhr
gemessen und ist abhängig vom Rechnertyp
- Inanspruchnahme von möglichst wenig (Arbeits-) Speicher
- Optimierung wichtiger Leistungsmerkmale, z.B. die Anzahl der Vergleichsbedingungen, die Anzahl
der Iterationen, die Anzahl der Anweisungen (, die der Algorithmus benutzt). Die
Berechnungskriterien bestimmen die sog. rechnerische Komplexität in einer Datensammlung. Man
spricht auch von der rechnerischen Effizienz.
9
Algorithmen und Datenstrukturen
Berechnungsgrundlagen für rechnerische Komplexität
Generell kann man für Algorithmen folgende Grenzfälle bzgl. der Rechenbarkeit
beobachten:
- kombinatorische Explosion
Der Suchprozeß probiert jede mögliche Kombination der für das Problem relevanten Objekte aus.
Suchalgorithmen, die ein derartiges Verhalten zeigen, übersteigen schnell die Grenze der
Rechenbarkeit.
- exponentielle Explosion
Die Rechenbarkeit für solche Suchprozesse nimmt exponentiell, z.B. mit xN für irgendeinen
problemspezifischen Wert x zu.
- polynomiales Verhalten
N
2
Die benötigte Rechenzeit kann durch ein Polynom a N x +...+ a 2 x + a1 x + a0 ausgedrückt werden.
„x“ ist bestimmt durch die zu suchenden problemspezifischen Werte, N beschreibt die Anzahl der
Objekte. Da gegen das erste Glied mit der höchsten Potenz bei größeren Objektzahlen alle anderen
Terme des Ausdrucks vernachlässigt werden können, klassifiziert man das polynomiale Zeitverhalten
nach dieser höchsten Potenz.
Man sagt, ein Verfahren zeigt polynomiales Zeitverhalten O(xN), falls die benötigte Rechenzeit mit
der Nten Potenz der Zahl der zu bearbeitenden Objekte anwächst. Die meisten Algorithmen für
Datenstrukturen bewegen sich in einem schmalen Band rechnerischer Komplexität. So ist ein
Algorithmus von der Ordnung O(1) unabhängig von der Anzahl der Datenelemente in der
Datensammlung. Der Algorithmus läuft unter konstanter Zeit ab, z.B.: Das Suchen des zuletzt in eine
Schlange eingefügten Elements bzw. die Suche des Topelements in einem Stapel.
Ein Algorithmus mit dem Verhalten O(N) ist linear. Zeitlich verhält er sich proportional zur Größe der
Liste.
Bsp.: Das Bestimmen des größten Elements in einer Liste. Es sind N Elemente zu überprüfen, bevor
das Ende der Liste erkannt wird.
Andere Algorithmen zeigen „logarithmisches Verhalten“. Ein solches Verhalten läßt sich beobachten,
falls Teildaten die Größe einer Liste auf die Hälfte, ein Viertel, ein Achtel... reduzieren. Die
Lösungssuche ist dann auf die Teilliste beschränkt. Derartiges Verhalten findet sich bei der
Behandlung binärer Bäume bzw. tritt auf beim „binären Suchen“. Der Algorithmus für binäre Suche
zeigt das Verhalten O(log 2 N ) , Sortieralgorithmen wie der Quicksort und Heapsort besitzen eine
O( N log 2 N ) . Einfache Sortierverfahren (z.B. „Bubble-Sort“)
2
bestehen aus Algorithmen mit einer Komplexität von O( N ) . Sie sind deshalb auch nur für kleine
3
Datenmengen brauchbar. Algorithmen mit kubischem Verhalten O( N ) sind bereits äußerst
rechnerische Komplexität von
langsam (z.B. der Algorithmus von Warshall zur Bearbeitung von Graphen). Ein Algorithmus mit
N
einer Komplexität von O( 2 ) zeigt exponentielle Komplexität. Ein derartiger Algorithmus ist nur für
kleine N auf einem Rechner lauffähig.
N
log 2 N
N log 2 N
N2
N3
2N
2
4
8
16
32
128
1
2
3
4
5
7
2
8
24
64
160
896
4
16
64
256
1024
16384
8
64
512
4096
32768
2097152
4
16
256
65536
4294967296
1024
10
10240
1048576
1073741824
3.4 ⋅ 1038
18
. ⋅ 10 308
65536
16
1048576
4294967296
2.8 ⋅ 1014
-4
4
übertrifft den Darstellungsbereich
10
Algorithmen und Datenstrukturen
1.2.2 Daten und Datenstrukturen
1.2.2.1 Der Begriff Datenstruktur
Betrachtet wird ein Ausschnitt aus der realen Welt, z.B. die Hörer dieser Vorlesung
an einem bestimmten Tag:
Juergen
Josef
Liesel
Maria
........
Regensburg
.........
.........
.........
.........
Bad Hersfeld
.........
.........
.........
.........
13.11.70
........
........
........
........
Friedrich-. Ebertstr. 14
..........
..........
..........
..........
Diese Daten können sich zeitlich ändern, z.B. eine Woche später kann eine
veränderte Zusammensetzung der Zuhörerschaft vorliegen. Es ist aber deutlich
erkennbar: Die Modelldaten entsprechen einem zeitinvarianten Schema:
NAME
WOHNORT
GEBURTSORT
GEB.-DATUM
STRASSE
Diese Feststellung entspricht einem Abstraktionsprozeß und führt zur Datenstruktur.
Sie bestimmt den Rahmen (Schema) für die Beschreibung eines Datenbestandes.
Der Datenbestand ist dann eine Ansammlung von Datenelementen (Knoten), der
Knotentyp ist durch das Schema festgelegt.
Der Wert eines Knoten k ∈ K wird mit wk bezeichnet und ist ein n ≥ 0 -Tupel von
Zeichenfolgen; w i k bezeichnet die i-te Komponente des Knoten. Es gilt
wk = ( w1k , w2 k ,...., wn k )
Die Knotenwerte des vorstehenden Beispiels sind:
wk1 = (Jürgen____,Regensburg,Bad Hersfeld,....__,Ulmenweg__)
wk2 = (Josef_____,Straubing_,......______,....__,........__)
wk3 = (Liesel____,....._____,......______,....__,........__)
..........
wkn = (__________,__________,____________,______,__________)
Welche Operationen sind mit dieser Datenstruktur möglich?
Bei der vorliegenden Tabelle sind z.B. Zugriffsfunktionen zum Einfügen, Löschen
und Ändern eines Tabelleneintrages mögliche Operationen. Generell bestimmen
Datenstrukturen auch die Operationen, die mit diesen Strukturen ausgeführt werden
dürfen.
Zusammenhänge zwischen den Knoten eines Datenbestandes lassen sich mit Hilfe
von Relationen bequem darstellen. Den vorliegenden Datenbestand wird man aus
Verarbeitungsgründen bspw. nach einem bestimmten Merkmal anordnen (Ordnungsrelation). Dafür steht hier (im vorliegenden Beispiel) der Name der Studenten:
11
Algorithmen und Datenstrukturen
Josef
Juergen
Liesel
Abb. 1.2-1: Einfacher Zusammenhang zwischen Knoten eines Datenbestandes
Datenstrukturen bestehen also aus Knoten(den einzelnen Datenobjekten) und
Relationen (Verbindungen). Die Verbindungen bestimmen die Struktur des
Datenbestandes.
Bsp.:
1. An Bayerischen Fachhochschulen sind im Hauptstudium mindestens 2
allgemeinwissenschaftliche Wahlfächer zu absolvieren. Zwischen den einzelnen
Fächern, den Dozenten, die diese Fächer betreuen, und den Studenten bestehen
Verbindungen. Die Objektmengen der Studenten und die der Dozenten ist nach den
Namen sortiert (geordnet). Die Datenstruktur, aus der hervorgeht, welche
Vorlesungen die Studenten bei welchen Dozenten hören, ist:
12
Algorithmen und Datenstrukturen
STUDENT
FACH
DATEN
DOZENT
DATEN
DATEN
DATEN
DATEN
DATEN
DATEN
DATEN
DATEN
DATEN
geordnet
(z.B. nach Matrikelnummern)
geordnet
(z.B. nach Titel
im Vorlesungsverzeichnis)
geordnet
(z.B. nach Namen)
Abb.: 1.2-2: Komplexer Zusammenhang zwischen den Knoten eines Datenbestands
2. Ein Gerät soll sich in folgender Form aus verschiedenen Teilen zusammensetzen:
Anfangszeiger Analyse
Anfangszeiger Vorrat
G1, 5
B2, 4
B1, 3
B3, 2
B4, 1
Abb. 1.2-3: Darstellung der Zusammensetzung eines Geräts
13
Algorithmen und Datenstrukturen
2 Relationen können hier unterschieden werden:
1) Beziehungsverhältnisse eines Knoten zu seinen unmittelbaren Nachfolgeknoten. Die Relation
Analyse beschreibt den Aufbau eines Gerätes
2) Die Relation Vorrat gibt die Knoten mit w2k <= 3 an.
Die Beschreibung eines Geräts erfordert in der Praxis eine weit komplexere
Datenstruktur (größere Knotenzahl, zusätzliche Relationen).
3. Eine Bibliotheksverwaltung soll angeben, welches Buch welcher Student
entliehen hat. Es ist ausreichend, Bücher mit dem Namen des Verfassers (z.B.
„Stroustrup“) und die Entleiher mit ihrem Vornamen (z.B. „Juergen“, „Josef“)
anzugeben. Damit kann die Bibliotheksverwaltung Aussagen, z.B. „Josef hat
Stroustrup ausgeliehen“ oder „Juergen hat Goldberg zurückgegeben“ bzw. Fragen,
z.B. „welche Bücher hat Juergen ausgeliehen?“, realisieren. In die Bibliothek sind
Objekte aufzunehmen, die Bücher repäsentieren, z.B.:
Buch
„Stroustrup“
Weiterhin muß es Objekte geben, die Personen repräsentieren, z.B.:
Person
„Juergen“
Falls „Juergen“ Stroustrup“ ausleiht, ergibt sich folgende Darstellung:
Person
„Juergen“
Buch
„Stroustrup“
Abb. 1.2-4: Objekte und ihre Beziehung in der Bibliotheksverwaltung
Der Pfeil von „Stroustrup“ nach „Juergen“ zeigt: „Juergen“ ist der Entleiher von „Stroustrup“, der Pfeil
von „Juergen“ nach „Stroustrup“ besagt: „Stroustrup“ ist eines der von „Juergen“ entliehenen Bücher.
Für mehrere Personen kann sich folgende Darstellung ergeben:
14
Algorithmen und Datenstrukturen
Person
Person
„Juergen“
Person
„Josef“
Buch
„Stroustrup“
Buch
„Goldberg“
Buch
„Lippman“
Abb. 1.2-5: Objektverknüpfungen in der Bibliotheksverwaltung
Zur Verbindung der Klasse „Person“ bzw. „Buch“ wird eine Verbindungsstruktur benötigt:
Person
buecher =
Verbindungsstruktur
„Juergen“
Buch
„Stroustrup“
Abb.1.2-6: Verbindungsstruktur zwischen den Objekttypen „Person“ und „Buch“
Ein bestimmtes Problem kann auf vielfätige Art in Rechnersystemen abgebildet
werden. So kann das vorliegende Problem über verkettete Listen im Arbeitsspeicher
oder auf Externspeicher (Dateien) realisiert werden.
Die vorliegenden Beispiele können folgendermaßen zusammengefaßt werden:
Die Verkörperung einer Datenstruktur wird durch das Paar D = (K,R) definiert.
K ist die Knotenmenge (Objektmenge) und R ist eine endliche Menge von binären Relationen über
K.
15
Algorithmen und Datenstrukturen
1.2.2.2 Relationen und Ordnungen
Relationen
Zusammenhänge zwischen den Knoten eines Datenbestandes lassen sich mit Hilfe
von Relationen bequem darstellen.
Eine Relation ist bspw. in folgender Form gegeben:
R = {(1,2),(1,3),(2,4),(2,5),(2,6),(3,5),(3,7),(5,7),(6,7)}
Diese Relation bezeichnet eine Menge geordneter Paare oder eine Produktmenge
M × N . Sind M und N also Mengen, dann nennt man jede Teilmenge M × N eine
zweistellige oder binäre Relation über M × N (oder nur über M , wenn M = N ist).
Jede binäre Relation auf einer Produktmenge kann durch einen Graphen dargestellt
werden, z.B.:
1
3
2
5
6
4
7
Abb.: 1.2-7: Ein Graph zur Darstellung einer binären Relation
Bsp.: Gegeben ist S (eine Menge der Studenten) und V (eine Menge von
Vorlesungen). Die Beziehung ist: x ∈ S hört y ∈V . Diese Beziehung kann man
durch die Angabe aller Paare ( x , y ) beschreiben, für die gilt: Student x hört
Vorlesung y . Jedes dieser Paare ist Element des kartesischen Produkts S × V der
Mengen S und V .
Für Relationen sind aus der Mathematik folgende Erklärungen bekannt:
1. Vorgänger und Nachfolger
R ist eine Relation über der Menge M.
Gilt ( a, b) ∈ R , dann sagt man: „a ist Vorgänger von b, b ist Nachfolger von a“.
Zweckmäßigerweise unterscheidet man in diesem Zusammenhang auch den
Definitions- und Bildbereich
Def(R) = { x | ( x , y ) ∈ R }
Bild(R) = { y | ( x , y ) ∈ R }
16
Algorithmen und Datenstrukturen
2. Inverse Relation (Umkehrrelation)
Relationen sind umkehrbar. Die Beziehungen zwischen 2 Grössen x und y können auch als
Beziehung zwischen y und x dargestellt werden, z.B.: Aus „x ist Vater von y“ wird durch Umkehrung
„y ist Sohn von x“.
Allgemein gilt:
R-1 = { (y,x) | ( x , y ) ∈ R }
3. Reflexive Relation
∀ ( x , x ) ∈ R (Für alle Elemente x aus M gilt, x steht in Relation zu x)
x ∈M
Beschreibt man bspw. die Relation "... ist Teiler von ..." für die Menge M = {2,4,6,12} in einem
Grafen, so erhält man:
12
4
6
2
Abb.1.2-8: Die binäre Relation „... ist Teiler von ... “
Alle Pfeile, die von einer bestimmten Zahl ausgehen und wieder auf diese Zahl verweisen, sind
Kennzeichen einer reflexiven Relation ( in der Darstellung sind das Schleifen).
Eine Relation, die nicht reflexiv ist, ist antireflexiv oder irreflexiv.
4. Symmetrische Relation
Aus (( ( x , y ) ∈ R ) folgt auch (( ( y , x ) ∈ R ).
Das läßt sich auch so schreiben: Wenn ein geordnetes Paar (x,y) der Relation R angehört, dann
gehört auch das umgekehrte Paar (y,x) ebenfalls dieser Relation an.
Bsp.:
a) g ist parallel zu h
h ist parallel zu g
b) g ist senkrecht zu h
h ist senkrecht zu g
5. Asymmetrische Relation
Solche Relationen sind auch aus dem täglichen Leben bekannt. Es gilt bspw. „x ist Vater von y“ aber
nicht gleichzeitig „y ist Vater von x“.
Eine binäre Relation ist unter folgenden Bedingungen streng asymetrisch:
17
Algorithmen und Datenstrukturen
∀ ( x , y ) ∈ R → (( y , x ) ∉ R)
( x , y )∈R
Das läßt sich auch so ausdrücken: Gehört das geordnete Paar (x,y) zur Relation, so gehört das
entgegengesetzte Paar (y,x) nicht zur Relation.
Gilt für x <> y die vorstehende Relation und für x = y ∀( x , x ) ∈ R , so wird diese binäre Relation
"unstreng asymmetrisch" oder "antisymmetrisch" genannt.
6. Transitive Relation
Eine binäre Relation ist transitiv, wenn (( ( x , y ) ∈ R ) und (( ( y , z ) ∈ R ) ist, dann ist auch
(( ( x , z ) ∈ R ). x hat also y zur Folge und y hat z zur Folge. Somit hat x auch z zur Folge.
7. Äquivalenzrelation
Eine binäre Relation ist eine Äquivalenzrelation, wenn sie folgenden Eigenschaften entspricht:
- Reflexivität
- Transitivität
- Symmetrie
Bsp.: Die Beziehung "... ist ebenso gross wie ..." ist eine Äquivalenzrelation.
1. Wenn x1 ebenso groß ist wie x2, dann ist x2 ebenso groß wie x1. Die Relation ist symmetrisch.
2. x1 ist ebenso groß wie x1. Die Relation ist reflexiv.
3. Wenn x1 ebenso groß wie x2 und x2 ebenso gross ist wie x3, dann ist x1 ebenso groß wie x3. Die
Relation ist transitiv.
Klasseneinteilung
- Ist eine Äquivalenzrelation R in einer Menge M erklärt, so ist M in Klassen eingeteilt
- Jede Klasse enthält Elemente aus M, die untereinander äquivalent sind
- Die Einteilung in Klassen beruht auf Mengen M1, M2, ... , Mx, ... , My
Für die Teilmengen gilt:
(1) M x ∩ M y = 0
(2) M1 ∪ M 2 ∪....∪ M y = M
(3) Mx <> 0 (keine Teilmenge ist die leere Menge)
Bsp.: Klasseneinteilungen können sein:
- Die Menge der Studenten der FH Regensburg: Äquivalenzrelation "... ist im gleichen Semester wie
..."
- Die Menge aller Einwohner einer Stadt in die Klassen der Einwohner, die in der-selben Straße
wohnen: Äquivalenzrelation ".. wohnt in der gleichen Strasse wie .."
Aufgabe
1. Welche der folgenden Relationen sind transitiv bzw. nicht transitiv?
1)
2)
3)
4)
5)
...
...
...
...
...
ist
ist
ist
ist
ist
der Teiler von ....
der Kamerad von ...
Bruder von ...
deckungsgleich mit ...
senkrecht zu ...
(transitiv)
(transitiv)
(transitiv)
(transitiv)
(nicht transitiv)
2. Welche der folgenden Relationen sind Aequivalenzrelationen?
18
Algorithmen und Datenstrukturen
1)
2)
3)
4)
...
...
...
...
gehört dem gleichen Sportverein an ...
hat denselben Geburtsort wie ...
wohnt in derselben Stadt wie ...
hat diesselbe Anzahl von Söhnen
Ordnungen
1. Halbordnung
Eine binäre Relation ist eine "Halbordnung", wenn sie folgende Eigenschaften
besitzt: "Reflexivität, Transitivität"
2. Strenge Ordnungsrelation
Eine binäre Relation ist eine "strenge Ordnungsrelation", wenn sie folgende Eigenschaft besitzt: "Transitivität, Asymmetrie"
3. Unstrenge Ordnungsrelation
Eine binäre Relation ist eine "unstrenge Ordnungsrelation", wenn sie folgende
Eigenschaften besitzt: Transitivität, unstrenge Asymmetrie
4. Totale Ordnungsrelation und partielle Ordnungsrelation
Tritt in der Ordnungsrelation x vor y auf, so verwendet man das Symbol < (x < y).
Vergleicht man die Abb. 1.2-9, so kann man für (1) schreiben: e < a < b < d und c <
d
Das Element c kann man weder mit den Elementen e, a noch mit b in eine
gemeinsame Ordnung bringen. Daher bezeichnet man diese Ordnungsrelation als
partielle Ordnung (teilweise Ordnung).
Eine totale Ordnungsrelation enthält im Gegensatz dazu Abb. 1.2-9 in (2): e < a <
b<c<d
Kann also jedes Element hinsichtlich aller anderen Elemente geordnet werden, so ist
die Ordnungsrelation eine totale, andernfalls heißt sie partiell.
(1)
(2)
a
a
b
e
e
b
d
c
d
19
c
Algorithmen und Datenstrukturen
Abb. 1.2-9: Totale und partielle Ordnungsrelationen
Natürliche Ordnung im Java Development Kit (JDK)
Im JDK gibt es die Möglichkeit, Elemente z.B. in einem Set, in einer Map5, etc. zu
sortieren. Dabei kann entweder über die natürliche Ordnung, oder die Elemente
können mit Hilfe eines expliziten Vergleichsobjekts sortiert werden.
Für eine natürliche Ordnung muß sichergestellt werden, daß alle Elemente der
Datensammlung (Collection) eine compareTo-Methode besitzen und je zwei
beliebige Element miteinander verglichen werden können, ohne dass es zu einem
Typfehler kommt. Dazu müssen die Elemente das Interface Comparable aus dem
Paket java.lang implementieren. Das Interface Comparable besitzt lediglich eine
einzige Methode zum Vergleich des aktuellen Elements mit einem anderen:
public int compareTo(Object o)
„compareTo“ muß einen Wert kleiner als Null zurückgeben, falls das aktuelle Element vor dem zu
vergleichenden liegt.
„compareTo“ muß den Wert Null zurückgen, falls das aktuelle Element und das zu vergleichende
gleich sind.
„compareTo“ muß einen Wert größer als Null zurückgeben, falls das aktuelle Element hinter dem zu
vergleichenden liegt.
Das Comparable-Interface ist seit dem JDK 1.2 bereits in vielen eingebauten
Klassen implementiert (z.B. String, Character, Double, etc.). Die natürliche Ordnung
ergibt sich durch paarweisen Vergleich aller Elemente. Dabei wird das kleinere vor
das größere Element gestellt.
Die zweite Möglichkeit zum Sortieren von Elementen im JDK besteht darin, an den
Konstruktor der Collection-Klasse ein Objekt des Typs Comparator zu übergeben.
Comparator ist ein Interface, das lediglich eine Einzige Methode dekariert:
public int compare(Object o1, Object o2)
Das übergebene Comparator-Objekt übernimmt die Aufgabe einer Vergleichsfunktion, deren Methode compare immer dann aufgerufen wird, wenn bestimmt
werden muß, in welcher Reihenfolge zwei beliebige Elemente zueinander stehen.
1.2.2.3 Klassifikation von Datenstrukturen
Eine Datenstruktur ist durch Anzahl und Eigenschaften der Relationen bestimmt.
Obwohl sehr viele Relationstypen denkbar sind, gibt es nur 4 fundamentale
Datenstrukturen6, die immer wieder verwendet werden und auf die andere
Datenstrukturen zurückgeführt werden können. Den 4 Datenstrukturen ist
gemeinsam, daß sie nur binäre Relationen verwenden.
1. Lineare Ordnungsgruppen
5
6
Derartige Datensammlungen werden Collections genannt, vgl. 1.3
nach: Rembold, Ulrich (Hrsg.): "Einführung in die Informatik", München/Wien, 1987
20
Algorithmen und Datenstrukturen
Sie sind über eine (oder mehrere) totale Ordnung(en) definiert. Die bekanntesten
Verkörperungen der linearen Ordnung sind:
- (ein- oder mehrdimensionale) Felder (lineare Felder)
- Stapel
- Schlangen
- lineare Listen
Lineare Ordnungsgruppen können sequentiell (seqentiell gespeichert)7 bzw.
verkettet (verkettet gespeichert)8 angeordnet werden.
2. Bäume
Sie sind im wesentlichen durch die Äquivalenzrelation bestimmt.
Bsp.: Gliederung zur Vorlesung Algorithmen und Datenstrukturen
Algorithmen und Datenstrukturen
Kapitel 1: Datenverarbeitung und
Datenorganisation
Abschnitt 1:
Ein einführendes Beispiel
Kapitel 2:
Suchverfahren
Abschnitt 2:
Begriffe
Abb.: 1.2-10: Gliederung zur Vorlesung Datenorganisation
Die Verkörperung dieser Vorlesung ist das vorliegende Skriptum. Diese Skriptum {Seite 1, Seite 2,
..... , Seite n} teilt sich in einzelne Kapitel, diese wiederum gliedern sich in Abschnitte. Die folgenden
Äquivalenzrelationen definieren diesen Baum:
1. Seite i gehört zum gleichen Kapitel wie Seite j
2. Seite i gehört zum gleichen Abschnitt von Kapitel 1 wie Seite j
3. ........
Die Definitionen eines Baums mit Hilfe von Äquivalenzrelationen regelt ausschließlich "Vorgänger/Nachfolger" - Beziehungen (in vertikaler Richtung) zwischen
den Knoten eines Baums. Ein Baum ist auch hinsichtlich der Baumknoten in der
horizontalen Ebene geordnet, wenn zur Definition des Baums neben der
Äquivalenzrelation auch eine partielle Ordnung (Knoten Ki kommt vor Knoten Kj, z.B.
Kapitel1 kommt vor Kapitel 2) eingeführt wird.
3. Graphen
In seiner einfachsten Form besteht eine Verkörperung dieser Datenstruktur aus
einer Knotenmenge K (Objektmenge) und einer festen aber beliebigen Relation R
7
8
vgl. 2.2.1
vgl. 2.2.2
21
Algorithmen und Datenstrukturen
über dieser Menge9. Die folgende Darstellung zeigt einen Netzplan zur Ermittlung
des kritischen Wegs:
Die einzelnen Knoten des Graphen sind Anfangs- und Endereignispunkte der Tätigkeiten, die an den
Kanten angegeben sind. Die Kanten (Pfeile) beschreiben die Vorgangsdauer und sind Abbildungen
binärer Relationen. Zwischen den Knoten liegt eine partielle Ordnungsrelation10.
Bestelle A
50 Tage
Baue B
Teste B
4
25 Tage
1
20 Tage
Korrigiere Fehler
2
15 Tage
3
Handbucherstellung
60 Tage
Abb. 1.2-11: Ein Graph der Netzplantechnik
4. Dateien
Damit ist eine Datenstruktur bestimmt, bei der Verbindungen zwischen den
Datenobjekten durch beliebige, binäre Relationen beschrieben werden. Die Anzahl
der Relationen ist somit im Gegensatz zur Datenstruktur Graph nicht auf eine
beschränkt. Verkörperungen solcher assoziativer Datenstrukturen sind vor allem
Dateien. In der Praxis wird statt mehrere binärer Relationen eine n-stellige Relation
(Datensatz) gespeichert. Eine Datei ist dann eine Sammlung von Datensätzen
gleichen Typs.
Bsp.: Studenten-Datei
Sie faßt die relevanten Daten der Studenten11 nach einem ganz bestimmten Schema zusammen.
Ein solches Schema beschreibt einen Datensatz. Alle Datensätze, die nach diesem Schema
aufgestellt werden, ergeben die Studenten-Datei. Es sind binäre Relationen (Student - Wohnort,
Student - Geburtsort, ...), die aus Speicheraufwandsgründen zu einer n-stelligen Relation (bezogen
auf eine Datei) zusammengefaßt werden.
5. Datenbanken
Eine Datenbank ist die Sammlung von verschiedenen Datensatz-Typen. Die
Datensätze sind in einer Codasyl-Datenbank12 untereinander verbunden, z.B. alle
Studenten im Fachbereich „Informatik und Mathematik“ der Fachhochschule
Regensburg, z.B.:
Fachbereich Informatik
9
vgl. 1.2.2.2, Abb. 1.2-7
vgl. 3.4.2
11 vgl. 1.2.2.1
12 Datenbank der Data Base Task Group der Conference on Data Systems Languages (CODASYL)
10
22
Algorithmen und Datenstrukturen
Student_1
Student_2
....
Student_n
Abb.: 1.2-12: Erscheinungsbild der Datensätze „Fachbereich“ und „Student“
Der letzte Studentensatz zeigt auf den Satz „Fachbereich Informatik und
Mathematik“ zurück. Diese detaillierte Darstellung der physischen Struktur kann auf
folgende Beschreibung der logischen Datenstruktur zurückgeführt werden:
Fachbereich
betreut
Student
Abb.: 1.2-13: Logische Datenstruktur
Auch hier zeigt sich: Knoten bzw. Knotentypen und ihre Beziehungen bzw.
Beziehungstypen stehen im Mittelpunkt der Datenbank-Beschreibung. Statt Knoten
spricht man hier von Entitäten (bzw. Entitätstypen) und Beziehungen werden
Relationen genannt. Dies ist dann Basis für den Entity-Relationship (ER) -Ansatz
von P.S. Chen. Zur Beschreibung der Entitätstypen und ihrer Beziehungen benutzt
der ER-Ansatz in einem ER-Diagramm rechteckige Kanten bzw. Rauten:
23
Algorithmen und Datenstrukturen
Fachbereich
1
betreut
M
Student
Abb. 1.2-14: „ER“-Diagramm zur Darstellung der Beziehung „Fachbereich-Student“
Die als „1“ und „M“ an den Kanten aufgeschriebenen Zahlen zeigen: Ein Fachbereich betreut mehrere (viele) Studenten. Solche Beziehungen können vom Typ
1:M, 1:1, M:N sein. Es ist auch die Bezugnahme auf gleiche Entitätstypen möglich,
z.B.:
Person
1
1
Heirat
Abb.: 1.2-15: Bezugnahme auf den gleiche Entitätstyp „Person“
Die folgende Darstellung einer Datenbank in einem ER-Diagramm
Abt_ID
Bezeichnung
Job_ID
Titel
Abteilung
Gehalt
Job
Abt-Ang
Job-Ang
Qualifikation
Angestellte
Ang_ID
Name
GebJahr
Abb. 1.2-16: ER-Diagramm zur Datenbank Personalverwaltung
führt zum folgenden Schemaentwurf einer relationalen Datenbank
- Abteilung(Abt_ID,Bezeichnung)
- Angestellte(Ang_ID,Name,Gebjahr,Abt_ID,Job_ID)
- Job(Job_ID,Titel,Gehalt)
24
Algorithmen und Datenstrukturen
- Qualifikation(Ang_ID,Job_ID)
und resultiert in folgender relationalen Datnbank für das Personalwesen
ABTEILUNG
ABT_ID
KO
OD
PA
RZ
VT
BEZEICHNUNG
Konstruktion
Organisation und Datenverarbeitung
Personalabteilung
Rechenzentrum
Vertrieb
ANGESTELLTE
ANG_ID
A1
A2
A3
A4
A5
A6
A7
A8
A9
A10
A11
A12
A13
A14
NAME
Fritz
Tom
Werner
Gerd
Emil
Uwe
Eva
Rita
Ute
Willi
Erna
Anton
Josef
Maria
GEBJAHR
2.1.1950
2.3.1951
23.4.1948
3.11.1950
2.3.1960
3.4.1952
17.11.1955
02.12.1957
08.09.1962
7.7.1956
13.10.1966
5.7.1948
2.8.1952
17.09.1964
JOB_ID
KA
TA
SY
PR
OP
TITEL
Kaufm. Angestellter
Techn. Angestellter
Systemplaner
Programmierer
Operateur
ABT_ID
OD
KO
OD
VT
PA
RZ
KO
KO
OD
KO
OD
OD
KO
PA
JOB_ID
SY
IN
PR
KA
PR
OP
TA
TA
SY
IN
KA
SY
SY
KA
JOB
GEHALT
3000,00 DM
3000,00 DM
6000,00 DM
5000,00 DM
3500,00 DM
QUALIFIKATION
ANG_ID
A1
A1
A1
A2
A2
A3
A4
A5
A6
A7
A8
A9
A10
A11
A12
A13
A14
JOB_ID
SY
PR
OP
IN
SY
PR
KA
PR
OP
TA
IN
SY
IN
KA
SY
IN
KA
25
Algorithmen und Datenstrukturen
Abb. 1.2-17: Tabellen zur relationalen Datenbank
Die relationale Datenbank besteht, wie die vorliegende Darstellung zeigt aus einer
Datenstruktur, die Dateien (Tabellen) vernetzt. Die Verbindung besorgen
Referenzen (Fremdschlüssel).
Die vorliegende Einteilung der Datenstrukturen zeigt: Sammeln und Ordnen der
durch die reale Welt vorgegebenen Objekte ist eine der wichtigsten und häufigsten
Anwendungen in der Datenverarbeitung. Leider unterstützen herkömmliche
Programmiersprachen nicht umfassend genug diese Möglichkeiten. Erst die
objektorientierte Programmierung (vor allem Smalltalk) haben hier den Ansatz zu
einer umfassenden Implementierung (Collection, Container) gezeigt.
1.2.3 Definitionsmethoden für Datenstrukturen
1.2.3.1 Der abstrakte Datentyp
Die Definition einer Datenstruktur ist bestimmt durch Daten (Datenfelder, Aufbau,
Wertebereiche) und die für die Daten gültigen Rechenvorschriften (Algorithmen,
Operationen). Datenfelder und Algorithmen bilden einen Typ, den abstrakten
Datentyp (ADT). Der ADT ist eine Kapsel, der die gemeinsame Deklaration von
Daten und Algorithmen zu einem Typ zusammenfaßt. Datenkapseln sind
elementare Bausteine, die den konventionellen Programmierstil (Programm =
Algorithmus + Daten) auf ein anspruchvolleres Niveau anheben. Die Datenkapsel
betrachtet Daten und Rechenvorschrift als eine Einheit. Das bedeutet aber auch:
Für die Ausführung einer Aufgabe ist die Datenkapsel selbst verantwortlich. Der
Anwender hat damit nichts zu tun. Er teilt dem durch die Datenkapsel bestimmten
Objekt lediglich über eine Botschaft mit, daß er eine spezielle Funktion ausgeführt
haben möchte. Das Empfangsobjekt wählt daraufhin eine ihm bekannte Methode aus
und führt die dazugehörige Prozedur aus. Das Ergebnis der Methode wird vom
Objekt an den den Sender der Botschaft wieder zurückgeschickt. Die durch die
Datenkapsel realisierte Einheit hat einen speziellen Namen: Objekt. Den
zugehörigen Programmierstil nennt man: objektorientierte Programmierung.
Die objektorientierte Sichtweise faßt Daten, Prozeduren und Funktionen zu
möglichst realistischen Modellen (Objekten) der Wirklichkeit zusammen. Zugriff auf
objektorientierte Modelle ist nur den Methoden (Prozeduren und Funktionen) erlaubt.
Eine Methode gehört zu einem Objekt mit dem Zweck die Daten des Oblekts zu
bearbeiten. Nachrichen (Botschaften) sind neben den Objekten das 2. wesentliche
Element in objektorientierten Programmiersprachen. Objekte machen nur dann
etwas, wenn sie eine Nachricht empfangen und für diese Nachricht eine Methode
haben. Andernfalls geben sie die Nachricht an die Klasse weiter, der das Objekt
angehört. Klassen (Objekttypen) sind Realisierungen abstrakter Datentypen und
umfassen: Attribute (Eigenschaften) , Methoden, Axiome. Sie beschreiben Zustand
und Verhalten gleichartiger Objekte.
Generell gilt: Ein neues Objekt (Instanz, Exemplar) einer Klasse erbt alle
Eigenschaften der Klasse. Man kann aber diesem Erbe Eigenschaften hinzufügen
26
Algorithmen und Datenstrukturen
bzw. Methoden streichen bzw. modifizieren. Falls der Erbe selbst Nachkommen
erhält, dann geschieht folgendes:
1. Die Instanz wird zur Klasse
2. Die Nachkommen erben die Eigenschaftten der Vorfahren
Jede Klasse in einem objektorientierten Programmiersystem (OOP) hat einen
Vorfahren
Eine unmittelbare Implementierung der objektorientierten Programmierung (und
damit von ADT) gibt es erst seit 1981 (Smalltalk-80)13. In der Praxis ist dieser
Programmierstil erst seit 1980 verbreitet. Aktuell ist die objektorientierte
Programmierung vor allem durch die inzwischen weit bekannte Programmiersprache
C++ bzw. Java.
Daten und Algorithmen als Einheit zu sehen, war bereits schon vor 1980 bekannt.
Da damals noch keine allgemein einsetzbare Implementierung vorlag, hat man
Methoden zur Deklaration von ADT14 bereitgestellt. Damit sollte dem Programmierer
wenigstens durch die Spezifikation die Einheit von Daten und zugehörigen
Operationen vermittelt werden.
1.2.3.2 Die axiomatische Methode
Die axiomatische Methode beschreibt abstrakte (Daten-)Typen über die Angabe
einer Menge von Operationen und deren Eigenschaften, die in der Form von
Axiomen präzisiert werden. Problematisch ist jedoch: Die Axiomenmenge ist so
anzugeben, daß Widerspruchsfreiheit, Vollständigkeit und möglichst Eindeutigkeit
erzielt wird.
Eine spezielle axiomatische Methode ist die algebraische Spezifikation von Datenstrukturen. Sie soll hier stellvertretend für axiomatische Definitionsmethoden an
einem Beispiel vorgestellt werden.
Bsp.: Der abstrakte Datentyp (ADT) Schlange
Konventionell würde die Datenstruktur Schlange so definiert werden: Eine Schlange
ist ein lineares Feld, bei dem nur am Anfang Knoten entfernt und nur am Ende
Knoten hinzugefügt werden können. Die Definition ist ungenau. Operationen sollten
mathematisch exakt als Funktionen und Beziehungen der Operationen als
Gleichungen angegeben sein. Erst dann ist die Prüfung auf Konsistenz und der
Nachweis der korrekten Implementierung möglich. Die algebraische Spezifikation
bestimmt den ADT Schlange deshalb folgendermaßen:
13
14
vgl. BYTE, Heft August 1981
vgl. Guttag, John: "Abstract Data Types and the Development of Data Structures", CACM, June 1977
27
Algorithmen und Datenstrukturen
ADT Schlange
Typen
Schlange<T>, boolean
Funktionen (Protokoll)
NeueSchlange
FuegeHinzu :
Vorn
:
Entferne
:
Leer
:
→ Schlange<T>
T,Schlange<T> → Schlange<T>
Schlange<T>
→ T
Schlange<T> → Schlange<T>
Schlange<T>
→ boolean
Axiome
Für alle t : T bzw. s : Schlange<T> gilt:
1.
2.
3.
4.
5.
6.
Leer(NeueSchlange) = wahr
Leer(FuegeHinzu(t,s)) = falsch
Vorn(NeueSchlange) = Fehler
Vorn(FuegeHinzu(t,s)) = Wenn Leer(s), dann t; andernfalls Vorn(s)
Entferne(NeueSchlange) = Fehler
Entferne(FuegeHinzu(t,s)) = Wenn Leer(s), dann NeueSchlange; andernfalls
FuegeHinzu(t,Entferne(s))
Der Abschnitt Typen zeigt die Datentypen der Spezifikation. Der 1. Typ ist der
spezifizierte ADT. Von anderen Typen wird angenommen, daß sie an anderer Stelle
definiert sind. Der Typ „Schlange<T>“ wird als generischer ADT bezeichnet, da er
"übliche Schlangeneigenschaften" bereitstellt. Eigentliche Schlangentypen erhält
man durch die Vereinbarung eines Exemplars des ADT, z.B.: Schlange<integer>
Der Abschnitt Funktionen zeigt die auf Exemplare des Typs anwendbaren
Funktionen: f : D1 , D2 ,...., Dn → D . Einer der Datentypen D1 , D2 ,...., Dn oder D muß
der spezifizierte ADT sein.
Die Funktionen können eingeteilt werden in:
- Konstruktoren (constructor functions)
(Der ADT erscheint nur auf der rechten Seite des Pfeils.) Sie liefern neue Elemente (Instanzen) des
ADT.
- Zugriffsfunktionen (accessor functions)
(Der ADT erscheint nur auf der linken Seite des Pfeils.) Sie liefern Eigenschaften von existierenden
Elementen des Typs (vgl. Die Funktion: Leer)
- Umsetzungsfunktionen (transformer functions)
(Der ADT erscheint links und rechts vom Pfeil.) Sie bilden neue Elemente des ADT aus
bestehenden Elementen und (möglicherweise) anderen Argumenten (vgl. FuegeHinzu,
Entferne).
Der Abschnitt Axiome beschreibt die dynamischen Eigenschaften des ADT.
Aufgabe: Entwickle die „algebraische Spezifikation“ des ADT Stapel
ADT Stapel<T>, integer, boolean
1. Funktionen (Operationen, Protokoll)
NeuerStapel
PUSH
POP
→ Stapel<T>
: T,Stapel<T> → Stapel<T>
: Stapel<T>
→ Stapel<T>
28
Algorithmen und Datenstrukturen
Top
Stapeltiefe
Leer
: Stapel<T>
: Stapel<T>
: Stapel<T>
→ T
→ integer
→ boolean
2. Axiome
Für alle t:T und s:Stapel<T> gilt:
(POP(PUSH(t,s)) = s
Top(PUSH(t,s)) = t
Stapeltiefe(NeuerStapel) = 0
Stapeltiefe(PUSH(i,s)) = Stapeltiefe + 1
Leer(NeuerStapel) = wahr
¬ Leer(PUSH(t,s) = wahr
3. Restriktionen (conditional axioms)
Wenn Stapeltiefe(s) = 0, dann führt POP(s) auf einen Fehler
Wenn Stapeltiefe(s) = 0, dann ist Top(s) undefiniert
Wenn Leer(s) = wahr, dann ist Stapeltiefe(s) Null.
Wenn Stapeltiefe(s) = 0, dann ist Leer(s) wahr.
Für viele Programmierer ist eine solche Spezifikationsmethode zu abstrakt. Die
Angabe von Axiomen, die widerspruchsfrei und vollständig ist, ist nicht möglich bzw.
nicht nachvollziehbar. Der direkte Weg zur Deklaration von ADT im Rahmen der
objektorientierten Programmierung ist noch nicht weit berbreitet. Der konventionelle
Programmierstil, Daten und Algorithmen getrennt zu behandeln, bevorzugt den
konstruktiven Aufbau der Daten aus elementaren Datentypen.
1.2.3.3 Die konstruktive Methode
Die Basis bilden hier die Datentypen. Jedem Objekt ist eine Typvereinbarung in der
folgenden Form zugeordnet: X : T;
X ... Bezeichner (Identifizierer) für ein Objekt
T ... Bezeichner (Identifizierer) für einen Datentyp
Einem Datentyp sind folgende Eigenschaften zugeordnet:
1. Ein Datentyp bestimmt die Wertmenge, zu der eine Konstante gehört oder die durch eine Variable
oder durch einen Ausdruck angenommen werden kann oder die durch einen Operator oder durch
eine Funktion berechnet werden kann.
2. Jeder Operator oder jede Funktion erwartet Argumente eines bestimmten Typs und liefert
Resultate eines bestimmten Typs.
Bei der konstruktiven Methode erfolgt die Definition von Datenstrukturen mit Hilfe
bereits eingeführter Datentypen. Die niedrigste Stufe bilden die einfachen
Datentypen. Basis-Datentypen werden in den meisten Programmiersprachen zur
Verfügung gestellt und sind eng mit dem physikalischen Wertevorrat einer DVAnlage verknüpft (Standard-Typen). Sie sind die „Atome“ auf der niedrigsten
Betrachtungsebene. Neue "höherwertige" Datentypen werden aus bereits definierten
„niederstufigen“ Datentypen definiert.
29
Algorithmen und Datenstrukturen
1.2.3.4 Die objektorientierte Modellierung abstrakter Datentypen
Die Spezifikation abstrakter Datentypen
Im Mittelpunkt dieser Methode steht die Definition von Struktur und Wertebereich
der Daten bzw. eine Sammlung von Operationen mit Zugriff auf die Daten. Jede
Aufgabe aus der Datenverarbeitung läßt sich auf ein solches Schema
(Datenabstraktion) zurückführen.
Zur Beschreibung des ADT dient das folgende Format:
ADT Name
Daten
Beschreibung der Datenstruktur
Operationen
Konstruktor
Intialisierungswerte: Daten zur Initialisierung des
Objekts
Verarbeitung: Initialisierung des Objekts
Operation1
Eingabe: Daten der Anwendung dieser Methode
Vorbedingung: Notwendiger Zustand des Systems vor
Ausführung einer Operation
Verarbeitung: Aktionen, die an den Daten ausgeführt
werden
Ausgabe: Daten (Rückgabewerte) an die Anwendung dieser
Methode
Nachbedingung: Zustand des Systems nach Ausführung der
Operation
Operation2
.........
Operationn
.........
Bsp.: Anwendung dieser Vorlage zur Beschreibung des ADT Stapel
ADT Stapel
Daten
Eine Liste von Datenelementen mit einer Position „top“, sie auf den
Anfang des Stapels verweist.
Operationen
Konstruktor:
Initialisierungswerte: keine
Verarbeitung: Initialisiere „top“.
Push
Eingabe: Ein Datenelement zur Aufnahme in den Stapel
Vorbedingung: keine
Verarbeitung: Speichere das Datenelement am Anfang
(„top“) des Stapel
Ausgabe: keine
Nachbedingung: Der Stapel hat ein neues Datenelement
an der Spitze („top“).
Pop
Eingabe: keine
Vorbedingung: Der Stapel ist nicht leer
Verarbeitung: Das Element an der Spitze („top“) wird entfernt.
Ausgabe: keine
Peek bzw. Top
Eingabe: keine
30
Algorithmen und Datenstrukturen
Vorbedingung: Stapel ist nicht leer
Verarbeitung: Bestimme den Wert des Datenelements an der Spitze („top“
des Stapel.
Ausgabe: Rückgabe des Datenwerts, der an der Spitze
(„top“) des Stapel steht.
Nachbedingung: Der Stapel bleibt unverändert.
Leer
Eingabe: keine
Vorbedingung: keine
Verarbeitung: Prüfe, ob der Stapel leer ist.
Ausgabe: Gib TRUE zurueck, falls der Stapel leer ist; andernfalls FALSE.
Nachbedingung: keine
bereinigeStapel
Eingabe: keine
Vorbedingung: keine
Verarbeitung: Löscht alle Elemente im Stapel und setzt die Spitze
(„top“) des Stapels zurück.
Ausgabe: keine
Klassendiagramme der Unified Modelling Language
Visualisierung und Spezifizierung objektorientierter Softwaresysteme erfolgt mit der
Unified Modelling Language (UML). Zur Beschreibung abstrakter Dytentypen dient
das wichtigste Diagramm der UML: Das Klassendiagramm.
Elemente und Darstellung des Klassendiagramms
Das Klassendiagramm beschreibt die statische Struktur der Objekte in einem
System sowie ihre Beziehungen untereinander. Die Klasse ist das zentrale Element.
Klassen werden durch Rechtecke dargestellt, die entweder den Namen der Klasse
tragen oder zusätzlich auch Attribute und Operationen. Klassenname, Attribute,
Operationen (Methoden) sind jeweils durch eine horizontale Linie getrennt.
Klassennamen beginnen mit Großbuchstaben und sind Substantive im Singular.
Ein strenge visuelle Unterscheidung zwischen Klassen und Objekten entfällt in der
UML. Objekte werden von den Klassen dadurch unterschieden, daß ihre
Bezeichnung unterstrichen ist. Haufig wird auch dem Bezeichner eines Objekts ein
Doppelpunkt vorangestellt.. Auch können Klassen und Objekte zusammen im
Klassendiagramm auftreten.
Klasse
Objekt
Wenn man die Objekt-Klassen-Beziehung (Exemplarbeziehung, Instanzbeziehung)
darstellen möchte, wird zwischen einem Objekt und seiner Klasse ein gestrichelter
Pfeil in Richtung Klasse gezeichnet:
Klasse
Objekt
Defintion einer Klasse
Die Definition einer Klasse umfaßt die „bedeutsamen“ Eigenschaften. Das sind:
31
Algorithmen und Datenstrukturen
- Attribute
d.h.: die Struktur der Objekte: ihre Bestandteile und die in ihnen enthaltenen Informationen und
Daten.. Abhängig von der Detaillierung im Diagramm kann die Notation für ein Attribut den
Attributnamen, den Typ und den voreingestellten Wert zeigen:
Sichtbarkeit Name: Typ = voreingestellter Wert
- Operationen
d.h.: das Verhalten der Objekte. Manchmal wird auch von Services oder Methoden gesprochen. Das
Verhalten eines Objekts wird beschrieben durch die möglichen Nachrichten, die es verstehen kann.
Zu jeder Nachricht benötigt das Objekt entsprechende Operationen. Die UML-Syntax für
Operationen ist:
Sichtbarkeit Name (Parameterliste) : Rückgabetypausdruck (Eigenschaften)
Sichtbarkeit ist + (öffentlich), # (geschützt) oder – (privat)
Name ist eine Zeichenkette
Parameterliste enthält optional Argumente, deren Syntax dieselbe wie für Attribute ist
Rückgabetypausdruck ist eine optionale, sprachabhängige Spezifikation
Eigenschaften zeigt Eigenschaftswerte (über String) an, die für die Operation Anwendung finden
- Zusicherungen
Die Bedingungen, Voraussetzungen und Regeln, die die Objekte erfüllen müssen, werden
Zusicherungen genannt. UML definiert keine strikte Syntax für die Beschreibung von Bedingungen.
Sie müssen nur in geschweifte Klammern ({}) gesetzt werden.
Idealerweise sollten Regeln als Zusicherungen (engl. assertions) in der Programmiersprache
implementiert werden können.
Attribute werden mindestens mit ihrem Namen aufgeführt und können zusätzliche
Angaben zu ihrem Typ (d.h. ihrer Klasse), einen Initialwert und evtl.
Eigenschaftswerte und Zusicherungen enthalten. Attribute bilden den Datenbestand
einer Klasse.
Operationen (Methoden) werden mindestens mit ihrem Namen, zusätzlich durch ihre
möglichen Parameter, deren Klasse und Initialwerte sowie evtl. Eigenschaftswerte
und Zusicherungen notiert. Methoden sind die aus anderen Sprachen bekannten
Funktionen.
Klassenname
attribut:Typ=initialerWert
operation(argumentenliste):rückgabetyp
32
Algorithmen und Datenstrukturen
Bsp.: Die Klasse Object aus dem Paket java.lang
Object
+equals(obj :Object)
#finalize()
+toString()
+getClass()
#clone()
+wait()
+notify()
........
Sämtliche Java-Klassen bilden eine Hierarchie mit java.lang.Object als
gemeinsame Basisklasse.
Assoziationen
Assoziationen repräsentieren Beziehungen zwischen Instanzen von Klassen.
Mögliche Assoziationen sind:
- einfache (benannte) Assoziationen
- Assoziation mit angefügten Attributen oder Klassen
- Qualifzierte Assoziationen
- Aggregationen
- Assoziationen zwischen drei oder mehr Elementen
- Navigationsassoziationen
- Vererbung
Attribute werden von Assoziationen unterschieden:
Assoziation: Beschreibt Beziehungen, bei denen beteiligte Klassen und Objekte von anderen Klassen
und Objekten benutzt werden können.
Attribut: Beschreibt einen privaten Bestandteil einer Klasse oder eines Objekts, welcher von außen
nicht sichtbar bzw. modifizierbar ist.
Grafisch wird eine Assoziation als durchgezogene Line wiedergegeben, die gerichtet
sein kann, manchmal eine Beschriftung besitzt und oft noch weitere Details wie z.B.
Muliplizität (Kardinalität) oder Rollenanmen enthält, z.B.:
Arbeitet für
0..1
Arbeitgeber
Arbeitnehmer
Eine Assoziation kann einen Namen zur Beschreibung der Natur der Beziehung
(„Arbeitet für“) besitzen. Damit die Bedeutung unzweideutig ist, kann man dem
Namen eine Richtung zuweisen: Ein Dreieck zeigt in die Richtung, in der der Name
gelesen werden soll.
Rollen („Arbeitgeber, Arbeitnehmer) sind Namen für Klassen in einer Relation. Eine
Rolle ist die Seite, die die Klasse an einem Ende der Assoziation der Klasse am
33
Algorithmen und Datenstrukturen
anderen Ende der Assoziation zukehrt. Die Navigierbarkeit kann durch einen Pfeil in
Richtung einer Rolle angezeigt werden.
Rolle1
Rolle2
K1
K2
1
0..*
Abb.: Binäre Relation R = C1 x C2
Rolle1
K1
K2
Rollen
...
Kn
Abb.: n-äre Relation K1 x K2 x ... x Kn
In vielen Situationen ist es wichtig anzugeben, wie viele Objekte in der Instanz einer
Assoziation miteinander zusammenhänen können. Die Frage „Wie viele?“
bezeichnet man als Multiplizität der Rolle einer Assoziation. Gibt man an einem
Ende der Assoziation eine Multiplizität an, dann spezifiziert man dadurch: Für jedes
Objekt am entgegengesetzten Ende der Assoziation muß die angegebene Anzahl
von Objekten vorhanden sein.
Ein A ist immer Ein A ist immer Ein A ist mit
oder
mit
einem
B mit einem oder keinem
mehre-ren
B einem B assoassoziiert
ziiert
assoziiert
Ein A ist mit keinem, einem oder
mehreren B assoziiert
Unified
A
1 B
A
1..*
B
A
0..1 B
A
*
B
1:1
1..*
1:1..n
0..*
2..6
0..*
*
17
4
n
m
0..n:2..6
0..n:0..n
17:4
?
Abb.: Kardinalitäten für Beziehungen
Pfeile in Klassendiagrammen zeigen Navigierbarkeit an. So kann in der folgenden
Darstellung
34
Algorithmen und Datenstrukturen
Wenn die Navigierbarkeit nur in einer Richtung existiert, nennt man die Assoziation
eine gerichtete Assoziation (uni-directional association). Eine ungerichtete
(bidirektionale) Assoziation enthält Navigierbarkeiten in beiden Richtungen. In UML
bedeuten Assoziationen ohne Pfeile, daß die Navigierbarbeit unbekannt oder die
Assoziation ungerichtet ist.
Ungerichtete Assoziationen enthalten eine zusätzliche Bedingung: Die zu dieser
Assoziation zugehörigen zwei Rollen sind zueinander invers.
Eine Aggregation ist eine Sonderform der Assoziation. Sie repräsentiert eine
(strukturelle) Ganzes/Teil-Beziehung. Zusätzlich zu einfacher Aggregation bietet
UML eine stärkere Art der Aggregation, die Komposition genannt wird. Bei der
Komposition darf ein Teil-Objekt nur zu genau einem Ganzen gehören.
Teil
Ganzes
Existenzabhängiges Teil
Abb.: Aggregation und Komposition
Eine Aggregation wird durch eine Raute dargestellt. Die Komposition wird durch eine
ausgefüllte Raute dargestellt und beschreibt ein „physikalisches Enthaltensein“.
Die Vererbung (Spezialisierung bzw. Generalisierung) stellt eine Verallgemeinerung
von Eigenschaften dar. Eine Generalisierung (generalization) ist eine Beziehung
zwischen dem Allgemeinen und dem Speziellen, in der Objekte des speziellen Typs
(der Subklasse) durch Elemente des allgemeinen Typs (der Oberklassse) ersetzt
werden können. Grafisch wird eine Generalisierung als durchgezogene Linle mit
einer unausgefüllten, auf die Oberklasse zeigenden Pfeilspitze wiedergegeben, z.B.:
Supertyp
Subtyp 1
Subtyp 2
35
Algorithmen und Datenstrukturen
Bsp.: Vererbungshierarchie und wichtige Methoden der Klasse Applet
Panel
Applet
+init()
+start()
+paint(g:Graphics) {geerbt}
+update(g:Graphics) {geerbt}
+repaint()
+stop()
+destroy()
+getParameter(name:String);
+getParameterInfo()
+getAppletInfo()
Schnittstellen und abstrakte Klassen
Programmiersprachen benutzen ein einzelnes Konstrukt, die Klasse, die sowohl
Schnittstelle als die Implementierung enthält. Bei der Bildung einer Unterklasse wird
beides vererbt. Eine reine Schnittstelle (wie bspw. in Java) ist eine Klasse ohne
Implementierung und besitzt daher nur Operationsdeklarationen. Schnittstellen
werden oft mit Hilfe abstrakter Klassen deklariert.
Bei abstrakten Klassen oder Methoden wird der Name des abstrakten Gegenstands
in der UML kursiv geschrieben. Ebenso kann man die Bedingung {abstract}
benutzen.
<<interface>>
InputStream
DataInput
OrderReader
{abstract}
Abhängigkeit
Generalisierung
Verfeinerung
DataInputStream
Abb.: Schnittstellen und abstrakte Klassen: Ein Beispiel aus Java
Irgendeine Klasse, z.B. „OrderReader“ benötigt die DataInput-Funktionalität. Die
Klasse DataInputStream implementiert DataInput und InputStream. Die Verbindung
zwischen DataInputStream und DataInput ist eine „Verfeinerung (refinement)“. Eine
Verfeinerung ist in UML ein allgemeiner Begriff zur Anzeige eines höheren
Detaillierungsniveaus.
Die Objektbeziehung zwischen OrderReader und DataInput ist eine Abhängigkeit.
Sie zeigt, daß „OrderReader“ die Schnittstelle „DataInput für einige Zwecke benutzt.
36
Algorithmen und Datenstrukturen
Abstrakte Klassen und Schnittstellen ermöglichen die Definition einer Schnittstelle
und das Verschieben ihrer Implementierung auf später. Jedoch kann die abstrakte
Klasse schon die Implementierung einiger Methoden enthalten, während die
Schnittstelle die Verschiebung der Definition aller Methoden erzwingt.
1.2.3.5 Die Implementierung abstrakter Datentypen
C++-Implementierung
Klassen (Objekttypen) sind Realisierungen abstrakter Datentypen und umfassen:
Daten und Methoden (Operationen auf den Daten). Das C++-Klassenkonzept
(definiert über struct, union, class) stellt ein universell einsetzbares Werkzeug für
die Erzeugung neuer Datentypen (, die so bequem wie eingebaute Typen eingesetzt
werden können) zur Verfügung. Zu einer Klasse gehören Daten- und
Verarbeitungselemente (d.h. Funktionen). Bestandteile einer Klasse können dem
allgemeinen Zugriff entzogen sein (information hiding). Der Programmentwickler
bestimmt die Sichtbarkeit eines jeden Elements. Einer Klasse ist ein Name (TypBezeichner) zugeordnet. Dieser Typ-Bezeichner kann zur Deklaration von Instanzen
oder Objekten des Klassentyps verwendet werden.
Java-Implementierung
In Java gibt es nur Klassen und Interfaces.
37
Algorithmen und Datenstrukturen
1.3 Sammlungen mit bzw. ohne Ordnungen
1.3.1 Ausgangspunkt: Das Konzept für Sammlungen in Smalltalk
Ein Rückblick auf Datentypen prozeduraler Programmiersprachen zeigt: Was hier
angeboten wird, reicht bei weitem nicht aus. Komplexe Ordnungsgruppen15 bzw.
Sammlungen werden überhaupt nicht angeboten. Vorliegende Implementierungen
zeigen nicht die gewünschte Flexibilität. Selbst im Fall des varianten "record"16 muß
bereits dem Compiler bekannt sein, welche Möglichkeiten (unterschiedliche
Satzteile) es überhaupt gibt. Gefragt ist aber, wie man verschiedenartige, verwandte
Definitionen in einer Datenstruktur unterbringt, die auch neue bisher noch nicht
verwendete Strukturen berücksichtigen kann.
Gefordert ist eine allgemeines Konzept zum Sammeln und Ordnen von Objekten
(Behälter, Container, Collection). Der Entwurf solcher Konzepte bzw.
Containerklassen ist Gegenstand objektorientierter Programmiersprachen.
Ein einziger Containertyp, der allen Anforderungen gerecht wird, wäre sicherlich die
beste Lösung. Dem stehen verschiedene Schwierigkeiten entgegen:
- Gegensätzliche Ordnungskriterien (z.B. in Schlangen, Stapeln)
- Unterschiedliche Forderungen (z.B. bzgl. des Begriffs "Enthalten" in einer Menge (set) oder in einer
Sammlung (bag)
- Identifikation der Objekte über Wert oder Schlüssel oder Position (Index)
- Unterschiedliche Anforderungen der Zugriffs-Effizienz (z.B. wahlfrei-berechenbar, mengenmäßig
eingeschränkt, Zugriff über "keys" in einem "dictionary17")
Unter einem Container (bzw. Collection bzw. Ansammlung) versteht man eine
allgemeine Zusammenfassung von Objekten in irgendeiner, nicht näher
spezifizierten Organisationsstruktur. Ordnung kann in einer (An-)Sammlung viel
bedeuten, z.B.:
- in einem "array" die Ablage in irgendeiner Reihenfolge unter einem automatisch fortgeschriebenen
Index
- in einer hierarchischen Struktur (Baum-) ein Container (z.B. Liste), bei dem die enthaltenen
Elemente selbst (Listen) sein dürfen.
In Smalltalk-80 18 ist zu Beginn eine Entscheidung zu treffen, ob die gespeicherten
Elemente geordnet sein sollen.
Sollen Elemente geordnet sein, dann bedient man sich einer der zahlreichen
Unterklassen einer "sequenceableCollection". Im anderen Fall ist zu überlegen, ob
der Zugriff über einen Schlüssel erfolgen soll. Das führt auf ein Wörterbuch
(Tabelle) bzw. (kein Schlüsselzugriff) auf eine Menge (Set). Die Frage, ob Zugriff auf
15
vgl. 1.2.2.3: lineare Listen, Bäume, Graphen
vgl. 1.2.4.2 c)
17 Tabellen, vgl. 1.3.2
18 Smalltalk-80 ist das Ergebnis von Forschungen eines 1970 am Xerox PARC (Palo Alto Research Center)
begonnenen Projekts und umfaßt: Multitasking-BS, grafische Benutzeroberfläche, komplexe
Entwicklungsumgebung mit Editor, Debugger, Interpreter und Compiler, Fenstertechnik, Dialogboxen, DropDown-Menüs.
Aktueller Smalltalk-Dialekt ist VisualWorks, das von der von Xerox gegründeten Firma ParcPlace Systems
vertrieben wird und auf allen wichtigen UNIX-Workstations portiert werden kann.
Smalltalk/V von Digitalk. Wesentliche Unterschiede zu Smalltalk von ParcPlace Systems ergeben sich bei der
grafischen Benutzeroberfläche und höheren Systemklassen.
16
38
Algorithmen und Datenstrukturen
die Elemente über einen Schlüssel erfolgen kann, stellt sich auch bei geordneten
Sammlungen.
Die abstrakte Klasse Collection in Smalltalk
In Smalltalk/V realisiert die abstrakte Klasse "Collection" zusammen mit ihren
Unterklassen ein leistungsfähiges Konzept zum Sammeln und Ordnen von Objekten.
Die Klasse Collection beschreibt die gemeinsamen Eigenschaften aller ObjektAnsammlungen. Da es keine allgemeinen Ansammlungen gibt, kann man auch keine
Instanzen der Klasse Collection bilden (abstrakte Oberklasse). Objekteigenschaften,
die unabhängig von der Zugehörigkeit zu spezifischen Unterklassen sind, können in
der Oberklasse spezifiziert werden. Collection ist eine direkte Unterklasse der
allgemeinsten Klasse Object.
Object
Collection
Bag
IndexedCollection
FixedSizedCollection
Array
Bitmap
ByteArray
Interval
String
Symbol
OrderedCollection
Set
Dictionary
Identity Dictionary
System Dictionary
Abb. 1.3-1: Klassen zum Sammeln und Ordnen von Objekten in Smalltalk/V
Viele bekannte Datenstrukturen stehen bereits in den Klassen zur Verfügung. Durch
Unterklassen und Kombination von Klassen lassen sich beliebig andere
Datenstrukturen erstellen.
Ordnungsprinzipien
„Collection“ selbst nimmt keinen Bezug auf irgendein Ordnungsprinzip, nach dem
seine Elemente abgelegt sind. Die Subklassen von Collection werden so organisiert,
daß sie häufig auftretende Ordnungsprinzipien unterstützen. Das erste
Unterscheidungsmerkmal
betrifft
die
Indizierbarkeit
der
Sammlung
(IndexedCollection). Alle anderen (nicht indizierten) Sammlungen teilen sich sich in
Bag (mehrfache Einträge sind erlaubt) und Set (mehrfache Einträge sind nicht
erlaubt). IndexedCollection wird unterteilt in Sammlungen mit einer festen Anzahl
von Elementen (FixedCollection) oder mit variabler Anzahl von Elementen
(OrderedCollection, paßt die Größe automatisch dem Bedarf an und ermöglicht den
Aufbau üblicher dynamischer Datenstrukturen: Stacks, Fifos, etc.). In der Subklasse
SortedCollection kann die Reihenfolge der Elemente durch eine Sortiervorschrift
festgelegt werden.
Bei nicht indizierten Sammlungen wird nur die Klasse Set weiter spezialisiert. Ihre
Subklasse Dictionary" kann auf die Elemente einer Sammlung über Schlüsselwörter
zugreifen.
Methoden zur Beschreibung des Objektverhalten
39
Algorithmen und Datenstrukturen
Allen Sammlungen ist gemeinsam: Sie enthalten Nachrichten zum Hinzufügen und
Entfernen von Objekten, zum Test auf das Vorhandensein von Elementen und zum
Aufzählen der Elemente. Beschreibungen von Reaktionen auf Nachrichten eines
Objekts heißen Methoden. Jede Methode ist mit einer Nachrichtenkennung
versehen und besteht aus Smalltalk Anweisungen:
Eigenschaft der Methode:
Hinzufügen
Smalltalk-Anweisung
add:anObject
addALL:aCollection
Entfernen
remove:anObject
removeALL
Testen
includes:anObject
isEmpty
occurencesOf:anObject
Aufzählen
do:aBlock
reject:aBlock
collect:aBlock
Bedeutung:
füge ein Objekt hinzu
füge alle Elemente von
aCollection hinzu
entferne ein Objekt
entferne alle Elemente von
aCollection
gib true zurück, falls die
Anwendung leer ist, sonst false
gib true zurück, falls die
Anwendung leer ist, sonst false
gib zurück, wie oft ein
Objekt vorkommt
gib eine Ansammlung mit
den Elementen zurück,
für die die Auswertung
von aBlock true ergibt
gib eine Ansammlung mit
den Elementen zurück, für
die Auswertung von aBlock
false ergibt
führe aBlock für jedes
Element aus und gib eine
Ansammlung mit den
Ergebnisobjekten als
Element zurück
Abb. 1.3-2: Ein Auszug der Instanzmethoden zu Smalltalk-Anweisungen
Die Zuordnung von Nachrichtenkennungen zu Methoden erfolgt dynamisch beim
Senden der Nachricht. Gibt es in der Klasse des Empfängers eine Methode mit der
Nachrichtenkennung, so wird diese Methode ausgeführt. Andernfalls wird die
Methodensuche in der Oberklasse fortgesetzt und solange in der Klassenhierarchie
nach oben gegangen, bis eine Methode mit der gewünschten Nachrichtenkennung
gefunden wird. Gibt es keine solche Methode, dann setzt eine
Ausnahmebehandlung an.
40
Algorithmen und Datenstrukturen
1.3.2 Kollektion-Klassen
Kollektionen
Linear
Allgemein
indexiert
DirektZugriff
Nichtlinear
Sequentieller
Zugriff
Hierarchische
Sammlung
Baum
GruppenKollektionen
Heap
Set
Graph
Dictionary
prioritäts-
Hash-
Liste
Stapel
Schlange
Tabelle
gest. Schl.
„array“
„record“
„file“
Abb. 1.3-4: Hierarchischer Aufbau der Klasse Kollektion
Die Abbildung zeigt unterschiedliche, benutzerdefinierte Datentypen. Gemeinsam ist
diesen Klassen nur die Aufnahme und Berechnung der Daten durch ihre Instanzen.
Kollektionen können in lineare und nichlineare Kategorien eingeteilt werden. Eine
lineare Kollektion enthält eine Liste mit Elementen, die durch ihre Stellung (Position)
geordnet sind, Es gibt ein erstes, zweites, drittes Element etc.
1.3.2.1 Lineare Kollektionen
1. Sammlungen mit direktem Zugriff
Ein „array“ ist eine Sammlung von Komponenten desselben Typs, auf den direkt
zugegriffen werden kann.
41
Algorithmen und Datenstrukturen
„array“-Kollektion
Daten
Eine Kollektion von Objekten desselben (einheitlichen)
Typs
Operationen
Die Daten an jeder Stelle des „array“ können über einen
ganzzahligen Index erreicht werden.
Ein statisches Feld („array“) enthält eine feste Anzahl von Elementen und ist zur
Übersetzungszeit festgelegt. Ein dynamisches Feld benutzt Techniken zur
Speicherbeschaffung und kann während der Laufzeit an die Gegebenheiten
angepaßt werden. Ein „array“ kann zur Speicherung einer Liste herangezogen
werden. Allerdings können Elemente der Liste nur effizient am Ende des „array“
eingefügt werden. Anderenfalls sind für spezielle Einfügeoperationen
Verschiebungen der bereits vorliegenden Elemente (ab Einfügeposition) nötig.
Eine „array“-Klasse sollte Bereichsgrenzenüberwachung für Indexe und dynamische
Erweiterungsmöglichkeiten
erhalten.
Implementierungen
aktueller
Programmiersprachen
umfassen
Array-Klassen
mit
nützlichen
Bearbeitungsmethoden bzw. mit dynamischer Anpassung der Bereichsgrenzen zur
Laufzeit.
Eine Zeichenkette („character string“) ist ein spezialisierter „array“, dessen
Elemente aus Zeichen bestehen:
„character string“-Kollektion
Daten
Eine Zusammenstellung von Zeichen in bekannter Länge
Operationen
Sie umfassen Bestimmen der Länge der Zeichenkette, Kopieren
bzw. Verketten einer Zeichenkette auf eine bzw. mit einer
anderen Zeichenkette, Vergleich zweier Zeichenketten (für die
Musterverarbeitung), Ein-, Ausgabe von Zeichenketten
In einem „array“ kann ein Element über einen Index direkt angesprochen werden.
In vielen Anwendungen ist ein spezifisches Datenelement, der Schlüssel (key) für
den Zugriff auf einen Datensatz vorgesehen. Behälter, die Schlüssel und übrige
Datenelemente zusammen aufnehmen, sind Tabellen.
Ein Dictionary ist eine Menge von Elementen, die über einen Schlüssel identifiziert
werden. Das Paar aus Schlüsseln und zugeordnetem Wert heißt Assoziation, man
spricht auch von „assoziativen Arrays“. Derartige Tabellen ermöglichen den
Direktzugriff über Schlüssel so, wie in einem Array der Direktzugriff über den Index
erreicht wird, z.B.: Die Klasse Hashtable in Java
Der Verbund (record) ist in der Regel eine Zusammenfassung von Datenbehältern
unterschiedlichen Typs:
„record“-Kollektion
Daten
Ein Element mit einer Sammlung von Datenfeldern mit
möglicherweise unterschiedlichen Typen.
Operationen
Der Operator . ist für den Direktzugriff auf den Datenbehälter vorgesehen.
42
Algorithmen und Datenstrukturen
Eine Datei (file) ist eine extern eingerichtete Sammlung, die mit einer Datenstruktur
(„stream“) genannt verknüpft wird.
„file“-Kollektion
Daten
Eine Folge von Bytes, die auf einem externen Gerät abgelegt
ist. Die Daten fließen wie ein Strom von und zum Gerät.
Operationen
Öffnen (open) der Datei, einlesen der Daten aus der Datei,
schreiben der Daten in die Datei, aufsuchen (seek) eines
bestimmten Punkts in der Datei (Direktzugriff) und schließen
(close) der Datei.
2. Sammlungen mit sequentiellem Zugriff
Darunter versteht man lineare Listen (linear list), die Daten in sequentieller
Anordnung aufnehmen:
„list“-Kollektion
Daten
Ein meist größere Objektsammlung von Daten gleichen Typs.
Operationen
Das Durchlaufen der Liste mit Zugriff auf die einzelnen
Elemente beginnt an einem Anfangspunkt, schreitet danach von
Element zu Element fort bis der gewünschte Ort erreicht ist.
Operationen zum Einfügen und Löschen verändern die Größe der
Liste.
Stapel (stack) und Schlangen (queue) sind spezielle Versionen linearer Listen,
deren Zugriffsmöglichkeiten eingeschränkt sind.
„Stapel“-Kollektion
Daten
Eine Liste mit Elementen, auf die man nur über die Spitze
(„top“) zugreifen kann.
Operationen
Unterstützt werden „push“ und „pop“. „push“ fügt ein neues
Element an der Spitze der Liste hinzu, „pop“ entfernt ein
Element von der Spitze („top“) der Liste.
Bsp.: Die Klasse Stack in Java
„Schlange“-Kollektion
Daten
Eine Sammlung von Elementen mit Zugriff am Anfang und Ende
der Liste.
Operationen
Ein Element wird am Ende der Liste hinzugefügt und am Ende
der Liste entfernt.
43
Algorithmen und Datenstrukturen
Eine Schlange ist besonders geeignet zur Verwaltung von „Wartelisten“ und kann
zur Simulation von Wartesystemen eingesetzt werden. Eine Schlange kann ihre
Elemente nach Prioritäten bzgl. der Verarbeitung geordnet haben (priority
queue). Entfernt wird dann zuerst das Element, das die höchste Priorität besitzt.
„prioritätsgesteuerte Schlange“-Kollektion
Daten
Eine Sammlung von Elementen, von denen jedes Element eine
Priorität besitzt.
Operationen
Hinzufügen von Elementen zur Liste. Entfernt wird immer das
Element, das die höchste (oder niedrigste) Priorität besitzt.
1.3.2.2 Nichtlineare Kollektionen
1. Hierarchische Sammlung
Eine hierarchisch angeordnete Sammlung von Datenbehältern ist gewöhlich ein
Baum mit einem Ursprungs- bzw. Ausgangsknoten, der „Wurzel“ genannt wird. Von
besonderer Bedeutung ist eine Baumstruktur, in der jeder Baumknoten zwei Zeiger
auf nachfolgende Knoten aufnehmen kann. Diese Binärbaum-Struktur kann mit Hilfe
einer speziellen angeordneten Folge der Baumknoten zu einem binären Suchbaum
erweitert werden. Binäre Suchbäume bilden die Ausgangsbasis für das Speichern
großer Datenmengen.
„Baum“-Kollektion
Daten
Eine hierarchisch angeordnete Ansammlung von Knotenelementen,
die von einem Wurzelknoten abgeleitet sind. Jeder Knoten hält
Zeiger
zu
Nachfolgeknoten,
die
wiederum
Wurzeln
von
Teilbäumen sein können.
Operationen
Die Baumstruktur erlaubt das Hinzufügen und Löschen von
Knoten. Obwohl die Baumstruktur nichtlinear ist, ermöglichen
Algorithmen zum Ansteuern der Baumknoten den Zugriff auf die
in den Knoten gespeicherten Informationen.
Ein „heap“ ist eine spezielle Version, in der das kleinste bzw. größte Element den
Wurzelknoten besetzt. Operationen zum Löschen entfernen den Wurzelknoten,
dabei wird, wie auch beim Einfügen, der Baum reorganisiert.
Basis der Heap-Darstellung19 ist ein „array“ (Feldbaum), dessen Komponenten eine
Binärbaumstruktur überlagert ist. In der folgenden Darstellung ist eine derartige
Interpretation durch Markierung der Knotenelemente eines Binärbaums mit
Indexpositionen sichtbar:
[1]
wk1
19
Vgl. 3.2.2.3
44
Algorithmen und Datenstrukturen
[2]
[3]
wk2
wk3
[4]
[5]
wk4
[8]
[15]
wk8
wk5
[9]
[10]
[11]
[12]
wk9
wk10
wk11
wk12
[6]
[7]
wk6
wk7
[13]
wk13
[14]
wk14
wk15
Abb. 1.3-10: Darstellung eines Feldbaums
Liegen die Knoten auf den hier in Klammern angegebenen Positionen, so kann man
von jeder Position I mit
Pl = 2 * I
Pr = 2 * I + 1
Pv = I div 2
auf die Position des linken (Pl) und des rechten (Pr) Nachfolgers und des
Vorgängers (Pv) gelangen. Ein binärer Baum kann somit in einem eindimensionalen
Feld (array) gespeichert werden, falls folgende Relationen eingehalten werden:
X[I] <= X[2*I]
X[I] <= X[2*I+1]
X[1] = min(X[I] .. X[N])
Anstatt auf das kleinste kann auch auf das größte Element Bezug genommen
werden
Aufbau des Heap: Ausgangspunkt ist ein „array“ mit bspw. folgenden Daten:
X[1]
40
X[2]
10
X[3]
30
X[4]
......
, der folgendermaßen interpretiert wird:
X[1]
40
X[2]
X[3]
10
30
Abb. 1.3-11: Interpretation von Feldinhalten im Rahmen eines binären Baums
Durch eine neue Anordnung der Daten in den Feldkomponenten entsteht ein „heap“:
45
Algorithmen und Datenstrukturen
X[1]
10
X[2]
X[3]
40
30
Abb. 1.3-12:
Falls ein neues Element eingefügt wird, dann wird nach dem Ordnen gemäß der
„heap“-Bedingung erreicht:
X[1]
10
X[2]
X[3]
40
30
X[4]
15
X[1]
10
X[2]
X[3]
15
30
X[4]
40
Abb.1.3-13: Das Einbringen eines Elements in einen Heap
Beim Löschen wird das Wurzelelement an der 1. Position entfernt. Das letzte
Element im „heap“ wird dazu benutzt, das Vakuum zu füllen. Anschließend wird
reorganisiert:
46
Algorithmen und Datenstrukturen
X[1]
10
X[2]
X[3]
15
30
X[4]
40
X[1]
40
X[2]
X[3]
15
30
X[1]
15
X[2]
X[3]
40
30
Abb. 1.3-14: Das Löschen eines Elements im Heap
Die Klasse BinaryHeap in Java20
// BinaryHeap class
//
// Erzeugung mit optionaler Angabe zur Kapazitaet (Defaultwert: 100)
//
// ************** PUBLIC OPERATIONEN *************************************
// void insert( x )
--> Einfuegen x
// Comparable loescheMin()--> Rueckgabe und entfernen
//
des kleinsten Elements
// Comparable findMin( ) --> Rueckgabe des kleinsten Elements
// boolean isEmpty( )
--> Rueckgabe: true, falls leer;
//
anderenfalls false
// boolean isFull( )
--> Rueckgabe true, falls voll;
//
anderenfalls false
// void makeEmpty( )
--> Entfernen aller Elemente
20
vgl. pr13228
47
Algorithmen und Datenstrukturen
2. Gruppenkollektionen
Menge (Set)
Eine Gruppe umfaßt nichtlineare Kollektionen ohne jegliche Ordnungsbeziehung.
Eine Menge (set) einheitlicher Elemente ist. bspw. eine Gruppe. Operationen auf die
Kollektion „Set“ umfassen Vereinigung, Differenz und Schnittmengenbildung.
„Set“-Kollektion
Daten
Eine ungeordnete Ansammlung von Objekten ohne Ordnung
Operationen
Die binäre Operationen über Mitgliedschaft, Vereinigung,
Schnittmenge und Differenz bearbeiten die Strukturart „Set“.
Weiter Operationen testen auf Teilmengenbeziehungen.
Graphen
Ein Graph (graph) ist eine Datenstruktur, die durch eine Menge Knoten und eine
Menge Kanten, die die Knoten verbinden, definiert ist.
„graph“-Kollektion
Daten
Eine Menge von Knoten und eine Menge verbindender Kanten.
Operationen
Der Graph kann Knoten hinzufügen bzw. löschen. Bestimmte
Algorithmen starten an einem gegebenen Knoten und finden alle
von diesem Knoten aus erreichbaren Knoten. Andere Algorithmen
erreichen jeden Knoten im Graphen über „Tiefen“ bzw.
„Breiten“ - Suche.
Ein Netzwerk ist spezielle Form eines Graphen, in dem jede Kante ein bestimmtes
Gewicht trägt. Den Gewichten können Kosten, Entfernungen etc. zugeordnet sein.
48
Herunterladen