Algorithmen und Datenstrukturen Elementare Datenstrukturen

Werbung
Algorithmen und
Datenstrukturen
Dipl. Inform. Andreas Wilkens
[email protected]
Elementare Datenstrukturen
Array
Linked List
Stack
Queue
Tree
(Feld)
(Verkettete Liste)
(Stapel)
(Warteschlange)
(Baum)
Einschub: Rekursion
2
1
Rekursion
Rekursion ist keine elementare
Datenstruktur, sondern eine
Programmiertechnik.
3
Rekursion
ein rekursives Programm ruft sich selbst
auf bzw.
eine rekursive Funktion ruft sich selbst
auf
zu jeder Rekursion gehört eine
Abbruchbedingung
4
2
Beispiele für Rekursionen
Das Fernsehbild, das sich selbst enthält
die Fakultätsfunktion n! aus der
Mathematik
5
Fakultätsfunktion n!
n! = n * (n-1)!
für alle natürlichen Zahlen n
größergleich 1
0! = 1
(wichtig: die Abbruchbedingung!)
6
3
Rekursion – Iteration
Rekursion
ruft sich selbst auf
enthält Abbruchbedingung
Iteration
wiederholtes Durchlaufen von Anweisungen
Abbruch z.B. nach fest vorgegebener
Durchlaufzahl oder durch erfüllte
Abbruchbedingung
Beispiel: Schleifen (for, while)
7
Terminierung
Eine Rekursion muß genauso
terminieren wie eine Iteration
Bei Rekursion muß irgendwann ein
Zustand erreicht werden, in dem kein
weiterer rekursiver Aufruf erzeugt wird
8
4
direkt und indirekt
direkte Rekursion
Funktion ruft sich selbst auf
indirekte Rekursion
func_a() ruft func_b() auf, die wieder
func_a() aufruft usw.
9
indirekte Rekursion
void func_a(void)
{
..
func_b();
..
}
void func_b(void)
{
..
func_a();
..
}
void main(void)
{
..
func_a();
..
}
10
5
Speicherarten im PC
Codebereich
Register
nimmt globale Variablen auf
Stack
dienen als Ablage zur Programmsteuerung
Globaler Speicher
enthält Programmcode
nimmt u.a. lokale Variablen auf
Heap
restlicher Speicher, der vom Programmierer mit „new“
angefordert werden kann
11
Stackframe
beim Aufruf einer Funktion wird ein
Stackframe auf dem Laufzeitstack
angelegt
dieser enthält
die lokalen Variablen
die Parameter
Rückkehrinformationen
wird die Funktion beendet, wird der
Stackframe wieder vom Laufzeitstack
entfernt
12
6
Stackframes bei Rekursion
Laufzeitstack mit 4 rekursiven Instanzen
Top
rekur(4)
rekur(3)
rekur(2)
rekur(1)
main
Bottom
13
Teile und herrsche
viele rekursive Programme wenden das
sogenannte „Teile-und-herrsche“Prinzip an
engl.: divide and conquer
dabei erzeugt die rekursive Funktion
zwei rekursive Aufrufe von sich selbst,
jeweils mit der Hälfte der Eingabewerte
14
7
Beispiel für Teile und herrsche
gegeben sei ein Maßstab
dieser soll liniert werden mit
Teilstrichen (siehe Lineal)
die Teilstriche sollen verschiedene
Höhen haben
15
Funktion „liniere“
//
//
//
//
//
zeichnet Teilstriche an eine vorgegebene Strecke
innerhalb der angegebenen Grenzen l und r
Schema: Mittelstrich erhält die Höhe h,
die Mittelstriche der entfallenden linken und
rechten Hälfte erhalten die Höhe h-1 usw.
void liniere(int l, int r, int h) {
int m = (l+r)/2;
markiere(m, h);
if (h>1) {
liniere(l, m, h-1);
liniere(m, r, h-1);
}
}
16
8
Merkmale der Rekursion
Rekursion
ruft sich selbst auf
enthält Abbruchbedingung
void liniere(int l, int r, int h) {
int m = (l+r)/2;
markiere(m, h);
if (h>1) {
liniere(l, m, h-1);
liniere(m, r, h-1);
}
}
17
Rekursionsbaum für einen
Maßstab der Länge 8
18
9
Noch ein Rekursionsbeispiel
Türme von Hanoi
19
Türme von Hanoi
Scheiben unterschiedlicher Größen
sollen von einem Lagerplatz zu einem
anderen Transportiert werden
Regeln:
es darf nur eine Scheibe zur Zeit
transportiert werden
es darf nur eine kleinere auf eine größere
Scheibe gelegt werden
es steht ein zusätzlicher Hilfslagerplatz zur
Verfügung
20
10
Beispiellösung
Algorithmus zum Lösen des Problems
Türme von Hanoi
Siehe
hanoi.cpp
21
Ablauf für 3 Scheiben:
main: Anzahl der Scheiben: 3
towers( 3, A, B, C );
towers( 2, A, C, B );
towers( 1, A, B, C );
Ausgabe: Scheibe 1 von A nach
Ausgabe: Scheibe 2 von A nach
towers( 1, C, A, B );
Ausgabe: Scheibe 1 von C nach
Ausgabe: Scheibe 3 von A nach
towers( 2, B, A, C );
towers( 1, B, C, A );
Ausgabe: Scheibe 1 von B nach
Ausgabe: Scheibe 2 von B nach
towers( 1, A, B, C );
Ausgabe: Scheibe 1 von A nach
Fertig
C
B
B
C
A
C
C
22
11
Rekursionsbaum
Wie sieht der Rekursionsbaum für den
Aufruf towers(3, A, B, C) aus?
23
Türme von Hanoi
die rekursive Funktion „towers“ besteht
nur aus sehr wenigen Zeilen
trotzdem kann damit ein recht hoher
Aufwand geleistet werden
24
12
Frage
Wie kommt man vom Problem „Türme
von Hanoi“ zum Algorithmus?
25
Antwort
durch nachdenken
und vereinfachen
26
13
Einfachster Fall
Türme von Hanoi mit nur einer Scheibe
Transportiere die Scheibe vom Quellplatz
zum Zielplatz
Türme von Hanoi mit zwei Scheiben
Transportiere die obere, kleinere Scheibe
vom Quellplatz zum Hilfplatz
Transportiere die untere, größere Scheibe
vom Quellplatz zum Zielplatz
Transportiere die kleinere Scheibe vom
Hilfsplatz zum Zielplatz
27
Allgemeiner Fall
(für die größte Scheibe)
Um die unterste (größte) Scheibe vom
Startplatz zum endgültigen Zielplatz
transportieren zu können, muss man
1.
2.
Erst alle anderen Scheiben aus dem Weg
räumen (zum Hilfsplatz bringen)
Jetzt die größte Scheibe zum Zielplatz
bringen.
28
14
Allgemeiner Fall
(für alle Scheiben)
Um alle Scheiben vom Startplatz zum
Zielplatz zu transportieren, muss man
1.
2.
3.
Alle Scheiben (außer der größten) aus dem Weg
räumen (zum Hilfsplatz bringen)
Jetzt die größte Scheibe zum Zielplatz bringen.
Dann alle anderen (vorher aus dem Weg
geräumten Scheiben) wieder oben auf die
größte Scheibe legen.
(dies gilt immer, unabhängig von der Anzahl der Scheiben)
29
Funktion „towers“
Machen Sie sich die Bedeutung der
(nur) drei Zeilen der Funktion „towers“
deutlich!
Die drei genannten Punkte auf der
vorangegangenen Folie entsprechen jeder
genau einer Zeile der towers-Funktion!
30
15
Durchlaufen von Binärbäumen
Um z.B. alle Werte eines Binärbaumes
auszugeben, muß dieser durchlaufen
werden.
Das Durchlaufen nennt man auch
“traversieren”.
31
Durchlaufen von Binärbäumen
1.
2.
3.
4.
5.
6.
Verschiedene Möglichkeiten:
WLR – Wurzel-Links-Rechts
WRL – Wurzel-Rechts-Links
LWR – Links-Wurzel-Rechts
LRW – Links-Rechts-Wurzel
RWL – Rechts-Wurzel-Links
RLW – Rechts-Links-Wurzel
32
16
Beispiel
20
14
17
8
3
33
11
26
39
30
33
Auf einen Blick
derselbe Baum,
verschiedene Durchlaufvarianten:
WLR:
LWR:
LRW:
RWL:
20, 14, 8, 3, 11, 17, 33, 26, 30, 39
3, 8, 11, 14, 17, 20, 26, 30, 33, 39
3, 11, 8, 17, 14, 30, 26, 39, 33, 20
39, 33, 30, 26, 20, 17, 14, 11, 8, 3
34
17
Erkenntnis
Durchläuft man einen Binärbaum nach
LWR (Inorder), so erhält man eine
aufsteigende Sortierung.
Durchläuft man einen Binärbaum nach
RWL, so erhält man eine absteigende
Sortierung.
35
Programmieren
Das Durchlaufen eines binären
Suchbaums kann mittels Rekursion sehr
einfach programmiert werden.
Wiederholung Rekursion:
eine rekursive Funktion ruft sich selbst auf
zu jeder Rekursion gehört eine
Abbruchbedingung
36
18
Datenstruktur eines Knotens
struct node {
ItemType wert;
struct node left, right;
};
37
Preorder (WLR) programmiert
void preorder(node *root)
{
/* Was muß hier stehen ?*/
}
38
19
Preorder (WLR) programmiert
void preorder(node *root)
{
if (root!=NULL)
{
print_value(root);
preorder(root->left);
preorder(root->right);
}
}
39
Inorder (LWR) programmiert
void inorder(node *root)
{
if (root!=NULL)
{
inorder(root->left);
print_value(root);
inorder(root->right);
}
}
40
20
Postorder (LRW)
programmiert
void postorder(node *root)
{
if (root!=NULL)
{
postorder (root->left);
postorder (root->right);
print_value(root);
}
}
41
Löschen im binären Suchbaum
Neben dem Einfügen von Knoten ist
auch das Löschen von Knoten zu
betrachten.
Dabei sind verschiedene Fälle zu
berücksichtigen.
42
21
Löschen im binären Suchbaum
20
14
17
8
3
33
26
39
30
11
Fall 1:
Löschen eines Blattes,
z.B. Knoten mit Wert 30.
Trivial!
43
Löschen im binären Suchbaum
20
14
17
8
3
33
11
26
39
30
Fall 2:
Löschen eines Knotens mit nur einem
Nachfolger (Knoten mit Wert 26)
44
22
Löschen im binären Suchbaum
Fall 2:
Der Nachfolger (-Teilbaum) kann den zu
löschenden Knoten ersetzen.
45
Löschen im binären Suchbaum
Fall 2:
Vorgehen:
20
33
17
26
39
30
je ein Arbeitspointer verweist auf Vaterknoten 33
und auf Löschknoten 26
left-Pointer im Vaterknoten 33 wird auf den
Nachfolgerteilbaum
(Knoten 30) des
Löschknotens umgesetzt
Löschknoten 26 wird
entsorgt, nachdem Wert
26 entnommen wurde
46
23
Löschen im binären Suchbaum
20
14
17
8
3
33
11
26
39
30
Fall 3:
Löschen eines Knotens mit zwei Nachfolgeteilbäumen, z.B. Wurzelknoten 20
47
Löschen im binären Suchbaum
Fall 3, Lösung a:
einer der beiden Teilbäume ersetzt den
Löschknoten
der zweite Teilbaum wird in geeigneter
Weise unter dem ersten Teilbaum gehängt
48
24
Löschen im binären Suchbaum
Fall 3, Lösung a:
Ergebnis:
Teilbaum mit Wurzel
14 ersetzt
Löschknoten 20
Teilbaum mit Wurzel
33 wird als rechter
Nachfolger von
Knoten 17
eingehängt
49
Löschen im binären Suchbaum
Wie findet man den
richtigen Punkt zum
Einhängen des
Teilbaums mit
Wurzel 33?
50
25
Löschen im binären Suchbaum
Fall 3, Nachteil von Lösung a:
die Höhe des Baums wird größer
im ungünstigsten Fall kann sich die Höhe
verdoppeln
51
Löschen im binären Suchbaum
20
14
17
8
3
33
11
26
39
30
Fall 3:
Löschen eines Knotens mit zwei Nachfolgeteilbäumen, z.B. Wurzelknoten 20
52
26
Löschen im binären Suchbaum
Fall 3, Lösung b:
Der zu löschende Knoten wird durch den
Knoten mit dem in der Sortierreihenfolge
nächstgrößeren oder nächstkleineren Wert
ersetzt
Achtung, der nächstgrößere oder
nächstkleinere Knoten kann maximal einen
Nachfolger haben, deshalb hierbei
vorgehen wie in Fall 2, Löschen eines
Knotens mit einem Nachfolger
53
Löschen im binären Suchbaum
Ergebnis Fall 3, Lösung b:
Knoten 20 wurde durch nächstgrößeren
(Knoten 26) ersetzt
Knoten 26 wurde an der ursprünglichen
Stelle gelöscht
26
14
17
8
3
33
11
30
39
54
27
Löschen im binären Suchbaum
Fall 3, Vorteil Lösung b:
die Höhe des Baums hat sich nicht
vergrößert
26
14
17
8
3
33
30
39
11
55
Löschen im binären Suchbaum
Ablauf Fall 3, Lösung b:
je ein Arbeitspointer verweist auf den
Löschknoten (20) und auf dessen
Vorgänger (hier nicht existent, deshalb
root-Pointer verwenden)
nächstgrößerer Knoten (26) wird lokalisiert
und Arbeitspointer auf ihn und seinen
Vorgänger (33) gesetzt
Knoten 26 wird herausgeschnitten und
durch seinen Nachfolgerknoten 30 ersetzt56
28
Löschen im binären Suchbaum
left-Pointer im Vaterknoten 33 wird auf
Knoten 30 umgebogen
left-Pointer von 26 wird auf 14 gesetzt
right-Pointer von 26 wird auf 33 gesetzt
Vorgängerknoten des Löschknotens (hier
der root-Pointer) wird auf die neue Wurzel
26 umgesetzt
Wert 20 der alten Wurzel wird entnommen
und der Knoten gelöscht
fertig
57
Löschen im binären Suchbaum
Fall 3, Fazit von Lösung b:
deutlich aufwendiger zu programmieren als
Lösung a
aber auch deutliche Vorteile beim dadurch
entstehenden Baum, insbesondere bei der
Höhe
deshalb wird Lösung b in der Regel
bevorzugt
58
29
Herunterladen