1 Einführung in die Programmierung

Werbung
Kapitel 8: Elementare Datenstrukturen
Kapitel 8
Inhalt
● Definition: Abstrakter Datentyp (ADT)
Einführung in die Programmierung
● ADT Keller
Wintersemester 2008/09
● ADT Schlange
● ADT Liste
● ADT Binärer Suchbaum
● ADT Graph
Prof. Dr. Günter Rudolph
● Exkurse:
Lehrstuhl für Algorithm Engineering
Fakultät für Informatik
- Einfache Dateibehandlung
TU Dortmund
- C++ Strings
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
2
Kapitel 8
Elementare Datenstrukturen
Elementare Datenstrukturen
Definition:
Beispiel: ADT bool
Abstrakter Datentyp (ADT) ist ein Tripel (T, F, A), wobei
F: Operationen
● T eine nicht leere Menge von Datenobjekten
true
false
not
and
or
● F eine Menge von Operationen,
● A eine nicht leere Menge von Axiomen,
die die Bedeutung der Operationen erklären.
■
:
:
: bool
: bool x bool
: bool x bool
Kapitel 8
→ bool
→ bool
→ bool
→ bool
→ bool
Festlegung, welche
Funktionen es gibt
= true
= false
= false
= false
= false
= true
= not(and(not(x), not(y)))
Festlegung, was die
Funktionen bewirken
A: Axiome
Abstrakt?
● Datenobjekte brauchen keine konkrete Darstellung (Verallgemeinerung).
● Die Wirkung der Operationen wird beschrieben,
nicht deren algorithmische Ausprägung.
→ „WAS, nicht WIE!“
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
3
not(false)
not(true)
and(false, false)
and(false, true)
and(true, false)
and(true, true)
or(x, y)
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
4
1
Kapitel 8
Elementare Datenstrukturen
Kapitel 8
Elementare Datenstrukturen
Lineare Datenstrukturen: Keller bzw. Stapel (engl. stack)
Eigenschaften
● Wenn man ADT kennt, dann kann man ihn überall verwenden.
● Implementierung der Funktionen für Benutzer nicht von Bedeutung.
● Trennung von Spezifikation und Implementierung.
● Ermöglicht späteren Austausch der Implementierung,
ohne dass sich der Ablauf anderer Programme, die ihn benutzen, ändert!
create
push
pop
top
empty
:
:
:
:
:
Keller x T
Keller
Keller
Keller
Aufräumen:
Kiste in Keller,
oben auf Haufen.
→ Keller
→ Keller
→ Keller
→T
→ bool
Etwas aus Keller holen:
Zuerst Kiste, weil oben
auf Haufen.
Nur Operationen geben Zugriff auf Daten!
empty(create)
= true
empty(push(k, x)) = false
pop(push(k, x)) = k
top(push(k, x))
=x
→ Stichwort: Information Hiding!
Stapel
Teller
LIFO:
Last in, first out.
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
5
Kapitel 8
Elementare Datenstrukturen
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
6
Kapitel 8
Elementare Datenstrukturen
Implementierung: (Version 1)
Wann können Probleme auftreten?
const int maxSize = 100;
struct Keller {
T data[maxSize]; // data array
int sp;
// stack pointer
};
Bei pop, falls Keller leer ist:
→ Stackpointer wird -2, anschließendes push versucht auf data[-1] zu schreiben
Bei top, falls Keller leer ist:
void create(Keller &k) {
k.sp = -1;
}
T top(Keller k) {
return k.data[k.sp];
}
bool empty(Keller k) {
return k.sp == -1;
}
void push(Keller &k, T x){
k.data[++k.sp] = x;
}
→ es wird undefinierter Wert von data[-1] zurückgegeben
Bei push, falls Keller voll ist:
→ es wird versucht auf data[maxsize] zu schreiben
void pop(Keller &k) {
k.sp--;
}
Probleme:
Arraygrenzen!
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
7
⇒ diese Fälle müssen abgefangen werden! Fehlermeldung!
void error(char *info) {
cerr << info << endl;
exit(1);
}
gibt Fehlermeldung info aus und bricht
das Programm durch exit(1) sofort ab
und liefert den Wert des Arguments (hier: 1)
an das Betriebssystem zurück
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
8
2
Kapitel 8
Elementare Datenstrukturen
Implementierung: (Version 2, nur Änderungen und Zusätze bei Funktionen)
const int maxSize = 100;
struct Keller {
T data[maxSize]; // data array
int sp;
// stack pointer
};
unverändert
T top(Keller k) {
if (!empty(k))
return k.data[k.sp];
else error(“leer“);
}
void push(Keller &k, T x){
if (!full(k))
k.data[++k.sp] = x;
else error(“voll“);
}
void pop(Keller &k) {
if (!empty(k)) k.sp--;
else error(“leer“);
}
bool full(Keller k) {
return k.sp == maxSize-1;
}
Kapitel 8
Elementare Datenstrukturen
Lineare Datenstrukturen: Schlange (engl. queue)
FIFO:
First in, first out.
create
enq
deq
front
empty
Schlange an der
Supermarktkasse:
:
:
:
:
:
→ Schlange
Schlange x T → Schlange
Schlange
→ Schlange
Schlange
→T
Schlange
→ bool
empty(create)
empty(enq(s, x))
deq(enq(s, x))
front(enq(s, x))
= true
= false
= empty(s) ? s : enq(deq(s), x)
= empty(s) ? x : front(s)
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
9
Kapitel 8
Elementare Datenstrukturen
Wenn Einkauf fertig,
dann hinten anstellen.
Der nächste Kunde an
der Kasse steht ganz
vorne in der Schlange.
Eingehende Aufträge
werden „geparkt“, und
dann nach und nach in
der Reihenfolge des
Eingangs abgearbeitet.
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
10
Kapitel 8
Elementare Datenstrukturen
Implementierung: (Version 1)
Implementierung: (Version 2, nur Änderungen und Zusätze bei Funktionen)
const int maxSize = 100;
struct Schlange {
T data[maxSize]; // data array
int ep;
// end pointer
};
const int maxSize = 100;
struct Schlange {
T data[maxSize]; // data array
int ep;
// end pointer
};
void create(Schlange &s){
s.ep = -1;
}
void enq(Schlange &s, T x) {
s.data[++s.ep] = x;
}
bool empty(Schlange s) {
return s.ep == -1;
}
void deq(Schlange &s) {
for (int i=0; i<s.ep; i++)
s.data[i] = s.data[i+1];
s.ep--;
}
T front(Schlange s) {
return s.data[0];
}
Probleme: Arraygrenzen!
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
11
T front(Schlange s) {
if (!empty(s))
return s.data[0];
error(“leer“);
}
unverändert
void enq(Schlange &s, T x) {
if (!full(s))
s.data[++s.ep] = x;
else error(“full“);
}
void deq(Schlange &s) {
if (empty(s)) error(“leer“);
for (int i=0; i<s.ep; i++)
s.data[i] = s.data[i+1];
s.ep--;
}
bool full(Schlange s) {
return
s.ep == maxSize-1;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
12
3
Kapitel 8
Elementare Datenstrukturen
Benutzer des (abstrakten) Datentyps Schlange wird feststellen, dass
Kapitel 8
Elementare Datenstrukturen
Idee: Array zum Kreis machen; zusätzlich Anfang/Start markieren (s.sp)
1. fast alle Operationen schnell sind, aber
2. die Operation deq vergleichsweise langsam ist.
s.ep
7
Laufzeit / Effizienz der Operation deq
void deq(Schlange &s) {
if (empty(s)) error(“leer“);
for (int i=0; i < s.ep; i++)
s.data[i] = s.data[i+1];
s.ep--;
}
6
1
5
2
s.ep = Anzahl Elemente in Schlange
s.ep viele Datenverschiebungen
Worst case: (maxSize – 1) mal
4
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
13
Kapitel 8
Elementare Datenstrukturen
Implementierung: (Version 3)
const int maxSize =
struct Schlange {
T data[maxSize];
int sp;
int ep;
};
s.sp
0
3
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
14
Kapitel 8
Elementare Datenstrukturen
Implementierung: (Version 3)
100;
// data array
// start pointer
// end pointer
void create(Schlange& s) {
s.sp = 0; s.ep = maxSize;
}
bool empty(Schlange s) {
return s.ep == maxSize;
}
bool full(Schlange s) {
int d = s.ep – s.sp;
if ((d == -1) ||
(d == maxSize-1)) return;
}
T front(Schlange s) {
if (!empty(s))
return s.data[s.sp];
error(“leer“);
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
15
void enq(Schlange &s, T x) {
if (!full(s)) {
s.ep = (s.ep + 1) % maxSize;
s.data[s.ep] = x;
}
else error(“full“);
}
void deq(Schlange &s) {
if (empty(s)) error(“leer“);
else if (s.sp == s.ep) create(s);
else s.sp = (s.sp + 1) % maxSize;
}
Laufzeit:
unabhängig von Größe
der Schlange
Laufzeit:
unabhängig von Größe
der Schlange
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
16
4
Kapitel 8
Elementare Datenstrukturen
Kapitel 8
Wiederholung: ADT Schlange
Lineare Datenstrukturen: Schlange (engl. queue)
Unbefriedigend bei der Implementierung:
Maximale festgelegte Größe des Kellers bzw. der Schlange!
→ Liegt an der unterliegenden Datenstruktur Array:
Array ist statisch, d.h.
Größe wird zur Übersetzungszeit festgelegt
und ist während der Laufzeit des Programms nicht veränderbar!
Schön wären dynamische Datenstrukturen, d.h.
Größe wird zur Übersetzungszeit nicht festgelegt
und ist während der Laufzeit des Programms veränderbar!
⇒ Dynamischer Speicher!
(Stichwort: new / delete )
create
enq
deq
front
empty
:
:
:
:
:
→ Schlange
Schlange x T → Schlange
Schlange
→ Schlange
Schlange
→T
Schlange
→ bool
create
: erzeugt leere Schlange
enq
: hängt Element ans Ende der Schlange
deq
: entfernt Kopf der Schlange
front
: gibt im Kopf der Schlange gespeichertes Element zurück
empty
: prüft, ob Schlange leer ist
→ Implementierung mit statischen Speicher ersetzen durch dynamischen Speicher
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
17
Kapitel 8
Wiederholung: Dynamischer Speicher
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
18
Kapitel 8
Elementare Datenstrukturen
Vorüberlegungen für ADT Schlange mit dynamischen Speicher:
Bauplan:
Datentyp *Variable = new Datentyp;
(Erzeugen)
delete Variable;
(Löschen)
Wir können bei der Realisierung der Schlange
statt statischen (Array) nun dynamischen Speicher verwenden …
Ansatz: new int[oldsize+1]
Bauplan für Arrays:
Datentyp *Variable = new Datentyp[Anzahl];
(Erzeugen)
delete[] Variable;
(Löschen)
… bringt uns das weiter?
→ Größe kann zwar zur Laufzeit angegeben werden, ist aber dann fixiert!
Falls maximale Größe erreicht, könnte man
Achtung:
1. größeres Array anlegen
Dynamisch erzeugte Objekte müssen auch wieder gelöscht werden!
Keine automatische Speicherbereinigung!
2. Arraywerte ins größere Array kopieren und
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
19
ineffizient!
3. kleineres Array löschen.
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
20
5
Kapitel 8
Elementare Datenstrukturen
Vorüberlegungen für ADT Schlange mit dynamischen Speicher:
Objekt
Datenfeld
„Kopf “
+
„Schwanz der Schlange“
0
zeigt auf Null (nichts):
Nullpointer
Ende
Start
// Datenfeld
// Zeiger auf Schwanz der Schlange
// Start
// Ende
Schlange *create() {
Schlange *s = new Schlange;
s->ep = 0;
return s;
}
Speicher für Schlange allokieren
Initialisierung!
Rückgabe des Zeigers auf Schlange
bool empty(Schlange *s) {
return s->ep == 0;
}
true falls s->ep == 0, sonst false
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
21
Elementare Datenstrukturen
Kapitel 8
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
22
Elementare Datenstrukturen
Implementierung: (Version 4)
Implementierung: (Version 4)
struct Objekt {
T data;
Objekt *tail;
};
struct Schlange {
Objekt *sp;
Objekt *ep;
};
struct Objekt {
T data;
Objekt *tail;
};
struct Schlange {
Objekt *sp;
Objekt *ep;
};
// Datenfeld
// Zeiger auf Schwanz der Schlange
// Start
// Ende
T front(Schlange *s) {
if (!empty(s))
return (s->sp)->data;
error(“leer“);
}
void clear(Schlange *s) {
while (!empty(s)) deq(s);
}
Was bedeutet s->sp->data ?
identisch zu (*(*s).sp).data
vorderstes Element entfernen
bis Schlange leer
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
23
Kapitel 8
Implementierung: (Version 4)
struct Objekt {
T data;
Objekt *tail;
};
struct Schlange {
Objekt *sp;
Objekt *ep;
};
Zeiger
Schlange
Elementare Datenstrukturen
Kapitel 8
// Datenfeld
// Zeiger auf Schwanz der Schlange
// Start
// Ende
void enq(Schlange *s, T x) {
Objekt *obj = new Objekt;
obj->data = x;
obj->tail = 0;
if (empty(s)) s->sp = obj;
else s->ep->tail = obj;
s->ep = obj;
}
neues Objekt anlegen (dyn. Speicher)
neues Objekt initialisieren
neues Objekt vorne, falls Schlange leer
sonst hinten anhängen
Aktualisierung des Endezeigers
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
24
6
Elementare Datenstrukturen
Kapitel 8
Implementierung: (Version 4)
struct Objekt {
T data;
Objekt *tail;
};
struct Schlange {
Objekt *sp;
Objekt *ep;
};
// Datenfeld
// Zeiger auf Schwanz der Schlange
// Start
// Ende
void deq(Schlange *s) {
if (empty(s))error(“leer“);
Objekt *obj = s->sp;
s->sp = s->sp->tail;
if (s->sp == 0) s->ep = 0;
delete obj;
}
Elementare Datenstrukturen
int main() {
Testprogramm!
Schlange *s = create();
if (empty(s)) cout << "Schlange leer" << endl;
for (int i = 0; i < 10; i++) enq(s, i);
if (!empty(s)) cout << "Schlange nicht mehr leer" << endl;
cout << "vorderstes Element: " << front(s) << endl;
while (!empty(s)) {
cout << front(s) << " ";
deq(s);
}
cout << endl;
if (empty(s)) cout << "Schlange jetzt leer" << endl;
Zeiger auf zu löschendes Objekt retten
Startzeiger auf 2. Element setzen
falls kein 2. Element, dann Schlange leer
for (i = 0; i < 100; i++) enq(s,i);
if (!empty(s)) cout << "Schlange nicht mehr leer" << endl;
clear(s);
if (empty(s)) cout << "Schlange wieder leer" << endl;
ehemals 1. Element löschen
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
25
Elementare Datenstrukturen
Kapitel 8
Kapitel 8
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
26
Elementare Datenstrukturen
Kapitel 8
ADT Liste (1. Version)
Liste wird nur durch
einen Zeiger auf
ihren Listenkopf
repräsentiert
struct Liste {
T data;
Liste *next;
};
Operationen:
create
empty
append
prepend
clear
is_elem
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
27
:
:
:
:
:
:
Liste
T x Liste
T x Liste
T x Liste
→ Liste
→ bool
→ Liste
→ Liste
→ Liste
→ bool
hängt am Ende an
vor Kopf einfügen
ist Element enthalten?
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
28
7
Kapitel 8
Elementare Datenstrukturen
ADT Liste (1. Version)
Kapitel 8
Elementare Datenstrukturen
ADT Liste (1. Version)
struct Liste {
T data;
Liste *next;
};
Liste wird nur durch
einen Zeiger auf
ihren Listenkopf
repräsentiert
struct Liste {
T data;
Liste *next;
};
Liste *create() {
return 0;
}
Laufzeit:
unabhängig von Listenlänge
bool empty(Liste *liste) {
return liste == 0;
}
Laufzeit:
unabhängig von Listenlänge
bool is_elem(T x, Liste *liste) {
if (liste == 0) return false;
if (liste->data == x) return true;
return is_elem(x, liste->next);
}
void clear(Liste *liste) {
if (empty(liste)) return;
clear(liste->next);
delete liste;
}
rekursives Löschen von
„hinten“ nach „vorne“
Laufzeit:
proportional zur Listenlänge
Liste *prepend(T x, Liste *liste) {
Liste *L = new Liste;
L->data = x;
L->next = liste;
return L;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
29
Kapitel 8
Elementare Datenstrukturen
ADT Liste (1. Version)
rekursiver Durchlauf von
„vorne“ nach „hinten“
Laufzeit:
proportional zur Listenlänge
Laufzeit:
unabhängig von Listenlänge
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
30
Kapitel 8
Elementare Datenstrukturen
ADT Liste (1. Version)
Zusammenfassung:
struct Liste {
T data;
Liste *next;
};
1. Laufzeit von clear proportional zur Listenlänge
→ kann nicht verbessert werden, weil ja jedes Element gelöscht werden muss
→ unproblematisch, weil nur selten aufgerufen
Liste *append(T x, Liste *liste) {
Liste *tmp = liste;
Liste *L = new Liste;
L->data = x;
L->next = 0;
if (liste == 0) return L;
while (liste->next != 0)
liste = liste->next;
liste->next = L;
return tmp;
}
Zeiger auf Listenkopf retten
2. Laufzeit von is_elem proportional zur Listenlänge
→ kann bei dieser Datenstruktur nicht verbessert werden
→ später verbessert durch ADT BinärBaum
3. Laufzeit von append proportional zur Listenlänge
iterativer Durchlauf von
„vorne“ nach „hinten“
→ kann durch Veränderung der Implementierung verbessert werden
→ zusätzlicher Zeiger auf das Ende der Liste
Laufzeit:
proportional zur Listenlänge
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
31
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
32
8
Elementare Datenstrukturen
Kapitel 8
ADT Liste (2. Version)
struct Liste {
Element *head;
Element *foot;
};
struct Element {
T data;
Element *next;
};
Liste *create() {
Liste *L = new Liste;
L->head = L->foot = 0;
return L;
}
bool empty(Liste *liste) {
return liste->foot == 0;
}
ADT Liste (2. Version)
Liste besteht aus 2 Zeigern:
Zeiger auf Listenkopf (Anfang)
Zeiger auf Listenfuß (Ende)
Nutzdaten
Zeiger auf nächstes Element
Laufzeit:
unabhängig von Listenlänge
Laufzeit:
unabhängig von Listenlänge
struct Liste {
Element *head;
Element *foot;
};
struct Element {
T data;
Element *next;
};
bool is_elem(T x, Liste *liste) {
if (empty(liste)) return false;
Element *elem = liste->head;
while (elem != 0) {
if (elem->data == x) return true;
elem = elem->next;
}
return false;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
33
Elementare Datenstrukturen
Kapitel 8
ADT Liste (2. Version)
struct Liste {
Element *head;
Element *foot;
};
struct Element {
T data;
Element *next;
};
Laufzeit:
proportional zur Listenlänge
→ keine Verbesserung (OK)
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
35
Laufzeit:
proportional zur Listenlänge
→ keine Verbesserung (OK)
Kapitel 8
Elementare Datenstrukturen
ADT Liste (2. Version)
rekursives Löschen von
„hinten“ nach „vorne“
iterativer Durchlauf von
„vorne“ nach „hinten“
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
34
struct Liste {
Element *head;
Element *foot;
};
struct Element {
T data;
Element *next;
};
void clear(Liste *liste) {
if (empty(liste)) return;
Element *elem = liste->head;
liste->head = elem->next;
clear(liste);
delete elem;
}
Kapitel 8
Elementare Datenstrukturen
Liste *prepend(T x, Liste *liste) {
Element *elem = new Element;
elem->data = x;
elem->next = liste->head;
if (empty(liste)) liste->foot = elem;
liste->head = elem;
return liste;
}
Laufzeit:
unabhängig von Listenlänge
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
36
9
Kapitel 8
Elementare Datenstrukturen
ADT Liste (2. Version)
Kapitel 8
Elementare Datenstrukturen
ADT Liste (2. Version)
struct Liste {
Element *head;
Element *foot;
};
struct Element {
T data;
Element *next;
};
Zusammenfassung:
1. Laufzeit von clear proportional zur Listenlänge
→ kann nicht verbessert werden, weil ja jedes Element gelöscht werden muss
→ unproblematisch, weil nur selten aufgerufen
2. Laufzeit von is_elem proportional zur Listenlänge
Liste *append(T x, Liste *liste) {
Element *elem = new Element;
elem->data = x;
elem->next = 0;
if (empty(liste)) liste->head = elem;
else liste->foot->next = elem;
liste->foot = elem;
}
→ kann bei dieser Datenstruktur nicht verbessert werden
→ verbessern wir gleich durch ADT BinärBaum
3. Laufzeit von append unabhängig von Listenlänge
Laufzeit:
unabhängig von Listenlänge
→ war proportional zur Listenlänge in 1. Version
→ Verbesserung erzielt durch Veränderung der Implementierung
→ Verbesserung!
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
37
Elementare Datenstrukturen
Kapitel 8
ADT Binäre Bäume
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
38
Kapitel 8
Elementare Datenstrukturen
ADT Binäre Bäume
Vorbemerkungen:
Beispiel:
kleiner
: nach links
Zahlenfolge (z. B. 17, 4, 36, 2, 8, 19, 40, 6, 7, 37) soll gespeichert werden,
um später darin suchen zu können
Zahlenfolge 17, 4, 36, 2, 8, 19, 40, 6, 7, 37
größer
: nach rechts
17
Man könnte sich eine Menge A vorstellen mit Anfrage: Ist 40 ∈ A ?
4
Mögliche Lösung: Zahlen in einer Liste speichern und nach 40 suchen …
… aber: nicht effizient,
weil im schlechtesten Fall alle Elemente überprüft werden müssen!
36
2
8
19
6
Bessere Lösungen?
40
37
z.B. „Suche, ob 42 enthalten“
7
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
39
benötigt nur 3 Vergleiche bis
zur Entscheidung false
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
40
10
Kapitel 8
Elementare Datenstrukturen
Kapitel 8
Elementare Datenstrukturen
ADT Binäre Bäume
ADT Binäre Bäume: Terminologie
Beispiel:
kleiner
: nach links
Zahlenfolge 17, 4, 36, 2, 8, 19, 40, 6, 7, 37
größer
: nach rechts
keine Wurzel und kein Blatt
⇒ innerer Knoten
Wurzel
17
4
36
2
8
19
40
6
L
R
linker
Unterbaum
rechter
Unterbaum
37
z.B. „Suche, ob 42 enthalten“
7
benötigt nur 3 Vergleiche bis
zur Entscheidung false
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
41
Elementare Datenstrukturen
Kapitel 8
ADT Binäre Bäume: Datenstruktur
struct BinTree {
T data;
BinTree *lTree, *rTree;
};
Blätter
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
42
Elementare Datenstrukturen
Kapitel 8
ADT Binäre Bäume: Einfügen
// Nutzdaten
// linker und rechter Unterbaum
Falls ein Unterbaum nicht existiert, dann zeigt der Zeiger auf 0.
bool IsElement(int key, BinTree *tree) {
if (tree == 0) return false;
if (tree->data == key) return true;
if (tree->data < key) return IsElement(key, tree->rTree);
return IsElement(key, tree->lTree);
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
43
BinTree *Insert(int key, BinTree *tree) {
if (tree == 0) {
BinTree *b = new BinTree;
b->data = key;
b->lTree = b->rTree = 0;
return b;
}
else {
if (tree->data < key)
tree->rTree = Insert(key, tree->rTree);
else if (tree->data > key)
tree->lTree = Insert(key, tree->lTree);
return tree;
}
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
44
11
Kapitel 8
Elementare Datenstrukturen
ADT Binäre Bäume: Aufräumen
ADT Binäre Bäume
void Clear(BinTree *tree) {
if (tree == 0) return;
//
Clear(tree->lTree);
//
Clear(tree->rTree);
//
delete tree;
//
}
3
6
4
u.s.w.
20
19
2
7
Rekursionsabbruch
linken Unterbaum löschen
rechten Unterbaum löschen
aktuellen Knoten löschen
1
18
17
8
Höhe := Länge des längsten Pfades von der Wurzel zu einem Blatt.
16
9
Höhe(leerer Baum) = 0
10
5
Kapitel 8
Elementare Datenstrukturen
15
12
Höhe(nicht leerer Baum) = 1 + max { Höhe(linker U-Baum), Höhe(rechter U-Baum) }
13
11
14
Anmerkung: rekursive Definition!
(U-Baum = Unterbaum)
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
45
Kapitel 8
Elementare Datenstrukturen
ADT Binäre Bäume
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
46
ADT Binäre Bäume
1
Kapitel 8
Elementare Datenstrukturen
1
2
2
3
3
4
4
● Ein vollständiger Baum der Höhe h besitzt 2h – 1 Knoten.
Auf Ebene k können jeweils zwischen 1 und
2k-1
Elemente gespeichert werden.
⇒ In einem Baum der Höhe h können also zwischen h und
Elemente gespeichert werden!
Man braucht maximal h Vergleiche, um Element (ggf. nicht) zu finden.
Bei n = 2h – 1 Elementen braucht man log2(n) < h Vergleiche!
● Ein degenerierter Baum der Höhe h besitzt h Knoten (= lineare Liste).
Man braucht maximal h Vergleiche, um Element (ggf. nicht) zu finden.
Bei n = h braucht man also n Vergleiche!
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
47
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
48
12
Kapitel 8
Exkurs: Einfache Dateibehandlung
Datei := speichert Daten in linearer Anordnung
Kapitel 8
Exkurs: Einfache Dateibehandlung
Hier: einfache Dateibehandlung!
● Dateien können gelesen oder beschrieben werden.
Zwei Typen:
● Vor dem ersten Lesen oder Schreiben muss Datei geöffnet werden.
● Man kann prüfen, ob das Öffnen funktioniert hat.
● ASCII-Dateien
● Nach dem letzten Lesen oder Schreiben muss Datei geschlossen werden.
- sind mit Editor les- und schreibbar
- Dateiendung („suffix“ oder „extension“) meist .txt oder .asc
- betriebssystem-spezifische Übersetzung von Zeichen bei Datentransfer
zwischen Programm und externem Speicher
● Bei zu lesenden Dateien kann gefragt werden, ob Ende der Datei erreicht ist.
● Beim Öffnen einer zu schreibenden Datei wird vorheriger Inhalt gelöscht!
● Man kann noch viel mehr machen …
● Binär-Dateien
- werden byteweise beschrieben und gelesen
wir benötigen:
- lesen / schreiben mit Editor ist keine gute Idee
bzw.
#include <fstream>
- schnellerer Datentransfer, da keine Zeichenübersetzung
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
49
Kapitel 8
Exkurs: Einfache Dateibehandlung
● Eingabe-Datei = input file
ifstream Quelldatei;
Datentyp
Bezeichner
● Öffnen der Datei:
● Ausgabe-Datei = output file
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
50
Exkurs: Einfache Dateibehandlung
Bezeichner
● Öffnen der Datei:
Quelldatei.open(dateiName);
Zieldatei.open(dateiName);
ist Kurzform von
Quelldatei.open(dateiName, modus);
ist Kurzform von
Quelldatei.open(dateiName, modus);
wobei fehlender modus bedeutet:
ASCII-Datei,
Eingabedatei (weil ifstream)
wobei fehlender modus bedeutet:
ASCII-Datei,
Ausgabedatei (weil ofstream)
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
51
Kapitel 8
modus:
ios::binary
binäre Datei
ios::in
öffnet für Eingabe (implizit bei ifstream)
ios::out
öffnet für Ausgabe (implizit bei ofstream)
ios::app
hängt Daten am Dateiende an
ios::nocreate
wenn Datei existiert, dann nicht anlegen
ofstream Zieldatei;
Datentyp
<fstream.h>
Warnung: teilweise Compiler-abhängig
(nocreate fehlt in MS VS 2003, dafür trunc)
Man kann diese Schalter / Flags miteinander kombinieren via:
ios::binary | ios::app
(öffnet als binäre Datei und hängt Daten an)
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
52
13
Kapitel 8
Exkurs: Einfache Dateibehandlung
● Datei öffnen
file.open(fileName) bzw. file.open(fileName, modus)
falls Öffnen fehlschlägt, wird Nullpointer zurückgegeben
● Datei schließen
file.close()
sorgt für definierten Zustand der Datei auf Dateisystem;
bei nicht geschlossenen Dateien droht Datenverlust!
Merke:
1. Auf eine geöffnete Datei darf immer nur einer zugreifen.
2. Eine geöffnete Datei belegt Ressourcen des Betriebssystems.
⇒ Deshalb Datei nicht länger als nötig geöffnet halten.
3. Eine geöffnete Datei unbekannter Länge kann solange gelesen werden,
bis das Ende-Bit (end of file, EOF) gesetzt wird.
4. Der Versuch, eine nicht vorhandene Datei zu öffnen (zum Lesen) oder
eine schreibgeschützte Datei zu öffnen (zum Schreiben), führt zu einem
Nullpointer.
⇒ Das muss überprüft werden, sonst Absturz bei weiterer Verwendung!
● Ende erreicht?
ja falls file.eof() == true
● Lesen (von ifstream)
file.get(c);
file >> x;
Kapitel 8
Exkurs: Einfache Dateibehandlung
liest ein Zeichen
liest verschiedene Typen
● Schreiben (von ofstream)
file.put(c);
schreibt ein Zeichen
file << x;
schreibt verschiedene Typen
5. Dateieingabe und -ausgabe (input/output, I/O) ist sehr langsam im
Vergleich zu den Rechenoperationen.
⇒ I/O Operationen minimieren.
“The fastest I/O is no I/O.“
Nils-Peter Nelson, Bell Labs
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
53
Exkurs: Einfache Dateibehandlung
Kapitel 8
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
54
Exkurs: Einfache Dateibehandlung
#include <iostream>
#include <fstream>
while (!Quelldatei.eof()) {
char c;
Quelldatei.get(c);
Zieldatei.put(c);
}
using namespace std;
int main() {
ifstream Quelldatei;
ofstream Zieldatei;
Kapitel 8
// zeichenweise kopieren
Quelldatei.close();
Zieldatei.close();
Quelldatei.open("quelle.txt");
if (!Quelldatei.is_open()) {
cerr << "konnte Datei nicht zum Lesen öffnen\n";
exit(1);
}
Zieldatei.open("ziel.txt");
if (!Zieldatei.is_open()) {
cerr << "konnte Datei nicht zum Schreiben öffnen\n";
exit(1);
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
55
}
offene
Datei
Start
aktuelle Position
eof() == true
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
56
14
Kapitel 8
Exkurs: C++ Strings
Kapitel 8
Exkurs: C++ Strings
Datendefinition / Initialisierung
Bisher:
Zeichenketten wie char str[20];
string s1;
// leerer String
→ Relikt aus C-Programmierung!
string s2 = "xyz";
// initialisieren mit C-String
→ bei größeren Programmen mühevoll, lästig, …
string s3 = s2;
// vollständige Kopie!
→ … und insgesamt fehlerträchtig!
string s4("abc");
// initialisieren mit C-String
string s5(s4);
// initialisieren mit C++-String
string s6(10, ‘*‘);
// ergibt String aus 10 mal *
string s7(1‚‘x‘);
// initialisieren mit einem char
string sx(‘x‘);
// FEHLER!
string s8("");
// leerer String
Jetzt:
Zeichenketten aus C++
→ sehr angenehm zu verwenden (keine 0 am Ende, variable Größe, …)
→ eingebaute (umfangreiche) Funktionalität
wie benötigen: #include <string> und
using namespace std;
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
57
Kapitel 8
Exkurs: C++ Strings
● Konvertierung C++-String nach C-String via c_str()
Aufgabe:
const char *Cstr = s2.c_str();
Gegeben sei eine Textdatei.
Häufigkeiten der vorkommenden Worte feststellen.
Alphabetisch sortiert ausgeben.
● Stringlänge length()
cout << s2.length();
● Index von Teilstring finden
int pos = s2.find(“yz“);
Strategische Überlegungen:
Lesen aus Textdatei → ifstream benutzen!
Sortierte Ausgabe → Binärbaum: schnelles Einfügen, sortiert „von selbst“
Worte vergleichen → C++ Strings verwenden!
● Strings addieren
● Strings vergleichen
if (s1 == s2) s3 += s2;
if (s3 < s8) flag = true;
Kapitel 8
Elementare Datenstrukturen
ADT Binäre Bäume: Anwendung
Eingebaute Funktionen
s1 = s2 + s3;
s4 = s2 + “hello“;
s5 += s4;
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
58
● substr(),
replace(),
erase(),
…
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
59
Programmskizze:
Jeweils ein Wort aus Datei lesen und in Binärbaum eintragen.
Falls Wort schon vorhanden, dann Zähler erhöhen.
Wenn alle Wörter eingetragen, Ausgabe (Wort, Anzahl) via Inorder-Durchlauf.
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
60
15
Kapitel 8
Elementare Datenstrukturen
struct BinBaum {
string wort;
int anzahl;
BinBaum *links, *rechts;
};
gelesenes Wort
wie oft gelesen?
BinBaum *Einlesen(string &dateiname) {
ifstream quelle;
quelle.open(dateiname.c_str());
if (!quelle.is_open()) return 0;
string s;
BinBaum *b = 0;
while (!quelle.eof()) {
quelle >> s;
b = Einfuegen(s, b);
}
quelle.close();
return b;
}
Datei öffnen
Worte einzeln
auslesen und im
Baum einfügen
Datei schließen +
Zeiger auf Baum
zurückgeben
Elementare Datenstrukturen
BinBaum *Einfuegen(string &s, BinBaum *b) {
if (b == 0) {
b = new BinBaum;
b->wort = s;
b->anzahl = 1;
b->links = b->rechts = 0;
return b;
}
if (b->wort < s)
b->rechts = Einfuegen(s, b->rechts);
else if (b->wort > s)
b->links = Einfuegen(s, b->links);
else b->anzahl++;
return b;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
61
Kapitel 8
Elementare Datenstrukturen
Kapitel 8
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
62
Elementare Datenstrukturen
Kapitel 8
Hauptprogramm:
void Ausgabe(BinBaum *b) {
if (b == 0) return;
Ausgabe(b->links);
cout << b->anzahl << " " << b->wort.c_str() << endl;
Ausgabe(b->rechts);
#include <iostream>
#include <fstream>
#include <string>
}
using namespace std;
Dies ist die Inorder-Ausgabe.
Präorder:
Postorder:
cout …;
Ausgabe(…);
Ausgabe(…);
Ausgabe(…);
Ausgabe(…);
cout …;
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
63
int main() {
string s("quelle.txt");
BinBaum *b = Einlesen(s);
Ausgabe(b);
return 0;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
64
16
Kapitel 8
Elementare Datenstrukturen
z.B. Ausdruck des
Knotenwertes
Durchlaufstrategien:
Kapitel 8
Elementare Datenstrukturen
Breitensuche
Beispiel: eingegebene Zahlenfolge 17, 4, 36, 2, 8, 40, 19, 6, 7, 37
● Tiefensuche („depth-first search“, DFS)
- Präorder
Vor (prä) Abstieg in Unterbäume die „Knotenbehandlung“ durchführen
17
- Postorder
Nach (post) Abstieg in bzw. Rückkehr aus Unterbäumen die
„Knotenbehandlung“ durchführen
- Inorder
Zwischen zwei Abstiegen „Knotenbehandlung“ durchführen
4
36
2
8
19
40
6
37
Ausgabe:
17, 4, 36, 2, 8, 19, 40, 6, 37, 7
● Breitensuche („breadth-first search“, BFS; auch: „level search“)
7
auf jeder Ebene des Baumes werden Knoten abgearbeitet,
bevor in die Tiefe gegangen wird
Implementierung: → Praktikum!
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
65
Kapitel 8
Elementare Datenstrukturen
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
66
Kapitel 8
Elementare Datenstrukturen
Definition
ADT Graph
EIn Graph G = (V, E) besteht aus einer Menge von Knoten V („vertex, pl. vertices“)
● Verallgemeinerung von (binären) Bäumen
und einer Menge von Kanten E („edge, pl. edges“) mit E ⊆ V x V.
● Wichtige Struktur in der Informatik
● Zahlreiche Anwendungsmöglichkeiten
1
- Modellierung von Telefonnetzen, Versorgungsnetzwerken, Straßenverkehr, …
2
- Layout-Fragen bei elektrischen Schaltungen
3
- Darstellung sozialer Beziehungen
6
Δ(G)
δ(G)
5
Schlinge
7
= 6
= 3
4
- etc.
● Viele Probleme lassen sich als Graphenprobleme „verkleiden“
und dann mit Graphalgorithmen lösen!
Eine Kante (u, v) heißt Schlinge (“loop“), wenn u = v.
Der Grad („degree“) eines Knotens v ∈ V ist die Anzahl der zu ihm inzidenten
Kanten: deg(v) = | { (a, b) ∈ E : a = v oder b = v } | .
Maxgrad von G ist Δ(G) = max { deg(v) : v ∈ V }
Mingrad von G ist δ(G) = min { deg(v) : v ∈ V }
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
67
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
68
17
Kapitel 8
Elementare Datenstrukturen
Definition
Kapitel 8
Elementare Datenstrukturen
Darstellung im Computer
Für vi ∈ V heißt (v0, v1, v2, …, vk) ein Weg oder Pfad in G,
wenn (vi,vi+1) ∈ E für alle i = 0, 1, …, k-1.
● Adjazenzmatrix A mit aij =
Die Länge eines Pfades ist die Anzahl seiner Kanten.
Ein Pfad (v0, v1, v2, …, vk) mit v0 = vk wird Kreis genannt.
Distanz dist(u, v) von zwei Knoten ist die Länge des kürzesten Pfades von u nach v.
Durchmesser diam(G) eines Graphes G ist das Maximum über alle Distanzen:
1 falls (vi,vj) ∈ E
0 sonst
Problem:
Da | E | ≤ | V |2 = n2 ist Datenstruktur ineffizient (viele Nullen)
wenn | E | verschwindend klein.
diam(G) = max { dist(u, v) : (u, v) ∈ V x V }.
Graph ist zusammenhängend, wenn ∀ u, v ∈ V mit u ≠ v einen Pfad gibt.
● Adjazenzlisten:
Für jeden Knoten v eine (Nachbarschafts-) Liste L(v) mit
G heißt Baum gdw. G zusammenhängend und kreisfrei.
L(v) = { u ∈ V : (v, u) ∈ E }
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
69
Kapitel 8
Elementare Datenstrukturen
c
Beispiel
b
a
typedef unsigned int uint;
L(a) = ( b, e )
L(b) = ( a, c, d )
L(c) = ( b, d )
L(d) = ( b, c, e )
L(e) = ( a, d )
e
Kapitel 8
Elementare Datenstrukturen
Mögliche Funktionalität
Adjazenzlisten
d
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
70
typedef Datentyp TypName;
ADT Liste
Graph *createGraph(uint NoOfNodes);
void addEdge(Graph *graph, uint Node1, uint Node2);
Adjazenzmatrix
a
b
c
d
e
void deleteEdge(Graph *graph, uint Node1, uint Node2);
a
0
1
0
0
1
bool hasEdge(Graph *graph, uint Node1, uint Node2);
b
1
0
1
1
0
c
0
1
0
1
0
d
0
1
1
0
1
e
1
0
0
1
0
uint noOfEdges();
Array[][]
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
71
uint noOfNodes();
void printGraph();
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
72
18
uint noOfNodes(Graph *graph) {
return graph == 0 ? 0 : graph->NoOfNodes;
}
Graph *createGraph(uint NoOfNodes) {
Graph *graph = new Graph;
graph->NoOfNodes = NoOfNodes;
graph->AdjList = new List*[NoOfNodes];
for (uint i = 0; i < NoOfNodes; i++)
graph->AdjList[i] = create();
return graph;
}
Speicher reservieren
initialisieren!
uint noOfEdges(Graph *graph) {
if (noOfNodes(graph) == 0) return 0;
unsigned int cnt = 0, i;
for (i = 0; i < noOfNodes(graph); i++)
cnt += size(graph->AdjList[i]);
return cnt / 2;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
73
Elementare Datenstrukturen
List
// Zeiger auf Zeiger auf Listen
List
List
List
// Zeiger auf Zeiger auf Listen
AdjList
List
struct Graph {
uint NoOfNodes;
List **AdjList;
};
List
AdjList
List
struct Graph {
uint NoOfNodes;
List **AdjList;
};
Kapitel 8
Elementare Datenstrukturen
List
Kapitel 8
Elementare Datenstrukturen
Kapitel 8
Ineffizient!
Falls häufig
benutzt, dann
besser Zähler
NoOfEdges im
struct Graph
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
74
Kapitel 8
Elementare Datenstrukturen
AdjList
struct Graph {
uint NoOfNodes;
List **AdjList;
};
AdjList
// Zeiger auf Zeiger auf Listen
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
75
List
List
List
bool hasEdge(Graph *graph, uint Node1, uint Node2) {
if (graph == 0) error("no graph");
uint n = noOfNodes(graph);
if (Node1 >= n || Node2 >= n) error("invalid node");
return is_elem(Node2, graph->AdjList[Node1]);
}
void addEdge(Graph *graph, uint Node1, uint Node2) {
if (!hasEdge(graph, Node1, Node2)) {
append(Node2, graph->AdjList[Node1]);
append(Node1, graph->AdjList[Node2]);
}
}
List
List
List
List
// Zeiger auf Zeiger auf Listen
List
struct Graph {
uint NoOfNodes;
List **AdjList;
};
void printGraph(Graph *graph) {
if (noOfNodes(graph) == 0) return;
unsigned int i, n = noOfNodes(graph);
for (i = 0; i < n; i++) {
cout << i << ": ";
printList(graph->AdjList[i]);
}
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
76
19
Kapitel 8
Elementare Datenstrukturen
AdjList
List
List
List
// Zeiger auf Zeiger auf Listen
List
struct Graph {
uint NoOfNodes;
List **AdjList;
};
Kapitel 8
Elementare Datenstrukturen
Graph *clearGraph(Graph *graph) {
if (graph == 0) return 0;
unsigned int cnt = 0, i;
for (i = 0; i < noOfNodes(graph); i++)
clearList(graph->AdjList[i]);
delete[] graph->AdjList;
delete graph;
return 0;
}
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
77
int main() {
Graph *graph = createGraph(7);
addEdge(graph, 0, 2);
addEdge(graph, 1, 2);
addEdge(graph, 3, 2);
addEdge(graph, 2, 4);
addEdge(graph, 4, 6);
addEdge(graph, 6, 5);
addEdge(graph, 5, 2);
addEdge(graph, 2, 3);
addEdge(graph, 3, 0);
addEdge(graph, 6, 3);
addEdge(graph, 0, 1);
cout << "nodes: " << noOfNodes(graph)
cout << "edges: " << noOfEdges(graph)
printGraph(graph);
graph = clearGraph(graph);
cout << "nodes: " << noOfNodes(graph)
cout << "edges: " << noOfEdges(graph)
return 0;
}
0
6
1
5
2
4
<< endl;
<< endl;
<< endl;
<< endl;
nodes:
edges:
0: 2 1
1: 2
2: 4 3
3: 2 0
4: 6
5: 2
6: 5 3
nodes:
edges:
3
7
11
0
0
G. Rudolph: Einführung in die Programmierung ▪ WS 2008/09
78
20
Herunterladen