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