Algorithmen und Datenstrukturen (für ET/IT

Werbung
Algorithmen und Datenstrukturen (für ET/IT)
Sommersemester 2014
Dr. Tobias Lasser
Computer Aided Medical Procedures
Technische Universität München
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
2
Was sind primitive Datentypen?
Primitive Datentypen
Wir bezeichnen grundlegende, in Programmiersprachen eingebaute
Datentypen als primitive Datentypen.
Durch Kombination von primitiven Datentypen lassen sich
zusammengesetzte Datentypen bilden.
Beispiele für primitive Datentypen in C++:
• int für ganze Zahlen
• float für floating point Zahlen
• bool für logische Werte
3
Bits und Bytes
Bit 0
Bit 7
1 Byte = 8 Bit
Bytes als Maßeinheit für Speichergrössen (nach IEC, traditionell):
• 210 Bytes = 1024 Bytes = 1 KiB, ein Kilo Byte (Kibi Byte)
• 220 Bytes = 1 MiB, ein Mega Byte (bzw. MebiByte)
• 230 Bytes = 1 GiB, ein Giga Byte (bzw. GibiByte)
• 240 Bytes = 1 TiB, ein Tera Byte (bzw. TebiByte)
• 250 Bytes = 1 PiB, ein Peta Byte (bzw. PebiByte)
• 260 Bytes = 1 EiB, ein Exa Byte (bzw. ExbiByte)
4
Bits und Bytes
Bit 0
Bit 7
1 Byte = 8 Bit
Bytes als Maßeinheit für Speichergrössen (nach IEC, metrisch):
• 103 Bytes = 1000 Bytes = 1 kB, ein kilo Byte (großes B)
• 106 Bytes = 1 MB, ein Mega Byte
• 109 Bytes = 1 GB, ein Giga Byte
• 1012 Bytes = 1 TB, ein Tera Byte
• 1015 Bytes = 1 PB, ein Peta Byte
• 1018 Bytes = 1 EB, ein Exa Byte
Hinweis: auch Bits werden als Maßangabe verwendet, z.B. 16 Mbit
oder 16 Mb (kleines b).
5
1001110010001
0101001001000100010001
001000101010100100100010001
1110010001010101001001000100011
10010001
010101
00100100
01001110
00101
00010011
001001110
10011
011100101
10010001010
0100110
00111010111
010011100001001110000100111011101100110
110100 1001110010100011010001110 101001
11010 01001110010001110101100 01011
10011 000100111010010100111 10100
010100 01001110100101100 001001
10101110
010010100
10011101
100101010
010011101
001001110100110110010
0010011101011
6
Primitive Datentypen in C++-ähnlichen Sprachen
Wir betrachten im Detail primitive Datentypen für:
1
natürliche Zahlen (unsigned integers)
2
ganze Zahlen (signed integers)
3
floating point Zahlen (floats)
7
Zahldarstellung
• Dezimalsystem:
• Basis x = 10
• Koeffizienten cn ∈ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
• Beispiel: 12310 = 1 · 102 + 2 · 101 + 3 · 100
• Binärsystem:
• Basis x = 2
• Koeffizienten cn ∈ {0, 1}
• Beispiel: 11012 = 1 · 23 + 1 · 22 + 0 · 21 + 1 · 20 = 1310
8
Zahldarstellung
• Oktalsystem:
• Basis x = 8 (= 23 )
• Koeffizienten cn ∈ {0, 1, 2, 3, 4, 5, 6, 7}
• Beispiel: 1738 = 1 · 82 + 7 · 81 + 3 · 80 = 12310
• Hexadezimalsystem:
• Basis x = 16 (= 24 )
• Koeffizienten cn ∈ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C , D, E , F }
• Beispiel: 7B 16 = 7 · 161 + B · 160 = 12310
9
Wie viele Ziffern pro Zahl?
Problem
Gegeben Zahl z ∈ N, wie viele Ziffern m werden bezüglich Basis x
benötigt?
Lösung
m = blogx (z)c + 1
Erläuterung: (a ∈ R)
• bac = floor(a) = größte ganze Zahl kleiner gleich a
• dae = ceil(a) = kleinste ganze Zahl größer gleich a
a − 1 < bac ≤ a ≤ dae < a + 1
ln(z)
• logx (z) = ln(x)
, wobei ln“ der natürliche Logarithmus ist
”
10
Wie viele Ziffern pro Zahl?
Lösung
m = blogx (z)c + 1
Beispiele: z = 123
• Basis x = 10:
m = blog10 (123)c + 1 = b2.0899 . . .c + 1 = 3
• Basis x = 2:
m = blog2 (123)c + 1 = b6.9425 . . .c + 1 = 7
• Basis x = 8:
m = blog8 (123)c + 1 = b2.3141 . . .c + 1 = 3
• Basis x = 16:
m = blog16 (123)c + 1 = b1.7356 . . .c + 1 = 2
11
Größte Zahl pro Anzahl Ziffern?
Problem
Gegeben Basis x und m Ziffern, was ist die größte darstellbare
Zahl?
Lösung
zmax = x m − 1
Beispiele:
• x = 2, m = 4:
zmax = 24 − 1 = 15 = 11112
• x = 2, m = 8:
zmax = 28 − 1 = 255 = 111111112
• x = 16, m = 2:
zmax = 162 − 1 = 255 = FF16
12
Natürliche Zahlen in C++-ähnlichen Sprachen
Natürliche Zahlen
In Computern verwendet man Binärdarstellung mit einer fixen
Anzahl Ziffern (genannt Bits).
Die primitiven Datentypen für natürliche Zahlen sind:
• 8 Bits (ein Byte), darstellbare Zahlen: {0, . . . , 255}
in C++: unsigned char
• 16 Bits, darstellbare Zahlen: {0, . . . , 65535}
in C++: unsigned short
• 32 Bits, darstellbare Zahlen: {0, . . . , 4294967295}
in C++: unsigned long
• 64 Bits, darstellbare Zahlen: {0, . . . , 264 − 1}
in C++: unsigned long long
13
Negative Zahlen
Darstellung durch 2-Komplement
Beispiel für 4 Bits (darstellbare Zahlen: 24 = 16):
-9 -8 -7 -6 -5 -4 -3 -2 -1 0
1 2 3 4 5 6
7 8 9
Damit erhält man:
0000
0001
0010
0011
=
=
=
=
+0
+1
+2
+3
0100
0101
0110
0111
=
=
=
=
+4
+5
+6
+7
1000
1001
1010
1011
=
=
=
=
-8
-7
-6
-5
1100
1101
1110
1111
=
=
=
=
-4
-3
-2
-1
Das erste Bit ist also das Vorzeichen!
14
2-Komplement Darstellung I
2-Komplement Darstellung
Sei x ∈ N, x > 0. Die 2-Komplement Darstellung −xz von −x
mittels n Bits ist gegeben durch
−xz = 2n − x.
Vorheriges Beispiel war: −5 = 1011, also x = 5 und n = 4.
Nun:
−5z = 24 − 5 = 16 − 5 = 11 = 10112
15
2-Komplement Darstellung II
Sei bn bn−1 . . . b1 eine Bitfolge.
• (bn bn−1 . . . b1 )z sei der Zahlwert in 2-Komplement Darstellung
• für positive Zahlen von 0 bis 2n−1 − 1 entspricht
(bn bn−1 . . . b1 )z der Binärdarstellung:
(0bn−1 . . . b1 )z = (0bn−1 . . . b1 )2
• für negative Zahlen von −2n−1 bis −1 gilt
(1bn−1 . . . b1 )z = −2n−1 + (0bn−1 . . . b1 )2
• allgemein:
(bn bn−1 . . . b1 )z = bn · (−2n−1 ) + (bn−1 . . . b1 )2
16
Eigenschaften 2-Komplement
• Für n ∈ N gilt
(111 . . . 11)z = (−2n−1 ) + 2n−2 + . . . + 21 + 20
= −2n−1 + (2n−1 − 1)
= −1
• Um −x aus x in 2-Komplement Darstellung zu erhalten:
Bilde bitweises Komplement und addiere 1.
• Beispiel: Negatives von 6 = (0110)2 mit n = 4
−6 = (0̄1̄1̄0̄)z + 1 = (1001)z + 1 = (1010)z
• und zurück:
6 = (1̄0̄1̄0̄)z + 1 = (0101)z + 1 = (0110)z
17
Ganze Zahlen in C++-ähnlichen Sprachen
Ganze Zahlen
Die primitiven Datentypen für ganze Zahlen sind:
• 8 Bits: unsigned char {0, . . . , 255}
signed char {−128, . . . , 127}
• 16 Bits: unsigned short {0, . . . , 65535}
signed short {−32768, . . . , 32767}
• 32 Bits: unsigned long {0, . . . , 232 − 1}
signed long {−231 , . . . , 231 − 1}
• 64 Bits: unsigned long long {0, . . . , 264 − 1}
signed long long {−263 , . . . , 263 − 1}
• signed kann weggelassen werden (ausser bei char!)
• unsigned int und signed int sind je nach System 16, 32
oder 64 Bit
18
Rationale Zahlen I
Festkomma Darstellung:
• Komma an fester Stelle in Zahl
• Beispiel mit n = 32:
32
1
ganzzahliger Anteil
gebrochener Anteil
Komma
• Nachteile:
• weniger große Zahlen darstellbar
• feste Genauigkeit der Nachkommastellen
19
Rationale Zahlen II
32
1
ganzzahliger Anteil
gebrochener Anteil
Komma
• Interpretation für r ∈ Q:
r = cn · 2n + . . . + c0 · 20 + c−1 2−1 + . . . + c−m · 2−m
mit n Vorkomma- und m Nachkomma-Ziffern
• Beispiel:
11.012 = 1 · 21 + 1 · 20 + 0 · 2−1 + 1 · 2−2
=2+1+0+
1
4
= 3.2510
20
Floating Point Zahlen I
Wissenschaftliche Notation:
• x = a · 10b für x ∈ R, wobei:
• a ∈ R mit 1 ≤ |a| < 10
• b∈Z
• Beispiele:
• −2.7315 · 102 ◦ C
• 1.4 · 109 Hz
absoluter Nullpunkt
Taktfrequenz A7 Prozessor
• Drei Bestandteile:
• Vorzeichen
• Mantisse |a|
• Exponent b
• Problem: bei fester Länge der Mantisse (z.B. 3 Ziffern)
• zwischen 1.23 · 104 = 12300 und 1.24 · 104 = 12400 keine Zahl
darstellbar!
21
Floating Point Zahlen II
1 Bit
1 Bit
11 Bit
8 Bit
52 Bit
23 Bit
V
Exponent E
Mantisse M
64 Bit double
32 Bit float
• wissenschaftliche Darstellung mit Basis 2
f = (−1)V · (1 + M) · 2E −bias
• Vorzeichen Bit V
• Mantisse M hat immer die Form 1.abc, also wird erste Stelle
weggelassen ( hidden bit“)
”
• Exponent E wird vorzeichenlos abgespeichert, verschoben um
bias
• bei 32 bit float: bias = 127, bei 64 bit double: bias = 1023
22
Floating Point Zahlen III
Übliche Floating Point Formate:
Bit
Vorz.
Exponent
Mantisse
32
1 Bit
8 Bit
23 Bit
gültige
Dezimalst.
∼7
64
1 Bit
11 Bit
52 Bit
∼ 15
80
1 Bit
15 Bit
64 Bit
∼ 19
darstellbarer
Bereich
±2 · 10−38 bis
± 2 · 1038
±2 · 10−308 bis
± 2 · 10308
±1 · 10−4932 bis
± 1 · 104932
In C++:
float (32 Bit), double (64 Bit), long double (80 Bit)
23
Vorsicht mit Floating Point!
Floating Point Zahlen sind bequem, aber Vorsicht!
• Viele Dezimalzahlen haben keine Floating Point Darstellung
• Beispiel: 0.110 = 0.0001100110011 . . .2 (periodisch)
• Durch feste Länge der Mantisse sind ebenfalls viele Zahlen
nicht darstellbar
• Beispiel: mit 3 Ziffern Mantisse ist zwischen 1.23 · 104 = 12300
und 1.24 · 104 = 12400 keine Zahl darstellbar!
• Kritisch sind Vergleiche von Floating Point Zahlen
• Beispiel: (0.1 + 0.2 == 0.3) ist meist FALSE!
• Zins-Berechnungen und dergleichen NIE mit Floating Point
Zahlen!
• Stattdessen: spezielle Bibliotheken wie GMP
24
Definition Datenstruktur
Definition Datenstruktur (nach Prof. Eckert)
Eine Datenstruktur ist eine
• logische Anordnung von Datenobjekten,
• die Informationen repräsentieren,
• den Zugriff auf die repräsentierte Information über
Operationen auf Daten ermöglichen und
• die Information verwalten.
Zwei Hauptbestandteile:
• Datenobjekte
• z.B. definiert über primitive Datentypen
• Operationen auf den Objekten
• z.B. definiert als Funktionen
25
Primitive Datentypen in C++
• Natürliche Zahlen, z.B. unsigned short, unsigned long
• Wertebereich: bei n Bit von 0 bis 2n − 1
• Operationen: +, -, *, /, %, <, ==, !=, >
• Ganze Zahlen, z.B. int, long
• Wertebereich: bei n Bit von −2n−1 bis 2n−1 − 1
• Operationen: +, -, *, /, %, <, ==, !=, >
• Floating Point Zahlen, z.B. double, float
• Wertebereich: abhängig von Größe
• Operationen: +, -, *, /, <, ==, !=, >
• Logische Werte, bool
• Wertebereich: true, false
• Operationen: &&, ||, !, ==, !=
26
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
27
Definition Feld
Definition Feld
Ein Feld A ist eine Folge von n Datenelementen (di )i=1,...,n ,
A = d1 , d2 , . . . , dn
mit n ∈ N0 .
Die Datenelemente di sind beliebige Datentypen (z.B. primitive).
Beispiele:
• A sind die natürlichen Zahlen von 1 bis 10, aufsteigend
geordnet:
A = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
• Ist n = 0, so ist das Feld leer.
28
Feld als sequentielle Liste
Repräsentation von Feld A als sequentielle Liste (oder Array)
• feste Anzahl n von Datenelementen
• zusammenhängend gespeichert
• in linearer Reihenfolge mit Index
• Zugriff auf i-tes Element über Index i: A[i]
Feld A:
A[n-1]
A[n-2]
...
A[2]
A[1]
A[0]
Achtung: Indizierung startet meist bei 0!
29
Beispiel sequentielle Liste
Feld A:
A[2]
A[1]
A[0]
15
8
0
• Feld-Deklaration in C++ (optionales Beispiel):
std :: vector < int > A {0 , 8 , 15};
• Zugriff auf Elemente:
A [0] = 4;
A [1] = 9;
A [2] = A [0] + A [1]; // nun : A [2] == 13
30
Eigenschaften sequentielle Liste
Feld A mit Länge n als sequentielle Liste (Array)
• Vorteile:
• direkter Zugriff auf Elemente in konstanter Zeit mittels A[i]
• sequentielles Durchlaufen sehr einfach
• Nachteile:
• Verschwendung von Speicher falls Liste nicht voll belegt
• Verlängern der sequentiellen Liste aufwendig
• Hinzufügen und Löschen von Elementen aufwendig
31
Optionales C++ Beispiel: sequentielles Durchlaufen
Code:
std :: vector < int > A (5); // Feld der Laenge 5
// sequentielles Durchlaufen
for ( int i = 0; i < A . size (); ++ i ) {
A[i] = i * i;
std :: cout << A [ i ] << " " ;
}
Ausgabe:
0 1 4 9 16
32
Verlängern der sequentiellen Liste
Gegeben: Feld A, Länge n+1, als sequentielle Liste
Gewünscht: Feld A erweitert auf Länge n+2
• neuen Speicher der Größe n+2 reservieren
• alte Liste in neuen Speicher kopieren
Feld A:
neues
Feld A:
A[n+1]
A[n]
A[n-1]
...
A[2]
A[1]
A[0]
A[n]
A[n-1]
...
A[2]
A[1]
A[0]
33
Optionales C++ Beispiel: Verlängern der Liste
Code:
std :: vector < int > A {0 , 1 , 4 , 9 , 16}; // Feld A
// A um 1 verlaengern
A . resize ( A . size () + 1);
// neues Element 25 am Ende einfuegen
A [ A . size () - 1] = 25;
// A ausgeben
for ( int i = 0; i < A . size (); ++ i )
std :: cout << A [ i ] << " " ;
Ausgabe:
0 1 4 9 16 25
34
Löschen von Element aus Liste
Gegeben: Feld A, Länge n, als sequentielle Liste
Gewünscht: Element i aus Feld A löschen
• Element i entfernen
• Listenelemente nach i umkopieren
25
16
9
4
1
0
25
16
9
4
1
35
Optionales C++ Beispiel: Löschen von Element
Code:
std :: vector < int > A {0 , 1 , 4 , 9 , 16 , 25}; // Feld A
// loesche erstes Element
A . erase ( A . begin () );
// A ausgeben
for ( int i = 0; i < A . size (); ++ i )
std :: cout << A [ i ] << " " ;
Ausgabe:
1 4 9 16 25
36
Einfügen von Element in Liste
Gegeben: Feld A, Länge n, als sequentielle Liste
Gewünscht: neues Element in Feld A an Stelle i einfügen
• Listenelemente nach i umkopieren
• Element i einfügen
25
25
16
9
4
1
16
9
8
4
1
37
Optionales C++ Beispiel: Einfügen von Element
Code:
std :: vector < int > A {1 , 4 , 9 , 16 , 25}; // Feld A
// neues Element 8 an Stelle 3 einfuegen
A . insert ( A . begin ()+2 , 8);
// A ausgeben
for ( int i = 0; i < A . size (); ++ i )
std :: cout << A [ i ] << " " ;
Ausgabe:
1 4 8 9 16 25
38
Ausblick: Anwendung von sequentiellen Listen
in 2D und 3D Bildern!
39
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
40
Bytes und ASCII
Interpretation eines Bytes als Zeichen (anstatt Zahlen)
−→ z.B. ASCII Code
7 Bit ASCII Code:
Code ..0 ..1
0..
1..
2..
3..
4..
5..
6..
7..
..2
..3
..4
..5
..6 ..7
nul soh stx etx eot enq ack
dle dc1 dc2 dc3 dc4 nak syn
sp !
“
# $ % &
0
1
2
3
4
5
6
@ A B C D E
F
P Q R
S T U V
‘
a
b
c
d
e
f
p
q
r
s
t
u
v
..8 ..9 ..A ..B ..C ..D ..E ..F
bel bs ht lf vt
etb can em sub esc
’
(
)
* +
7
8 9
:
;
G H I
J K
W X Y Z
[
g
h
i
j
k
w x
y
z
{
ff
fs
,
<
L
\
l
k
cr so si
gs rs us
. /
= > ?
M N O
] ˆ
m n o
} ˜ del
41
ASCII Erweiterungen, Unicode
• ASCII verwendet nur 7 Bit von einem Byte
• enthält z.B. keine Umlaute (ä, ö, ü) oder Akzente (é, ç)
• es gibt verschiedene Erweiterungen von ASCII auf 8 Bit
• in Europa ist ISO Latin-1 verbreitet (ISO Norm 8859-1)
• belegt die Codes von 128-255 (bzw. 80-FF in hex)
• Unicode wurde als 16 Bit Codierung eingeführt
• erste 128 Zeichen stimmen mit ASCII überein
• die nächsten 128 Zeichen mit ISO Latin-1
• danach z.B. kyrillische, arabische, japanische Schriftzeichen
• UTF-8 ist eine Mehrbyte-Codierung von Unicode (1-6 Bytes)
• Code-Länge wird durch die ersten Bits codiert
42
Zeichen und Zeichenfolgen
Repräsentation eines ASCII Zeichens in C++: char
• Zeichen-Literale in einfachen Anführungszeichen
Beispiele: ’A’, ’u’, ’D’
char zeichen = ’A ’;
• Vorsicht bei nicht-ASCII Zeichen!
Repräsentation einer Zeichenfolge? (Englisch: String)
• String-Literale in doppelten Anführungszeichen
Beispiel: “AuD“
• in C++ gespeichert als Feld von Zeichen:
'D'
'u'
'A'
43
Optionales C++ Beispiel: Strings
Code:
std :: string aud { " AvD " };
// String ausgeben
std :: cout << aud ;
// Fehler an 2. Stelle beheben
aud [1] = ’u ’;
// Leerzeichen und String ausgeben
std :: cout << " " << aud ;
Ausgabe:
AvD AuD
44
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
45
Definition Feld
Definition Feld
Ein Feld A ist eine Folge von n Datenelementen (di )i=1,...,n ,
A = d1 , d2 , . . . , dn
mit n ∈ N0 .
Die Datenelemente di sind beliebige Datentypen (z.B. primitive).
46
Feld als sequentielle Liste
Repräsentation von Feld A als sequentielle Liste (oder Array)
• feste Anzahl n von Datenelementen
• zusammenhängend gespeichert
• in linearer Reihenfolge mit Index
• Zugriff auf i-tes Element über Index i: A[i]
Feld A:
Deklaration:
in C++:
A[n-1]
A[n-2]
...
A[2]
A[1]
A[0]
std::vector<int> A(n);
47
Operationen auf sequentiellen Listen
Sei A sequentielle Liste.
Operationen:
• initialize: Initialisiere seq. Liste A mit n
Elementen
A[n-1]
A[n-2]
..
..
A[1]
A[0]
25
16
9
4
1
25
16
9
8
4
1
25
16
9
4
1
0
25
16
9
4
1
• elementAt(i): Zugriff auf i-tes Element von A:
A[i]
• insert: füge Element in seq. Liste A ein
(erfordert Umkopieren und evtl. Verlängern
von A)
• erase: entferne Element aus seq. Liste A
(erfordert Umkopieren)
48
Feld als einfach verkettete Liste
Repräsentation von Feld A als verkettete Liste
• dynamische Anzahl von Datenelementen
• in linearer Reihenfolge gespeichert (nicht notwendigerweise
zusammenhängend!)
• mit Referenzen oder Zeigern verkettet
start
Daten
Daten
Daten
Daten
next
next
next
next
null
auf Englisch: linked list
49
Verkettete Liste
start
Daten
Daten
Daten
Daten
next
next
next
next
null
• Folge von miteinander verbundenen Elementen
• jedes Element di besteht aus
• Daten: Wert des Feldes an Position i
• next: Referenz auf das nächste Element di+1
Node:
Daten
next
• start ist Referenz auf erstes Element des Feldes d1
• letztes Element dn hat keinen Nachfolger
• symbolisiert durch null-Referenz
50
Operationen auf verketteter Liste
Zugriff auf Element i:
• beginne bei start Referenz
• “vorhangeln” entlang next Referenzen bis zum i-ten Element
Beispiel für i=3:
start
Daten
Daten
Daten
Daten
next
next
next
next
null
Hilfsreferenz
51
Operationen auf verketteter Liste
Löschen von Element i:
• Zugriff auf Element i-1
• “umhängen” von next Referenz von Element i-1 auf Element
i+1
Beispiel für i=3:
start
Daten
Daten
Daten
Daten
next
next
next
next
null
Hilfsreferenz
52
Operationen auf verketteter Liste
Einfügen von Element an Stelle i:
• Zugriff auf Element i-1
• “umhängen” von next Referenz von Element i-1 auf neues
Element
• next Referenz von neuem Element setzen auf altes Element i
Beispiel für i=3:
start
Daten
Daten
Daten
Daten
next
next
next
next
null
Daten
Hilfsreferenz
neues Element
next
53
Optionales C++ Beispiel: Verkettete Liste (Auszug 1)
// Liste initialisieren
std :: forward_list < int > A {0 , 1 , 4 , 9 , 16};
// erstes Element aendern
A . front () = 7; // front () greift auf start Referenz zu
// Liste ausgeben
for ( int x : A )
std :: cout << x << " " ;
std :: cout << std :: endl ;
// Zugriff auf 3. Element
auto hilfsRef = A . begin (); // start Referenz
std :: advance ( hilfsRef , 2); // 2 Elemente weiter hangeln
std :: cout << " 3. Element : " << * hilfsRef << std :: endl ;
Ausgabe:
7 1 4 9 16
3. Element : 4
54
Optionales C++ Beispiel: Verkettete Liste (Auszug 2)
// 3. Element loeschen
auto hilfsRef = A . begin (); // start Referenz
std :: advance ( hilfsRef , 1); // zeigt nun auf 2. Element
A . erase_after ( hilfsRef ); // loesche Element nach hilfsRef
// Liste ausgeben
for ( int x : A )
std :: cout << x << " " ;
std :: cout << std :: endl ;
// neues Element an 3. Stelle einfuegen
A . insert_after ( hilfsRef , 8);
// Liste ausgeben
for ( int x : A )
std :: cout << x << " " ;
std :: cout << std :: endl ;
Ausgabe:
7 1 9 16
7 1 8 9 16
55
Gegenüberstellung sequentielle Liste und verkettete Liste
⊕
⊕
Sequentielle Liste
Direkter Zugriff auf i-tes
Element
sequentielles Durchlaufen
sehr einfach
statische Länge, kann Speicher verschwenden
⊕
⊕
Einfügen/Löschen erfordert
erheblich Kopieraufwand
⊕
Verkettete Liste
Zugriff auf i-tes Element erfordert i Iterationen
sequentielles Durchlaufen
sehr einfach
dynamische Länge
zusätzlicher Speicher für
Zeiger benötigt
Einfügen/Löschen einfach
56
Feld als doppelt verkettete Liste
Repräsentation von Feld A als doppelt verkettete Liste
• verkettete Liste
• jedes Element mit Referenzen doppelt verkettet
null
start
prev
prev
prev
prev
Daten
Daten
Daten
Daten
next
next
next
next
stop
null
auf Englisch: doubly linked list
57
Doppelt verkettete Liste
null
start
prev
prev
prev
prev
Daten
Daten
Daten
Daten
next
next
next
next
stop
null
• Folge von miteinander verbundenen Elementen
• jedes Element di besteht aus
• Daten: Wert des Feldes an Position i
• next: Referenz auf das nächste Element di+1
• prev: Referenz auf das vorherige Element di−1
Node:
prev
Daten
next
• start/stop sind Referenzen auf erstes/letztes Element des
58
Operationen auf doppelt verketteter Liste
Löschen von Element i:
• Zugriff auf Element i
• “umhängen” von next von Element i-1 auf Element i+1
• “umhängen” von prev von Element i+1 auf Element i-1
Beispiel für i=3:
null
start
prev
prev
prev
prev
Daten
Daten
Daten
Daten
next
next
next
next
stop
null
59
Operationen auf doppelt verketteter Liste
Einfügen von Element an Stelle i:
• Zugriff auf Element i
• “umhängen” von next von Element i-1 auf neues Element,
sowie “umhängen” von prev von altem Element i auf neues
Element
• next bzw. prev von neuem Element setzen auf altes Element i
bzw. Element i-1
Beispiel für i=3:
null
start
prev
prev
prev
prev
Daten
Daten
Daten
Daten
next
next
next
next
stop
null
prev
Daten
next
60
Eigenschaften doppelt verkettete Liste
Feld A als doppelt verkettete Liste
• Vorteile:
• Durchlauf in beiden Richtungen möglich
• Einfügen/Löschen potentiell einfacher, da man sich Vorgänger
nicht extra merken muss
• Nachteile:
• zusätzlicher Speicher erforderlich für zwei Referenzen
• Referenzverwaltung komplizierter und fehleranfällig
61
Optionales Beispiel in C++: Doppelt verkettete Liste 1
// Liste initialisieren
std :: list < int > A {0 , 1 , 4 , 9 , 16};
// erstes und letztes Element aendern
A . front () = 7; // front () greift auf start Referenz zu
A . back () = 23; // back () greift auf stop Referenz zu
// Liste ausgeben
for ( int x : A )
std :: cout << x << " " ;
std :: cout << std :: endl ;
// Zugriff auf 3. Element
auto hilfsRef = A . begin (); // start Referenz
std :: advance ( hilfsRef , 2); // zwei Elemente weiter hangeln
std :: cout << " 3. Element : " << * hilfsRef << std :: endl ;
Ausgabe:
7 1 4 9 23
3. Element : 4
62
Optionales Beispiel in C++: Doppelt verkettete Liste 2
// 3. Element loeschen
hilfsRef = A . begin (); // start Referenz
std :: advance ( hilfsRef , 2); // zwei Elemente weiter hangeln
hilfsRef = A . erase ( hilfsRef ); // Element loeschen
// Liste ausgeben
for ( int x : A )
std :: cout << x << " " ;
std :: cout << std :: endl ;
// neues Element an 3. Stelle einfuegen
A . insert ( hilfsRef , 8);
// Liste ausgeben
for ( int x : A )
std :: cout << x << " " ;
std :: cout << std :: endl ;
Ausgabe:
7 1 9 23
7 1 8 9 23
63
Zusammenfassung Felder
Ein Feld A kann repräsentiert werden als:
• sequentielle Liste (array)
• mit fixer Länge
• verkettete Liste (linked list)
• mit dynamischer Länge
• doppelt verkettete Liste (doubly linked list)
• mit dynamischer Länge
Eigenschaften:
• einfach und flexibel
• aber manche Operationen aufwendig
Als nächstes −→ Aufgabe von Flexibilität für Effizienz
64
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
65
Definition Abstrakter Datentyp
Abstrakter Datentyp (englisch: abstract data type, ADT)
Ein abstrakter Datentyp ist ein mathematisches Modell für
bestimmte Datenstrukturen mit vergleichbarem Verhalten.
Ein abstrakter Datentyp wird indirekt definiert über
• mögliche Operationen auf ihm sowie
• mathematische Bedingungen (oder: constraints) über die
Auswirkungen der Operationen (u.U. auch die Kosten der
Operationen).
66
Beispiel abstrakter Datentyp: abstrakte Variable
Abstrakte Variable V ist eine veränderliche Dateneinheit
mit zwei Operationen
• load(V) liefert einen Wert
• store(V, x) wobei x ein Wert
und der Bedingung
• load(V) liefert immer den Wert x der letzten Operation
store(V, x)
67
Beispiel abstrakter Datentyp: abstrakte Liste (Teil 1)
Abstrakte Liste L ist ein Datentyp
mit Operationen
• pushFront(L, x) liefert eine Liste
• front(L) liefert ein Element
• rest(L) liefert eine Liste
und den Bedingungen
• ist x Element, L Liste, dann liefert front(pushFront(L, x)) das
Element x.
• ist x Element, L Liste, dann liefert rest(pushFront(L, x)) die
Liste L.
68
Beispiel abstrakter Datentyp: abstrakte Liste (Teil 2)
Abstrakte Liste L. Weitere Operationen sind
• isEmpty(L) liefert true oder false
• initialize() liefert eine Listen Instanz
mit den Bedingungen
• initialize() 6= L für jede Liste L (d.h. jede neue Liste ist
separat von alten Listen)
• isEmpty(initialize()) == true (d.h. eine neue Liste ist leer)
• isEmpty(pushFront(L, x)) == false (d.h. eine Liste ist nach
einem pushFront nicht leer)
69
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
70
Definition Stack
Stack (oder deutsch: Stapel, Keller)
Ein Stack ist ein abstrakter Datentyp. Er beschreibt eine spezielle
Listenstruktur nach dem Last In – First Out (LIFO) Prinzip mit
den Eigenschaften
• löschen, einfügen ist nur am Ende der Liste erlaubt,
• nur das letzte Element darf manipuliert werden.
Operationen auf Stacks:
• push: legt ein Element auf den Stack (einfügen)
• pop: entfernt das letzte Element vom Stack (löschen)
• top: liefert das letzte Stack-Element
• isEmpty: liefert true falls Stack leer
• initialize: Stack erzeugen und in Anfangszustand (leer) setzen
71
Definition Stack
Stack (oder deutsch: Stapel, Keller)
Ein Stack ist ein abstrakter Datentyp. Er beschreibt eine spezielle
Listenstruktur nach dem Last In – First Out (LIFO) Prinzip mit
den Eigenschaften
• löschen, einfügen ist nur am Ende der Liste erlaubt,
• nur das letzte Element darf manipuliert werden.
"push"
Piz
za
Piz
za
neu
e
Piz
Piz
z
a
za
#3
#2
#1
72
Definition Stack (exakter)
Stack S ist ein abstrakter Datentyp mit Operationen
• pop(S) liefert einen Wert
• push(S, x) wobei x ein Wert
mit der Bedingung
• ist x Wert und V abstrakte Variable, dann ist die Sequenz
push(S, x); store(V, pop(S)) äquivalent zu store(V, x)
sowie der Operation
• top(S) liefert einen Wert
mit der Bedingung
• ist x Wert und V abstrakte Variable, dann ist die Sequenz
push(S, x); store(V, top(S)); äquivalent zu
push(S, x); store(V, x)
73
Definition Stack (exakter, Teil 2)
Stack S. Weitere Operationen sind
• isEmpty(S) liefert true oder false
• initialize() liefert eine Stack Instanz
mit den Bedingungen
• initialize() 6= S für jeden Stack S (d.h. jeder neue Stack ist
separat von alten Stacks)
• isEmpty(initialize()) == true (d.h. ein neuer Stack ist leer)
• isEmpty(push(S, x)) == false (d.h. ein Stack nach push ist
nicht leer)
74
Anwendungsbeispiele Stack
• Auswertung arithmetischer Ausdrücke (s. nächste Folie)
• Call-Stack bei Funktionsaufrufen
• Einfache Vorwärts- / Rückwärts Funktion in Software
• z.B. im Internet-Browser
• Syntaxanalyse eines Programms
• z.B. zur Erkennung von Syntax-Fehlern durch Compiler
75
Auswertung arithmetischer Ausdrücke
Gegeben sei ein vollständig geklammerter, einfacher arithmetischer
Ausdruck mit Bestandteilen Zahl, +, *, =
Beispiel: (3 * (4 + 5)) =
Schema:
• arbeite Ausdruck von links nach rechts ab, speichere jedes
Zeichen ausser ) und = in Stack S
• bei ) werte die 3 obersten Elemente von S aus, dann entferne
die passende Klammer ( vom Stack S und speichere Ergebnis
in Stack S
• bei = steht das Ergebnis im obersten Stack-Element von S
76
Implementation Stack
Stack ist abstrakter Datentyp.
• Implementation ist nicht festgelegt
• nur Operationen und Bedingungen sind festgelegt
Stack kann auf viele Arten implementiert werden, zum Beispiel als:
• sequentielle Liste
• verkettete Liste
78
Implementation Stack als sequentielle Liste
• Stack-Elemente speichern in sequentieller Liste A (Länge n)
• oberstes Stack-Element merken mittels Variable top
• falls Stack leer ist top == -1
n-1
n-2
...
1
0
-1
top
• push(x) inkrementiert top und speichert x in A[top]
• pop() liefert A[top] zurück und dekrementiert top
• top() liefert A[top] zurück
79
Implementation Stack als sequentielle Liste
n-1
n-2
...
1
0
-1
initialize();
top
n-1
n-2
...
1
0
9
4
1
...
1
0
9
4
1
-1
push(1);
push(4);
push(9);
top
n-1
n-2
-1
pop();
top
80
Implementation Stack als verkettete Liste
• Stack-Elemente speichern in verketteter Liste L
• oberstes Stack-Element wird durch start Referenz markiert
start
Daten
Daten
Daten
Daten
next
next
next
next
null
• push(x) fügt Element an erster Position ein
• pop() liefert Element an erster Position zurück und entfernt es
• top() liefert Element an erster Position zurück
81
Optional: Stacks in C++
• in der C++ Standard Library:
std::stack<datatype>
• unterstützte Operationen:
• push
• pop (liefert aber nichts zurück!)
• top
• empty
• implementiert wahlweise als
• doppelt verkettete Liste
als std::stack<datatype, std::list<datatype> >
• sequentielle Liste
als std::stack<datatype, std::vector<datatype> >
• std::dequeue (default, zerstückelte sequentielle Liste)
82
Optionales C++ Beispiel: Stack
std :: stack < int > S ; // erstelle Stack S
if ( S . empty ())
std :: cout << " Stack S ist leer . " << std :: endl ;
// fuege Elemente 1 , 4 , 9 hinzu
S . push (1); S . push (4); S . push (9);
// gib oberstes Element aus
std :: cout << " top = " << S . top () << std :: endl ;
// entferne oberstes Element
S . pop ();
// gib oberstes Element aus
std :: cout << " top = " << S . top () << std :: endl ;
Ausgabe:
Stack S ist leer .
top = 9
top = 4
83
Zusammenfassung Stack
• Stack ist abstrakter Datentyp als Metapher für einen Stapel
• wesentliche Operationen: push, pop
• Implementation als sequentielle Liste
• fixe Größe (entweder Speicher verschwendet oder zu klein)
• push, pop sehr effizient
• Implementation als verkettete Liste
• dynamische Größe, aber Platz für Zeiger “verschwendet”
• push, pop sehr effizient
84
Programm heute
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
85
Definition Queue
Queue (oder deutsch: Warteschlange)
Eine Queue ist ein abstrakter Datentyp. Sie beschreibt eine
spezielle Listenstruktur nach dem First In – First Out (FIFO)
Prinzip mit den Eigenschaften
• einfügen ist nur am Ende der Liste erlaubt,
• entfernen ist nur am Anfang der Liste erlaubt.
Person verlässt Schlange
Person stellt sich an
86
Definition Queue
Queue (oder deutsch: Warteschlange)
Eine Queue ist ein abstrakter Datentyp. Sie beschreibt eine
spezielle Listenstruktur nach dem First In – First Out (FIFO)
Prinzip mit den Eigenschaften
• einfügen ist nur am Ende der Liste erlaubt,
• entfernen ist nur am Anfang der Liste erlaubt.
Operationen auf Queues:
• enqueue: fügt ein Element am Ende der Schlange hinzu
• dequeue: entfernt das erste Element der Schlange
• isEmpty: liefert true falls Queue leer
• initialize: Queue erzeugen und in Anfangszustand (leer) setzen
87
Definition Queue (exakter)
Queue Q ist ein abstrakter Datentyp mit Operationen
• dequeue(Q) liefert einen Wert
• enqueue(Q, x) wobei x ein Wert
• isEmpty(Q) liefert true oder false
• initialize liefert eine Queue Instanz
und mit Bedingungen
• ist x Wert, V abstrakte Variable und Q eine leere Queue,
dann ist die Sequenz enqueue(Q, x); store(V, dequeue(Q))
äquivalent zu store(V, x)
• sind x,y Werte, V abstrakte Variable und Q eine leere Queue,
dann ist die Sequenz enqueue(Q, x); enqueue(Q, y); store(V,
dequeue(Q)) äquivalent zu store(V, x); enqueue(Q, y)
• initialize() 6= Q für jede Queue Q
• isEmpty(initialize()) == true
• isEmpty(enqueue(Q, x)) == false
88
Beispiel: Queue
Q:
Q = initialize();
Anfang
Q:
1
enqueue(1);
Anfang
Q:
1
2
enqueue(2);
Anfang
Q:
1
2
3
enqueue(3);
Anfang
Q:
2
3
dequeue();
Anfang
Q:
3
dequeue();
Anfang
89
Anwendungsbeispiele Queue
• Druckerwarteschlange
• Playlist von iTunes (oder ähnlichem Musikprogramm)
• Kundenaufträge bei Webshops
• Warteschlange für Prozesse im Betriebssystem (Multitasking)
90
Anwendungsbeispiel Stack und Queue
Palindrom
Ein Palindrom ist eine Zeichenkette, die von vorn und von hinten
gelesen gleich bleibt.
Beispiel: Reittier
• Erkennung ob Zeichenkette ein Palindrom ist
• ein Stack kann die Reihenfolge der Zeichen umkehren
• eine Queue behält die Reihenfolge der Zeichen
91
Palindrom Erkennung
Algorithmus:
• Eingabe: Zeichenkette k
• durchlaufe k von links nach rechts
• füge dabei jedes Zeichen in Stack S
(push) und Queue Q (enqueue) ein
• leere den Stack S (pop) und die Queue Q
(dequeue) und vergleiche die Zeichen
• falls die Zeichen nicht gleich sind, ist k
kein Palindrom
• ansonsten ist k Palindrom
Zeichenkette k: RADAR
Queue Q:
RADAR
Anfang
Stack S:
R
A
D
A
R
top
• Ausgabe: k ist Palindrom oder nicht
92
Implementation Queue
Auch Queue ist abstrakter Datentyp.
• Implementation ist nicht festgelegt
• nur Operationen und Bedingungen sind festgelegt
Queue kann auf viele Arten implementiert werden, zum Beispiel
als:
• verkettete Liste
• sequentielle Liste
93
Implementation Queue als verkettete Liste
• Queue-Elemente speichern in verketteter Liste L
• Anfang der Queue wird durch anfang Referenz markiert
• Ende der Queue wird durch extra ende Referenz markiert
anfang
Daten
Daten
Daten
Daten
next
next
next
next
ende
NULL
• enqueue(x) fügt Element bei ende Referenz ein
• dequeue() liefert Element bei anfang Referenz zurück und
entfernt es
94
Implementation Queue als sequentielle Liste
• Queue-Element speichern in sequentieller Liste L (Länge n)
• Anfang der Queue wird durch Index anfang markiert
• Ende der Queue wird durch Index ende markiert
n-1
n-2
...
2
1
0
15
8
0
ende
anfang
• enqueue(x) fügt Element bei Index ende+1 ein
• dequeue liefert Element bei Index anfang zurück und entfernt
es durch Inkrement von anfang
95
Implementation Queue als sequentielle Liste 2
Problem:
n-1
n-2
...
2
1
0
15
8
0
ende
anfang
wird nach ein paar Operationen zu
n-1
n-2
...
47
11
3
ende
2
1
0
anfang
Linksdrift!
Lösungsansatz: zirkuläre sequentielle Liste.
96
Implementation Queue als zwei Stacks
• Queue Q kann mittels zwei Stacks implementiert werden
• erster Stack inbox wird für enqueue benutzt:
• Q.enqueue(x) resultiert in inbox.push(x)
• zweiter Stack outbox wird für dequeue benutzt:
• falls outbox leer, kopiere alle Elemente von inbox zu outbox:
outbox.push( inbox.pop() )
• Q.dequeue liefert outbox.pop() zurück
dequeue
enqueue
3
1
2
2
1
3
inbox
outbox
inbox
outbox
97
Optional: Queues in C++
• in der C++ Standard Library:
std::queue<datatype>
• unterstützte Operationen:
• push (= enqueue)
• pop (= dequeue, liefert aber nichts zurück!)
• front
• back
• empty
• implementiert wahlweise als
• doppelt verkettete Liste
als std::queue<datatype, std::list<datatype >
• std::dequeue (default, zerstückelte sequentielle Liste)
98
Optionales C++ Beispiel: Queue
std :: queue < int > Q ; // erstelle Queue Q
if ( Q . empty ())
std :: cout << " Queue Q ist leer . " << std :: endl ;
// fuege Elemente 1 , 2 , 3 hinzu
Q . push (1); Q . push (2); Q . push (3);
// gib vorderstes Element aus
std :: cout << " front = " << Q . front () << std :: endl ;
// entferne vorderstes Element
Q . pop ();
// gib vorderstes Element aus
std :: cout << " front = " << Q . front () << std :: endl ;
Ausgabe:
Queue Q ist leer .
front = 1
front = 2
99
Zusammenfassung Queue
• Queue ist abstrakter Datentyp als Metapher für eine
Warteschlange
• wesentliche Operationen: enqueue, dequeue
• Implementation als verkettete Liste
• dynamische Größe, aber Platz für Referenzen “verschwendet”
• enqueue, dequeue sehr effizient
• Implementation als sequentielle Liste
• fixe Größe (entweder Speicher verschwendet oder zu klein)
• enqueue, dequeue sehr effizient
• Queue sehr schnell voll durch “Linksdrift”
(ist aber durch zirkuläre sequentielle Liste lösbar)
100
Zusammenfassung
1 Einführung
2 Grundlagen von Algorithmen
3 Grundlagen von Datenstrukturen
Primitive Datentypen und Zahldarstellung
Felder als sequentielle Liste
Zeichen und Zeichenfolgen
Felder als verkettete Liste
Abstrakte Datentypen
Stacks
Queues
101
Herunterladen